* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/bin/scripts/createlang.c,v 1.7 2003/11/29 19:52:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/createlang.c,v 1.8 2004/03/19 18:58:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
char *p;
bool handlerexists;
+ bool validatorexists;
bool trusted;
char *handler;
+ char *validator = NULL;
char *object;
PQExpBufferData sql;
{
trusted = true;
handler = "plpgsql_call_handler";
+ validator = "plpgsql_validator";
object = "plpgsql";
}
else if (strcmp(langname, "pltcl") == 0)
/*
* Check whether the call handler exists
*/
- printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND prorettype = (SELECT oid FROM pg_type WHERE typname = 'language_handler') AND pronargs = 0;", handler);
+ printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND prorettype = 'pg_catalog.language_handler'::regtype AND pronargs = 0;", handler);
result = executeQuery(conn, sql.data, progname, echo);
handlerexists = (PQntuples(result) > 0);
PQclear(result);
/*
- * Create the call handler and the language
+ * Check whether the validator exists
+ */
+ if (validator)
+ {
+ printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND proargtypes[0] = 'pg_catalog.oid'::regtype AND pronargs = 1;", validator);
+ result = executeQuery(conn, sql.data, progname, echo);
+ validatorexists = (PQntuples(result) > 0);
+ PQclear(result);
+ }
+ else
+ validatorexists = true; /* don't try to create it */
+
+ /*
+ * Create the function(s) and the language
*/
resetPQExpBuffer(&sql);
"CREATE FUNCTION \"%s\" () RETURNS language_handler AS '%s/%s' LANGUAGE C;\n",
handler, pglib, object);
+ if (!validatorexists)
+ appendPQExpBuffer(&sql,
+ "CREATE FUNCTION \"%s\" (oid) RETURNS void AS '%s/%s' LANGUAGE C;\n",
+ validator, pglib, object);
+
appendPQExpBuffer(&sql,
- "CREATE %sLANGUAGE \"%s\" HANDLER \"%s\";\n",
+ "CREATE %sLANGUAGE \"%s\" HANDLER \"%s\"",
(trusted ? "TRUSTED " : ""), langname, handler);
+ if (validator)
+ appendPQExpBuffer(&sql, " VALIDATOR \"%s\"", validator);
+
+ appendPQExpBuffer(&sql, ";\n");
+
if (echo)
printf("%s", sql.data);
result = PQexec(conn, sql.data);
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/bin/scripts/droplang.c,v 1.6 2003/11/29 19:52:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/droplang.c,v 1.7 2004/03/19 18:58:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "common.h"
#include "print.h"
+#define atooid(x) ((Oid) strtoul((x), NULL, 10))
+
static void help(const char *progname);
char *langname = NULL;
char *p;
- char *lanplcallfoid;
+ Oid lanplcallfoid;
+ Oid lanvalidator;
char *handler;
+ char *validator;
bool keephandler;
+ bool keepvalidator;
PQExpBufferData sql;
conn = connectDatabase(dbname, host, port, username, password, progname);
/*
- * Make sure the language is installed and find the OID of the handler
- * function
+ * Make sure the language is installed and find the OIDs of the handler
+ * and validator functions
*/
- printfPQExpBuffer(&sql, "SELECT lanplcallfoid FROM pg_language WHERE lanname = '%s' AND lanispl;", langname);
+ printfPQExpBuffer(&sql, "SELECT lanplcallfoid, lanvalidator FROM pg_language WHERE lanname = '%s' AND lanispl;", langname);
result = executeQuery(conn, sql.data, progname, echo);
if (PQntuples(result) == 0)
{
progname, langname, dbname);
exit(1);
}
- lanplcallfoid = PQgetvalue(result, 0, 0);
- /* result not cleared! */
+ lanplcallfoid = atooid(PQgetvalue(result, 0, 0));
+ lanvalidator = atooid(PQgetvalue(result, 0, 1));
+ PQclear(result);
/*
* Check that there are no functions left defined in that language
/*
* Check that the handler function isn't used by some other language
*/
- printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanplcallfoid = %s AND lanname <> '%s';", lanplcallfoid, langname);
+ printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanplcallfoid = %u AND lanname <> '%s';", lanplcallfoid, langname);
result = executeQuery(conn, sql.data, progname, echo);
if (strcmp(PQgetvalue(result, 0, 0), "0") == 0)
keephandler = false;
*/
if (!keephandler)
{
- printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %s;", lanplcallfoid);
+ printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %u;", lanplcallfoid);
result = executeQuery(conn, sql.data, progname, echo);
- handler = PQgetvalue(result, 0, 0);
- /* result not cleared! */
+ handler = strdup(PQgetvalue(result, 0, 0));
+ PQclear(result);
}
else
handler = NULL;
/*
- * Drop the language
+ * Check that the validator function isn't used by some other language
+ */
+ if (OidIsValid(lanvalidator))
+ {
+ printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanvalidator = %u AND lanname <> '%s';", lanvalidator, langname);
+ result = executeQuery(conn, sql.data, progname, echo);
+ if (strcmp(PQgetvalue(result, 0, 0), "0") == 0)
+ keepvalidator = false;
+ else
+ keepvalidator = true;
+ PQclear(result);
+ }
+ else
+ keepvalidator = true; /* don't try to delete it */
+
+ /*
+ * Find the validator name
+ */
+ if (!keepvalidator)
+ {
+ printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %u;", lanvalidator);
+ result = executeQuery(conn, sql.data, progname, echo);
+ validator = strdup(PQgetvalue(result, 0, 0));
+ PQclear(result);
+ }
+ else
+ validator = NULL;
+
+ /*
+ * Drop the language and the functions
*/
printfPQExpBuffer(&sql, "DROP LANGUAGE \"%s\";\n", langname);
if (!keephandler)
appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\" ();\n", handler);
+ if (!keepvalidator)
+ appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\" (oid);\n", validator);
if (echo)
printf("%s", sql.data);
result = PQexec(conn, sql.data);
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.150 2004/02/24 22:59:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.151 2004/03/19 18:58:07 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
DATA(insert OID = 1005 ( _int2 PGNSP PGUID -1 f b t \054 0 21 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
DATA(insert OID = 1006 ( _int2vector PGNSP PGUID -1 f b t \054 0 22 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
DATA(insert OID = 1007 ( _int4 PGNSP PGUID -1 f b t \054 0 23 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
+#define INT4ARRAYOID 1007
DATA(insert OID = 1008 ( _regproc PGNSP PGUID -1 f b t \054 0 24 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
DATA(insert OID = 1009 ( _text PGNSP PGUID -1 f b t \054 0 25 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
DATA(insert OID = 1028 ( _oid PGNSP PGUID -1 f b t \054 0 26 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.73 2004/01/07 18:56:30 neilc Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.74 2004/03/19 18:58:07 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
*/
static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup,
- PLpgSQL_func_hashkey *hashkey);
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator);
static void plpgsql_compile_error_callback(void *arg);
static char **fetchArgNames(HeapTuple procTup, int nargs);
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
static void compute_function_hashkey(FunctionCallInfo fcinfo,
Form_pg_proc procStruct,
- PLpgSQL_func_hashkey *hashkey);
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator);
static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
static void plpgsql_HashTableInsert(PLpgSQL_function *function,
PLpgSQL_func_hashkey *func_key);
/* ----------
* plpgsql_compile Make an execution tree for a PL/pgSQL function.
*
+ * If forValidator is true, we're only compiling for validation purposes,
+ * and so some checks are skipped.
+ *
* Note: it's important for this to fall through quickly if the function
* has already been compiled.
* ----------
*/
PLpgSQL_function *
-plpgsql_compile(FunctionCallInfo fcinfo)
+plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
{
Oid funcOid = fcinfo->flinfo->fn_oid;
HeapTuple procTup;
plpgsql_HashTableInit();
/* Compute hashkey using function signature and actual arg types */
- compute_function_hashkey(fcinfo, procStruct, &hashkey);
+ compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator);
hashkey_valid = true;
/* And do the lookup */
* the completed function.
*/
if (!hashkey_valid)
- compute_function_hashkey(fcinfo, procStruct, &hashkey);
+ compute_function_hashkey(fcinfo, procStruct, &hashkey,
+ forValidator);
/*
* Do the hard part.
*/
- function = do_compile(fcinfo, procTup, &hashkey);
+ function = do_compile(fcinfo, procTup, &hashkey, forValidator);
}
ReleaseSysCache(procTup);
static PLpgSQL_function *
do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup,
- PLpgSQL_func_hashkey *hashkey)
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;
/*
* Check for a polymorphic returntype. If found, use the
* actual returntype type from the caller's FuncExpr node, if
- * we have one.
+ * we have one. (In validation mode we arbitrarily assume we
+ * are dealing with integers.)
*
* Note: errcode is FEATURE_NOT_SUPPORTED because it should
* always work; if it doesn't we're in some context that fails
rettypeid = procStruct->prorettype;
if (rettypeid == ANYARRAYOID || rettypeid == ANYELEMENTOID)
{
- rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
+ if (forValidator)
+ {
+ if (rettypeid == ANYARRAYOID)
+ rettypeid = INT4ARRAYOID;
+ else
+ rettypeid = INT4OID;
+ }
+ else
+ rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
if (!OidIsValid(rettypeid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
}
-/* ---------
- * plpgsql_yyerror Handle parser error
- * ---------
- */
-
-void
-plpgsql_yyerror(const char *s)
-{
- plpgsql_error_lineno = plpgsql_scanner_lineno();
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- /* translator: first %s is a phrase like "syntax error" */
- errmsg("%s at or near \"%s\"", s, plpgsql_yytext)));
-}
-
-
/*
* Compute the hashkey for a given function invocation
*
static void
compute_function_hashkey(FunctionCallInfo fcinfo,
Form_pg_proc procStruct,
- PLpgSQL_func_hashkey *hashkey)
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator)
{
int i;
/* get function OID */
hashkey->funcOid = fcinfo->flinfo->fn_oid;
- /* if trigger, get relation OID */
- if (CALLED_AS_TRIGGER(fcinfo))
+ /*
+ * if trigger, get relation OID. In validation mode we do not know what
+ * relation is intended to be used, so we leave trigrelOid zero; the
+ * hash entry built in this case will never really be used.
+ */
+ if (CALLED_AS_TRIGGER(fcinfo) && !forValidator)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
/*
* Check for polymorphic arguments. If found, use the actual
* parameter type from the caller's FuncExpr node, if we have one.
+ * (In validation mode we arbitrarily assume we are dealing with
+ * integers. This lets us build a valid, if possibly useless,
+ * function hashtable entry.)
*
* We can support arguments of type ANY the same way as normal
* polymorphic arguments.
if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
argtypeid == ANYOID)
{
- argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
+ if (forValidator)
+ {
+ if (argtypeid == ANYARRAYOID)
+ argtypeid = INT4ARRAYOID;
+ else
+ argtypeid = INT4OID;
+ }
+ else
+ argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
if (!OidIsValid(argtypeid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.19 2003/11/29 19:52:12 pgsql Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.20 2004/03/19 18:58:07 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
+extern bool check_function_bodies;
+
static int plpgsql_firstcall = 1;
static void plpgsql_init_all(void);
/* ----------
* plpgsql_call_handler
*
- * This is the only visible function of the PL interpreter.
* The PostgreSQL function manager and trigger manager
* call this function for execution of PL/pgSQL procedures.
* ----------
elog(ERROR, "SPI_connect failed");
/* Find or compile the function */
- func = plpgsql_compile(fcinfo);
+ func = plpgsql_compile(fcinfo, false);
/*
* Determine if called as function or trigger and call appropriate
return retval;
}
+
+/* ----------
+ * plpgsql_validator
+ *
+ * This function attempts to validate a PL/pgSQL function at
+ * CREATE FUNCTION time.
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_validator);
+
+Datum
+plpgsql_validator(PG_FUNCTION_ARGS)
+{
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc proc;
+ char functyptype;
+ bool istrigger = false;
+ bool haspolyresult;
+ bool haspolyarg;
+ int i;
+
+ /* perform initialization */
+ plpgsql_init_all();
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(funcoid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcoid);
+ proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+ functyptype = get_typtype(proc->prorettype);
+
+ /* Disallow pseudotype result */
+ /* except for TRIGGER, RECORD, VOID, ANYARRAY, or ANYELEMENT */
+ if (functyptype == 'p')
+ {
+ /* we assume OPAQUE with no arguments means a trigger */
+ if (proc->prorettype == TRIGGEROID ||
+ (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+ istrigger = true;
+ else if (proc->prorettype == ANYARRAYOID ||
+ proc->prorettype == ANYELEMENTOID)
+ haspolyresult = true;
+ else if (proc->prorettype != RECORDOID &&
+ proc->prorettype != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("plpgsql functions cannot return type %s",
+ format_type_be(proc->prorettype))));
+ }
+
+ /* Disallow pseudotypes in arguments */
+ /* except for ANYARRAY or ANYELEMENT */
+ haspolyarg = false;
+ for (i = 0; i < proc->pronargs; i++)
+ {
+ if (get_typtype(proc->proargtypes[i]) == 'p')
+ {
+ if (proc->proargtypes[i] == ANYARRAYOID ||
+ proc->proargtypes[i] == ANYELEMENTOID)
+ haspolyarg = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("plpgsql functions cannot take type %s",
+ format_type_be(proc->proargtypes[i]))));
+ }
+ }
+
+ /* Postpone body checks if !check_function_bodies */
+ if (check_function_bodies)
+ {
+ FunctionCallInfoData fake_fcinfo;
+ FmgrInfo flinfo;
+ TriggerData trigdata;
+
+ /*
+ * Connect to SPI manager (is this needed for compilation?)
+ */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ /*
+ * Set up a fake fcinfo with just enough info to satisfy
+ * plpgsql_compile().
+ */
+ MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ fake_fcinfo.flinfo = &flinfo;
+ flinfo.fn_oid = funcoid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+ if (istrigger)
+ {
+ MemSet(&trigdata, 0, sizeof(trigdata));
+ trigdata.type = T_TriggerData;
+ fake_fcinfo.context = (Node *) &trigdata;
+ }
+
+ /* Test-compile the function */
+ plpgsql_compile(&fake_fcinfo, true);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+ }
+
+ ReleaseSysCache(tuple);
+
+ PG_RETURN_VOID();
+}
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.44 2004/02/25 18:10:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.45 2004/03/19 18:58:07 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
* Functions in pl_comp.c
* ----------
*/
-extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo);
+extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo,
+ bool forValidator);
extern int plpgsql_parse_word(char *word);
extern int plpgsql_parse_dblword(char *word);
extern int plpgsql_parse_tripword(char *word);
extern PLpgSQL_row *plpgsql_build_rowtype(Oid classOid);
extern void plpgsql_adddatum(PLpgSQL_datum * new);
extern int plpgsql_add_initdatums(int **varnos);
-extern void plpgsql_yyerror(const char *s);
extern void plpgsql_HashTableInit(void);
/* ----------
*/
extern void plpgsql_init(void);
extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS);
+extern Datum plpgsql_validator(PG_FUNCTION_ARGS);
/* ----------
* Functions in pl_exec.c
extern int plpgsql_base_yylex(void);
extern int plpgsql_yylex(void);
extern void plpgsql_push_back_token(int token);
+extern void plpgsql_yyerror(const char *message);
extern int plpgsql_scanner_lineno(void);
extern void plpgsql_scanner_init(const char *str, int functype);
extern void plpgsql_scanner_finish(void);
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.32 2004/02/25 18:10:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.33 2004/03/19 18:58:07 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
#include "plpgsql.h"
+#include "mb/pg_wchar.h"
+
/* No reason to constrain amount of data slurped */
#define YY_READ_BUF_SIZE 16777216
have_pushback_token = true;
}
+/*
+ * Report a syntax error.
+ */
+void
+plpgsql_yyerror(const char *message)
+{
+ const char *loc = yytext;
+ int cursorpos;
+
+ plpgsql_error_lineno = plpgsql_scanner_lineno();
+
+ /* in multibyte encodings, return index in characters not bytes */
+ cursorpos = pg_mbstrlen_with_len(scanbuf, loc - scanbuf) + 1;
+
+ if (*loc == YY_END_OF_BUFFER_CHAR)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: %s is typically "syntax error" */
+ errmsg("%s at end of input", message),
+ errposition(cursorpos)));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: first %s is typically "syntax error" */
+ errmsg("%s at or near \"%s\"", message, loc),
+ errposition(cursorpos)));
+ }
+}
+
/*
* Get the line number at which the current token ends. This substitutes
* for flex's very poorly implemented yylineno facility.
{
Size slen;
- /*----------
- * Hack: skip any initial newline, so that in the common coding layout
- * CREATE FUNCTION ... AS '
- * code body
- * ' LANGUAGE 'plpgsql';
- * we will think "line 1" is what the programmer thinks of as line 1.
- *----------
- */
- if (*str == '\r')
- str++;
- if (*str == '\n')
- str++;
-
slen = strlen(str);
/*
cur_line_start = scanbuf;
cur_line_num = 1;
+ /*----------
+ * Hack: skip any initial newline, so that in the common coding layout
+ * CREATE FUNCTION ... AS '
+ * code body
+ * ' LANGUAGE 'plpgsql';
+ * we will think "line 1" is what the programmer thinks of as line 1.
+ *----------
+ */
+ if (*cur_line_start == '\r')
+ cur_line_start++;
+ if (*cur_line_start == '\n')
+ cur_line_start++;
+
BEGIN(INITIAL);
}