* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/scan.l,v 1.163 2009/11/09 18:38:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/scan.l,v 1.164 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static char *litbufdup(core_yyscan_t yyscanner);
static char *litbuf_udeescape(unsigned char escape, core_yyscan_t yyscanner);
static unsigned char unescape_single_char(unsigned char c, core_yyscan_t yyscanner);
+static int process_integer_literal(const char *token, YYSTYPE *lval);
static bool is_utf16_surrogate_first(pg_wchar c);
static bool is_utf16_surrogate_second(pg_wchar c);
static pg_wchar surrogate_pair_to_codepoint(pg_wchar first, pg_wchar second);
* instead we pass it separately to parser. there it gets
* coerced via doNegate() -- Leon aug 20 1999
*
+* {decimalfail} is used because we would like "1..10" to lex as 1, dot_dot, 10.
+*
* {realfail1} and {realfail2} are added to prevent the need for scanner
* backup when the {real} rule fails to match completely.
*/
integer {digit}+
decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*))
+decimalfail {digit}+\.\.
real ({integer}|{decimal})[Ee][-+]?{digit}+
realfail1 ({integer}|{decimal})[Ee]
realfail2 ({integer}|{decimal})[Ee][-+]
}
{integer} {
- long val;
- char* endptr;
-
SET_YYLLOC();
- errno = 0;
- val = strtol(yytext, &endptr, 10);
- if (*endptr != '\0' || errno == ERANGE
-#ifdef HAVE_LONG_INT_64
- /* if long > 32 bits, check for overflow of int4 */
- || val != (long) ((int32) val)
-#endif
- )
- {
- /* integer too large, treat it as a float */
- yylval->str = pstrdup(yytext);
- return FCONST;
- }
- yylval->ival = val;
- return ICONST;
+ return process_integer_literal(yytext, yylval);
}
{decimal} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return FCONST;
}
+{decimalfail} {
+ /* throw back the .., and treat as integer */
+ yyless(yyleng-2);
+ SET_YYLLOC();
+ return process_integer_literal(yytext, yylval);
+ }
{real} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return new;
}
+static int
+process_integer_literal(const char *token, YYSTYPE *lval)
+{
+ long val;
+ char *endptr;
+
+ errno = 0;
+ val = strtol(token, &endptr, 10);
+ if (*endptr != '\0' || errno == ERANGE
+#ifdef HAVE_LONG_INT_64
+ /* if long > 32 bits, check for overflow of int4 */
+ || val != (long) ((int32) val)
+#endif
+ )
+ {
+ /* integer too large, treat it as a float */
+ lval->str = pstrdup(token);
+ return FCONST;
+ }
+ lval->ival = val;
+ return ICONST;
+}
+
static int
hexval(unsigned char c)
{
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/scansup.c,v 1.37 2009/01/01 17:23:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/scansup.c,v 1.38 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
scanner_isspace(char ch)
{
/* This must match scan.l's list of {space} characters */
- /* and plpgsql's scan.l as well */
if (ch == ' ' ||
ch == '\t' ||
ch == '\n' ||
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.29 2009/09/27 03:27:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.30 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* instead we pass it separately to parser. there it gets
* coerced via doNegate() -- Leon aug 20 1999
*
+ * {decimalfail} is used because we would like "1..10" to lex as 1, dot_dot, 10.
+ *
* {realfail1} and {realfail2} are added to prevent the need for scanner
* backup when the {real} rule fails to match completely.
*/
integer {digit}+
decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*))
+decimalfail {digit}+\.\.
real ({integer}|{decimal})[Ee][-+]?{digit}+
realfail1 ({integer}|{decimal})[Ee]
realfail2 ({integer}|{decimal})[Ee][-+]
{decimal} {
ECHO;
}
+{decimalfail} {
+ /* throw back the .., and treat as integer */
+ yyless(yyleng-2);
+ ECHO;
+ }
{real} {
ECHO;
}
pl_gram.c
pl_gram.h
-pl_scan.c
#
# Makefile for the plpgsql shared object
#
-# $PostgreSQL: pgsql/src/pl/plpgsql/src/Makefile,v 1.34 2009/08/28 20:26:19 petere Exp $
+# $PostgreSQL: pgsql/src/pl/plpgsql/src/Makefile,v 1.35 2009/11/12 00:13:00 tgl Exp $
#
#-------------------------------------------------------------------------
SHLIB_LINK = $(filter -lintl, $(LIBS))
rpath =
-OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o
+OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o
all: all-lib
# Force these dependencies to be known even without dependency info built:
-pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o: plpgsql.h pl_gram.h
-
-# pl_scan is compiled as part of pl_gram
-pl_gram.o: pl_scan.c
+pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h
# See notes in src/backend/parser/Makefile about the following two rules
@$(missing) bison $< $@
endif
-# Because we use %option case-insensitive, flex's results could vary
-# depending on what the compile-time locale setting is. Hence, force
-# it to see LC_CTYPE=C to ensure consistent build results.
-
-pl_scan.c: scan.l
-ifdef FLEX
- LC_CTYPE=C $(FLEX) $(FLEXFLAGS) -o'$@' $<
-else
- @$(missing) flex $< $@
-endif
-
-distprep: pl_scan.c pl_gram.h pl_gram.c
+distprep: pl_gram.h pl_gram.c
-# pl_gram.c, pl_gram.h, and pl_scan.c are in the distribution tarball,
+# pl_gram.c and pl_gram.h are in the distribution tarball,
# so they are not cleaned here.
clean distclean: clean-lib
rm -f $(OBJS)
maintainer-clean: clean
- rm -f pl_gram.c pl_gram.h pl_scan.c
+ rm -f pl_gram.c pl_gram.h
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.134 2009/11/10 02:13:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.135 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_type.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
+#include "parser/scanner.h"
#include "parser/scansup.h"
-/*
- * We track token locations in terms of byte offsets from the start of the
- * source string, not the column number/line number representation that
- * bison uses by default. Also, to minimize overhead we track only one
- * location (usually the first token location) for each construct, not
- * the beginning and ending locations as bison does by default. It's
- * therefore sufficient to make YYLTYPE an int.
- */
-#define YYLTYPE int
-
/* Location tracking support --- simpler than bison's default */
#define YYLLOC_DEFAULT(Current, Rhs, N) \
do { \
union YYSTYPE; /* need forward reference for tok_is_keyword */
static bool tok_is_keyword(int token, union YYSTYPE *lval,
- const char *keyword);
+ int kw_token, const char *kw_str);
static void token_is_not_variable(int tok);
static PLpgSQL_expr *read_sql_construct(int until,
int until2,
bool valid_sql,
int *startloc,
int *endtoken);
+static PLpgSQL_expr *read_sql_expression(int until,
+ const char *expected);
static PLpgSQL_expr *read_sql_expression2(int until, int until2,
const char *expected,
int *endtoken);
int leaderlen);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location);
-static char *parse_string_token(const char *token, int location);
static void check_labels(const char *start_label,
const char *end_label,
int end_location);
%locations
%union {
+ core_YYSTYPE core_yystype;
/* these fields must match core_YYSTYPE: */
int ival;
char *str;
PLpgSQL_var *var;
PLpgSQL_expr *expr;
PLpgSQL_stmt *stmt;
- PLpgSQL_stmt_block *program;
PLpgSQL_condition *condition;
PLpgSQL_exception *exception;
PLpgSQL_exception_block *exception_block;
%type <ival> getdiag_item getdiag_target
%type <ival> opt_scrollable
-%type <fetch> opt_fetch_direction
+%type <fetch> opt_fetch_direction
+
+%type <keyword> unreserved_keyword
+
/*
* Basic non-keyword token types. These are hard-wired into the core lexer.
%token TYPECAST DOT_DOT COLON_EQUALS
/*
- * Other tokens recognized by plpgsql's lexer interface layer.
+ * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).
*/
-%token T_STRING
-%token T_NUMBER
%token <word> T_WORD /* unrecognized simple identifier */
%token <cword> T_CWORD /* unrecognized composite identifier */
%token <wdatum> T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */
-
-%token O_OPTION
-%token O_DUMP
+%token LESS_LESS
+%token GREATER_GREATER
/*
- * Keyword tokens
+ * Keyword tokens. Some of these are reserved and some are not;
+ * see pl_scanner.c for info. Be sure unreserved keywords are listed
+ * in the "unreserved_keyword" production below.
*/
-%token K_ALIAS
-%token K_ALL
-%token K_ASSIGN
-%token K_BEGIN
-%token K_BY
-%token K_CASE
-%token K_CLOSE
-%token K_CONSTANT
-%token K_CONTINUE
-%token K_CURSOR
-%token K_DECLARE
-%token K_DEFAULT
-%token K_DIAGNOSTICS
-%token K_DOTDOT
-%token K_ELSE
-%token K_ELSIF
-%token K_END
-%token K_EXCEPTION
-%token K_EXECUTE
-%token K_EXIT
-%token K_FOR
-%token K_FETCH
-%token K_FROM
-%token K_GET
-%token K_IF
-%token K_IN
-%token K_INSERT
-%token K_INTO
-%token K_IS
-%token K_LOOP
-%token K_MOVE
-%token K_NOSCROLL
-%token K_NOT
-%token K_NULL
-%token K_OPEN
-%token K_OR
-%token K_PERFORM
-%token K_RAISE
-%token K_RETURN
-%token K_SCROLL
-%token K_STRICT
-%token K_THEN
-%token K_TO
-%token K_USING
-%token K_WHEN
-%token K_WHILE
+%token <keyword> K_ABSOLUTE
+%token <keyword> K_ALIAS
+%token <keyword> K_ALL
+%token <keyword> K_BACKWARD
+%token <keyword> K_BEGIN
+%token <keyword> K_BY
+%token <keyword> K_CASE
+%token <keyword> K_CLOSE
+%token <keyword> K_CONSTANT
+%token <keyword> K_CONTINUE
+%token <keyword> K_CURSOR
+%token <keyword> K_DEBUG
+%token <keyword> K_DECLARE
+%token <keyword> K_DEFAULT
+%token <keyword> K_DETAIL
+%token <keyword> K_DIAGNOSTICS
+%token <keyword> K_DUMP
+%token <keyword> K_ELSE
+%token <keyword> K_ELSIF
+%token <keyword> K_END
+%token <keyword> K_ERRCODE
+%token <keyword> K_EXCEPTION
+%token <keyword> K_EXECUTE
+%token <keyword> K_EXIT
+%token <keyword> K_FETCH
+%token <keyword> K_FIRST
+%token <keyword> K_FOR
+%token <keyword> K_FORWARD
+%token <keyword> K_FROM
+%token <keyword> K_GET
+%token <keyword> K_HINT
+%token <keyword> K_IF
+%token <keyword> K_IN
+%token <keyword> K_INFO
+%token <keyword> K_INSERT
+%token <keyword> K_INTO
+%token <keyword> K_IS
+%token <keyword> K_LAST
+%token <keyword> K_LOG
+%token <keyword> K_LOOP
+%token <keyword> K_MESSAGE
+%token <keyword> K_MOVE
+%token <keyword> K_NEXT
+%token <keyword> K_NO
+%token <keyword> K_NOT
+%token <keyword> K_NOTICE
+%token <keyword> K_NULL
+%token <keyword> K_OPEN
+%token <keyword> K_OPTION
+%token <keyword> K_OR
+%token <keyword> K_PERFORM
+%token <keyword> K_PRIOR
+%token <keyword> K_QUERY
+%token <keyword> K_RAISE
+%token <keyword> K_RELATIVE
+%token <keyword> K_RESULT_OID
+%token <keyword> K_RETURN
+%token <keyword> K_REVERSE
+%token <keyword> K_ROWTYPE
+%token <keyword> K_ROW_COUNT
+%token <keyword> K_SCROLL
+%token <keyword> K_SQLSTATE
+%token <keyword> K_STRICT
+%token <keyword> K_THEN
+%token <keyword> K_TO
+%token <keyword> K_TYPE
+%token <keyword> K_USING
+%token <keyword> K_WARNING
+%token <keyword> K_WHEN
+%token <keyword> K_WHILE
%%
-pl_function : comp_optsect pl_block opt_semi
+pl_function : comp_options pl_block opt_semi
{
- yylval.program = (PLpgSQL_stmt_block *) $2;
+ plpgsql_parse_result = (PLpgSQL_stmt_block *) $2;
}
;
-comp_optsect :
- | comp_options
- ;
-
-comp_options : comp_options comp_option
- | comp_option
+comp_options :
+ | comp_options comp_option
;
-comp_option : O_OPTION O_DUMP
+comp_option : '#' K_OPTION K_DUMP
{
plpgsql_DumpExecTree = true;
}
{ $$ = $1; }
;
-decl_stmt : '<' '<' any_identifier '>' '>'
- { $$ = $3; }
+decl_stmt : LESS_LESS any_identifier GREATER_GREATER
+ { $$ = $2; }
| K_DECLARE
{ $$ = NULL; }
| decl_statement
{
$$ = 0;
}
- | K_NOSCROLL
+ | K_NO K_SCROLL
{
$$ = CURSOR_OPT_NO_SCROLL;
}
NULL) != NULL)
yyerror("duplicate declaration");
}
+ | unreserved_keyword
+ {
+ $$.name = pstrdup($1);
+ $$.lineno = plpgsql_location_to_lineno(@1);
+ /*
+ * Check to make sure name isn't already declared
+ * in the current block.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ $1, NULL, NULL,
+ NULL) != NULL)
+ yyerror("duplicate declaration");
+ }
;
decl_const :
{ $$ = NULL; }
| decl_defkey
{
- $$ = plpgsql_read_expression(';', ";");
+ $$ = read_sql_expression(';', ";");
}
;
-decl_defkey : K_ASSIGN
+decl_defkey : assign_operator
| K_DEFAULT
;
+assign_operator : '='
+ | COLON_EQUALS
+ ;
+
proc_sect :
{ $$ = NIL; }
| proc_stmts
}
;
-stmt_assign : assign_var K_ASSIGN expr_until_semi
+stmt_assign : assign_var assign_operator expr_until_semi
{
PLpgSQL_stmt_assign *new;
}
;
-getdiag_list_item : getdiag_target K_ASSIGN getdiag_item
+getdiag_list_item : getdiag_target assign_operator getdiag_item
{
PLpgSQL_diag_item *new;
{
int tok = yylex();
- if (tok_is_keyword(tok, &yylval, "row_count"))
+ if (tok_is_keyword(tok, &yylval,
+ K_ROW_COUNT, "row_count"))
$$ = PLPGSQL_GETDIAG_ROW_COUNT;
- else if (tok_is_keyword(tok, &yylval, "result_oid"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_RESULT_OID, "result_oid"))
$$ = PLPGSQL_GETDIAG_RESULT_OID;
else
yyerror("unrecognized GET DIAGNOSTICS item");
if (tok != K_WHEN)
{
plpgsql_push_back_token(tok);
- expr = plpgsql_read_expression(K_WHEN, "WHEN");
+ expr = read_sql_expression(K_WHEN, "WHEN");
}
plpgsql_push_back_token(K_WHEN);
$$ = expr;
* keyword, which means it must be an
* integer loop.
*/
- if (tok_is_keyword(tok, &yylval, "reverse"))
+ if (tok_is_keyword(tok, &yylval,
+ K_REVERSE, "reverse"))
reverse = true;
else
plpgsql_push_back_token(tok);
* statement, so we need to invoke
* read_sql_construct directly.
*/
- expr1 = read_sql_construct(K_DOTDOT,
+ expr1 = read_sql_construct(DOT_DOT,
K_LOOP,
0,
"LOOP",
&expr1loc,
&tok);
- if (tok == K_DOTDOT)
+ if (tok == DOT_DOT)
{
/* Saw "..", so it must be an integer loop */
PLpgSQL_expr *expr2;
/* Get the BY clause if any */
if (tok == K_BY)
- expr_by = plpgsql_read_expression(K_LOOP,
- "LOOP");
+ expr_by = read_sql_expression(K_LOOP,
+ "LOOP");
else
expr_by = NULL;
if (tok == 0)
yyerror("unexpected end of function definition");
- if (tok_is_keyword(tok, &yylval, "next"))
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
{
$$ = make_return_next_stmt(@1);
}
- else if (tok_is_keyword(tok, &yylval, "query"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_QUERY, "query"))
{
$$ = make_return_query_stmt(@1);
}
/*
* First is an optional elog severity level.
*/
- if (tok == K_EXCEPTION)
+ if (tok_is_keyword(tok, &yylval,
+ K_EXCEPTION, "exception"))
{
new->elog_level = ERROR;
tok = yylex();
}
- else if (tok_is_keyword(tok, &yylval, "warning"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_WARNING, "warning"))
{
new->elog_level = WARNING;
tok = yylex();
}
- else if (tok_is_keyword(tok, &yylval, "notice"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_NOTICE, "notice"))
{
new->elog_level = NOTICE;
tok = yylex();
}
- else if (tok_is_keyword(tok, &yylval, "info"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_INFO, "info"))
{
new->elog_level = INFO;
tok = yylex();
}
- else if (tok_is_keyword(tok, &yylval, "log"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_LOG, "log"))
{
new->elog_level = LOG;
tok = yylex();
}
- else if (tok_is_keyword(tok, &yylval, "debug"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_DEBUG, "debug"))
{
new->elog_level = DEBUG1;
tok = yylex();
* literal that is the old-style message format,
* or USING to start the option list immediately.
*/
- if (tok == T_STRING)
+ if (tok == SCONST)
{
/* old style message and parameters */
- new->message = parse_string_token(yytext, yylloc);
+ new->message = yylval.str;
/*
* We expect either a semi-colon, which
* indicates no parameters, or a comma that
else if (tok != K_USING)
{
/* must be condition name or SQLSTATE */
- if (tok_is_keyword(tok, &yylval, "sqlstate"))
+ if (tok_is_keyword(tok, &yylval,
+ K_SQLSTATE, "sqlstate"))
{
/* next token should be a string literal */
char *sqlstatestr;
- if (yylex() != T_STRING)
+ if (yylex() != SCONST)
yyerror("syntax error");
- sqlstatestr = parse_string_token(yytext, yylloc);
+ sqlstatestr = yylval.str;
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
{
/* be nice if we could use opt_scrollable here */
tok = yylex();
- if (tok == K_NOSCROLL)
+ if (tok_is_keyword(tok, &yylval,
+ K_NO, "no"))
{
- new->cursor_options |= CURSOR_OPT_NO_SCROLL;
tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
+ {
+ new->cursor_options |= CURSOR_OPT_NO_SCROLL;
+ tok = yylex();
+ }
}
- else if (tok == K_SCROLL)
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
{
new->cursor_options |= CURSOR_OPT_SCROLL;
tok = yylex();
char *sqlstatestr;
/* next token should be a string literal */
- if (yylex() != T_STRING)
+ if (yylex() != SCONST)
yyerror("syntax error");
- sqlstatestr = parse_string_token(yytext, yylloc);
+ sqlstatestr = yylval.str;
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
;
expr_until_semi :
- { $$ = plpgsql_read_expression(';', ";"); }
+ { $$ = read_sql_expression(';', ";"); }
;
expr_until_rightbracket :
- { $$ = plpgsql_read_expression(']', "]"); }
+ { $$ = read_sql_expression(']', "]"); }
;
expr_until_then :
- { $$ = plpgsql_read_expression(K_THEN, "THEN"); }
+ { $$ = read_sql_expression(K_THEN, "THEN"); }
;
expr_until_loop :
- { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
+ { $$ = read_sql_expression(K_LOOP, "LOOP"); }
;
opt_block_label :
plpgsql_ns_push(NULL);
$$ = NULL;
}
- | '<' '<' any_identifier '>' '>'
+ | LESS_LESS any_identifier GREATER_GREATER
{
- plpgsql_ns_push($3);
- $$ = $3;
+ plpgsql_ns_push($2);
+ $$ = $2;
}
;
}
;
+unreserved_keyword :
+ K_ABSOLUTE
+ | K_ALIAS
+ | K_BACKWARD
+ | K_CONSTANT
+ | K_CURSOR
+ | K_DEBUG
+ | K_DETAIL
+ | K_DUMP
+ | K_ERRCODE
+ | K_FIRST
+ | K_FORWARD
+ | K_HINT
+ | K_INFO
+ | K_IS
+ | K_LAST
+ | K_LOG
+ | K_MESSAGE
+ | K_NEXT
+ | K_NO
+ | K_NOTICE
+ | K_OPTION
+ | K_PRIOR
+ | K_QUERY
+ | K_RELATIVE
+ | K_RESULT_OID
+ | K_REVERSE
+ | K_ROW_COUNT
+ | K_ROWTYPE
+ | K_SCROLL
+ | K_SQLSTATE
+ | K_TYPE
+ | K_WARNING
+ ;
+
%%
/*
* Check whether a token represents an "unreserved keyword".
* We have various places where we want to recognize a keyword in preference
* to a variable name, but not reserve that keyword in other contexts.
- * Hence, this kluge. CAUTION: don't use this for reserved keywords;
- * it won't recognize them.
+ * Hence, this kluge.
*/
static bool
-tok_is_keyword(int token, union YYSTYPE *lval, const char *keyword)
+tok_is_keyword(int token, union YYSTYPE *lval,
+ int kw_token, const char *kw_str)
{
- if (token == T_WORD)
+ if (token == kw_token)
{
- /* must be unquoted and match the downcased string */
- if (!lval->word.quoted && strcmp(lval->word.ident, keyword) == 0)
- return true;
+ /* Normal case, was recognized by scanner (no conflicting variable) */
+ return true;
}
else if (token == T_DATUM)
{
- /* like the T_WORD case, but also reject composite identifiers */
- /* (hence an unreserved word followed by "." will not be recognized) */
+ /*
+ * It's a variable, so recheck the string name. Note we will not
+ * match composite names (hence an unreserved word followed by "."
+ * will not be recognized).
+ */
if (!lval->word.quoted && lval->word.ident != NULL &&
- strcmp(lval->word.ident, keyword) == 0)
+ strcmp(lval->word.ident, kw_str) == 0)
return true;
}
return false; /* not the keyword */
}
/* Convenience routine to read an expression with one possible terminator */
-PLpgSQL_expr *
-plpgsql_read_expression(int until, const char *expected)
+static PLpgSQL_expr *
+read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
"SELECT ", true, true, NULL, NULL);
if (tok == '%')
{
tok = yylex();
- if (tok_is_keyword(tok, &yylval, "type"))
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname);
if (result)
return result;
}
- else if (tok_is_keyword(tok, &yylval, "rowtype"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_wordrowtype(dtname);
if (result)
if (tok == '%')
{
tok = yylex();
- if (tok_is_keyword(tok, &yylval, "type"))
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
{
result = plpgsql_parse_cwordtype(dtnames);
if (result)
return result;
}
- else if (tok_is_keyword(tok, &yylval, "rowtype"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_cwordrowtype(dtnames);
if (result)
yyerror("incomplete data type declaration");
}
/* Possible followers for datatype in a declaration */
- if (tok == K_NOT || tok == K_ASSIGN || tok == K_DEFAULT)
+ if (tok == K_NOT || tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT)
break;
/* Possible followers for datatype in a cursor_arg list */
if ((tok == ',' || tok == ')') && parenlevel == 0)
if (tok == 0)
yyerror("unexpected end of function definition");
- if (tok_is_keyword(tok, &yylval, "next"))
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
{
/* use defaults */
}
- else if (tok_is_keyword(tok, &yylval, "prior"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_PRIOR, "prior"))
{
fetch->direction = FETCH_BACKWARD;
}
- else if (tok_is_keyword(tok, &yylval, "first"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_FIRST, "first"))
{
fetch->direction = FETCH_ABSOLUTE;
}
- else if (tok_is_keyword(tok, &yylval, "last"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_LAST, "last"))
{
fetch->direction = FETCH_ABSOLUTE;
fetch->how_many = -1;
}
- else if (tok_is_keyword(tok, &yylval, "absolute"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_ABSOLUTE, "absolute"))
{
fetch->direction = FETCH_ABSOLUTE;
fetch->expr = read_sql_expression2(K_FROM, K_IN,
NULL);
check_FROM = false;
}
- else if (tok_is_keyword(tok, &yylval, "relative"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_RELATIVE, "relative"))
{
fetch->direction = FETCH_RELATIVE;
fetch->expr = read_sql_expression2(K_FROM, K_IN,
NULL);
check_FROM = false;
}
- else if (tok == K_ALL)
+ else if (tok_is_keyword(tok, &yylval,
+ K_ALL, "all"))
{
fetch->how_many = FETCH_ALL;
fetch->returns_multiple_rows = true;
}
- else if (tok_is_keyword(tok, &yylval, "forward"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_FORWARD, "forward"))
{
complete_direction(fetch, &check_FROM);
}
- else if (tok_is_keyword(tok, &yylval, "backward"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_BACKWARD, "backward"))
{
fetch->direction = FETCH_BACKWARD;
complete_direction(fetch, &check_FROM);
* Note that a well-formed expression is _required_ here;
* anything else is a compile-time error.
*/
- new->expr = plpgsql_read_expression(';', ";");
+ new->expr = read_sql_expression(';', ";");
}
return (PLpgSQL_stmt *) new;
yyerror("syntax error");
}
else
- new->expr = plpgsql_read_expression(';', ";");
+ new->expr = read_sql_expression(';', ";");
return (PLpgSQL_stmt *) new;
}
return plpgsql_build_datatype(type_id, typmod);
}
-/*
- * Convert a string-literal token to the represented string value.
- *
- * To do this, we need to invoke the core lexer. Here we are only concerned
- * with setting up an errcontext link, which is handled the same as
- * in check_sql_expr().
- */
-static char *
-parse_string_token(const char *token, int location)
-{
- char *result;
- sql_error_callback_arg cbarg;
- ErrorContextCallback syntax_errcontext;
-
- cbarg.location = location;
- cbarg.leaderlen = 0;
-
- syntax_errcontext.callback = plpgsql_sql_error_callback;
- syntax_errcontext.arg = &cbarg;
- syntax_errcontext.previous = error_context_stack;
- error_context_stack = &syntax_errcontext;
-
- result = pg_parse_string_token(token);
-
- /* Restore former ereport callback */
- error_context_stack = syntax_errcontext.previous;
-
- return result;
-}
-
/*
* Check block starting and ending labels match.
*/
/*
* Read expressions until the matching ')'.
*/
- expr = plpgsql_read_expression(')', ")");
+ expr = read_sql_expression(')', ")");
/* Next we'd better find the until token */
tok = yylex();
opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
- if (tok_is_keyword(tok, &yylval, "errcode"))
+ if (tok_is_keyword(tok, &yylval,
+ K_ERRCODE, "errcode"))
opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
- else if (tok_is_keyword(tok, &yylval, "message"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_MESSAGE, "message"))
opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
- else if (tok_is_keyword(tok, &yylval, "detail"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_DETAIL, "detail"))
opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
- else if (tok_is_keyword(tok, &yylval, "hint"))
+ else if (tok_is_keyword(tok, &yylval,
+ K_HINT, "hint"))
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
else
yyerror("unrecognized RAISE statement option");
- if (yylex() != K_ASSIGN)
+ tok = yylex();
+ if (tok != '=' && tok != COLON_EQUALS)
yyerror("syntax error, expected \"=\"");
opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
return (PLpgSQL_stmt *) new;
}
-
-
-/* Needed to avoid conflict between different prefix settings: */
-#undef yylex
-
-#include "pl_scan.c"
-# $PostgreSQL: pgsql/src/pl/plpgsql/src/nls.mk,v 1.11 2009/10/20 18:23:27 petere Exp $
+# $PostgreSQL: pgsql/src/pl/plpgsql/src/nls.mk,v 1.12 2009/11/12 00:13:00 tgl Exp $
CATALOG_NAME := plpgsql
AVAIL_LANGUAGES := de es fr it ja ro
-GETTEXT_FILES := pl_comp.c pl_exec.c pl_gram.c pl_funcs.c pl_handler.c pl_scan.c
+GETTEXT_FILES := pl_comp.c pl_exec.c pl_gram.c pl_funcs.c pl_handler.c pl_scanner.c
GETTEXT_TRIGGERS:= _ errmsg errmsg_plural:1,2 errdetail errdetail_log errdetail_plural:1,2 errhint errcontext yyerror plpgsql_yyerror
.PHONY: gettext-files
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.144 2009/11/10 02:13:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.145 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
-#include "pl_gram.h"
-
#include "catalog/namespace.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_attribute.h"
* Our own local and global variables
* ----------
*/
+PLpgSQL_stmt_block *plpgsql_parse_result;
+
static int datums_alloc;
int plpgsql_nDatums;
PLpgSQL_datum **plpgsql_Datums;
parse_rc = plpgsql_yyparse();
if (parse_rc != 0)
elog(ERROR, "plpgsql parser returned %d", parse_rc);
- function->action = plpgsql_yylval.program;
+ function->action = plpgsql_parse_result;
plpgsql_scanner_finish();
pfree(proc_source);
parse_rc = plpgsql_yyparse();
if (parse_rc != 0)
elog(ERROR, "plpgsql parser returned %d", parse_rc);
- function->action = plpgsql_yylval.program;
+ function->action = plpgsql_parse_result;
plpgsql_scanner_finish();
/* ----------
* plpgsql_parse_word The scanner calls this to postparse
- * any single word not found by a
- * keyword rule.
+ * any single word that is not a reserved keyword.
+ *
+ * word1 is the downcased/dequoted identifier; it must be palloc'd in the
+ * function's long-term memory context.
+ *
+ * yytxt is the original token text; we need this to check for quoting,
+ * so that later checks for unreserved keywords work properly.
+ *
+ * If recognized as a variable, fill in *wdatum and return TRUE;
+ * if not recognized, fill in *word and return FALSE.
+ * (Note: those two pointers actually point to members of the same union,
+ * but for notational reasons we pass them separately.)
* ----------
*/
-int
-plpgsql_parse_word(const char *word)
+bool
+plpgsql_parse_word(char *word1, const char *yytxt,
+ PLwdatum *wdatum, PLword *word)
{
- PLpgSQL_nsitem *nse;
- char *cp[1];
-
- /* Do case conversion and word separation */
- plpgsql_convert_ident(word, cp, 1);
+ PLpgSQL_nsitem *ns;
/* No lookup if disabled */
if (plpgsql_LookupIdentifiers)
/*
* Do a lookup in the current namespace stack
*/
- nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
- cp[0], NULL, NULL,
- NULL);
+ ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ word1, NULL, NULL,
+ NULL);
- if (nse != NULL)
+ if (ns != NULL)
{
- switch (nse->itemtype)
+ switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
case PLPGSQL_NSTYPE_ROW:
case PLPGSQL_NSTYPE_REC:
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[nse->itemno];
- plpgsql_yylval.wdatum.ident = cp[0];
- plpgsql_yylval.wdatum.quoted = (word[0] == '"');
- plpgsql_yylval.wdatum.idents = NIL;
- return T_DATUM;
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ wdatum->ident = word1;
+ wdatum->quoted = (yytxt[0] == '"');
+ wdatum->idents = NIL;
+ return true;
default:
elog(ERROR, "unrecognized plpgsql itemtype: %d",
- nse->itemtype);
+ ns->itemtype);
}
}
}
* Nothing found - up to now it's a word without any special meaning for
* us.
*/
- plpgsql_yylval.word.ident = cp[0];
- plpgsql_yylval.word.quoted = (word[0] == '"');
- return T_WORD;
+ word->ident = word1;
+ word->quoted = (yytxt[0] == '"');
+ return false;
}
* separated by a dot.
* ----------
*/
-int
-plpgsql_parse_dblword(const char *word)
+bool
+plpgsql_parse_dblword(char *word1, char *word2,
+ PLwdatum *wdatum, PLcword *cword)
{
PLpgSQL_nsitem *ns;
- char *cp[2];
List *idents;
int nnames;
- /* Do case conversion and word separation */
- plpgsql_convert_ident(word, cp, 2);
-
- idents = list_make2(makeString(cp[0]),
- makeString(cp[1]));
+ idents = list_make2(makeString(word1),
+ makeString(word2));
/* No lookup if disabled */
if (plpgsql_LookupIdentifiers)
* Do a lookup in the current namespace stack
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
- cp[0], cp[1], NULL,
+ word1, word2, NULL,
&nnames);
if (ns != NULL)
{
{
case PLPGSQL_NSTYPE_VAR:
/* Block-qualified reference to scalar variable. */
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
case PLPGSQL_NSTYPE_REC:
if (nnames == 1)
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
- new->fieldname = pstrdup(cp[1]);
+ new->fieldname = pstrdup(word2);
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
- plpgsql_yylval.wdatum.datum = (PLpgSQL_datum *) new;
+ wdatum->datum = (PLpgSQL_datum *) new;
}
else
{
/* Block-qualified reference to record variable. */
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
+ wdatum->datum = plpgsql_Datums[ns->itemno];
}
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
case PLPGSQL_NSTYPE_ROW:
if (nnames == 1)
for (i = 0; i < row->nfields; i++)
{
if (row->fieldnames[i] &&
- strcmp(row->fieldnames[i], cp[1]) == 0)
+ strcmp(row->fieldnames[i], word2) == 0)
{
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[row->varnos[i]];
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->datum = plpgsql_Datums[row->varnos[i]];
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
}
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("row \"%s\" has no field \"%s\"",
- cp[0], cp[1])));
+ word1, word2)));
}
else
{
/* Block-qualified reference to row variable. */
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
}
default:
}
/* Nothing found */
- plpgsql_yylval.cword.idents = idents;
- return T_CWORD;
+ cword->idents = idents;
+ return false;
}
* separated by dots.
* ----------
*/
-int
-plpgsql_parse_tripword(const char *word)
+bool
+plpgsql_parse_tripword(char *word1, char *word2, char *word3,
+ PLwdatum *wdatum, PLcword *cword)
{
PLpgSQL_nsitem *ns;
- char *cp[3];
List *idents;
int nnames;
- /* Do case conversion and word separation */
- plpgsql_convert_ident(word, cp, 3);
-
- idents = list_make3(makeString(cp[0]),
- makeString(cp[1]),
- makeString(cp[2]));
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
/* No lookup if disabled */
if (plpgsql_LookupIdentifiers)
* reference, else ignore.
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
- cp[0], cp[1], cp[2],
+ word1, word2, word3,
&nnames);
if (ns != NULL && nnames == 2)
{
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
- new->fieldname = pstrdup(cp[2]);
+ new->fieldname = pstrdup(word3);
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
- plpgsql_yylval.wdatum.datum = (PLpgSQL_datum *) new;
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->datum = (PLpgSQL_datum *) new;
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
}
case PLPGSQL_NSTYPE_ROW:
for (i = 0; i < row->nfields; i++)
{
if (row->fieldnames[i] &&
- strcmp(row->fieldnames[i], cp[2]) == 0)
+ strcmp(row->fieldnames[i], word3) == 0)
{
- plpgsql_yylval.wdatum.datum = plpgsql_Datums[row->varnos[i]];
- plpgsql_yylval.wdatum.ident = NULL;
- plpgsql_yylval.wdatum.quoted = false; /* not used */
- plpgsql_yylval.wdatum.idents = idents;
- return T_DATUM;
+ wdatum->datum = plpgsql_Datums[row->varnos[i]];
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
}
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("row \"%s.%s\" has no field \"%s\"",
- cp[0], cp[1], cp[2])));
+ word1, word2, word3)));
}
default:
}
/* Nothing found */
- plpgsql_yylval.cword.idents = idents;
- return T_CWORD;
+ cword->idents = idents;
+ return false;
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.85 2009/11/07 00:52:26 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.86 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "plpgsql.h"
-#include <ctype.h>
-
-#include "parser/scansup.h"
-
/* ----------
* Local variables for namespace handling
}
-/* ----------
- * plpgsql_convert_ident
- *
- * Convert a possibly-qualified identifier to internal form: handle
- * double quotes, translate to lower case where not inside quotes,
- * truncate to NAMEDATALEN.
- *
- * There may be several identifiers separated by dots and optional
- * whitespace. Each one is converted to a separate palloc'd string.
- * The caller passes the expected number of identifiers, as well as
- * a char* array to hold them. It is an error if we find the wrong
- * number of identifiers (cf grammar processing of fori_varname).
- *
- * NOTE: the input string has already been accepted by the flex lexer,
- * so we don't need a heckuva lot of error checking here.
- * ----------
- */
-void
-plpgsql_convert_ident(const char *s, char **output, int numidents)
-{
- const char *sstart = s;
- int identctr = 0;
-
- /* Outer loop over identifiers */
- while (*s)
- {
- char *curident;
- char *cp;
-
- /* Process current identifier */
-
- if (*s == '"')
- {
- /* Quoted identifier: copy, collapsing out doubled quotes */
-
- curident = palloc(strlen(s) + 1); /* surely enough room */
- cp = curident;
- s++;
- while (*s)
- {
- if (*s == '"')
- {
- if (s[1] != '"')
- break;
- s++;
- }
- *cp++ = *s++;
- }
- if (*s != '"') /* should not happen if lexer checked */
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("unterminated \" in identifier: %s", sstart)));
- s++;
- *cp = '\0';
- /* Truncate to NAMEDATALEN */
- truncate_identifier(curident, cp - curident, false);
- }
- else
- {
- /* Normal identifier: extends till dot or whitespace */
- const char *thisstart = s;
-
- while (*s && *s != '.' && !scanner_isspace(*s))
- s++;
- /* Downcase and truncate to NAMEDATALEN */
- curident = downcase_truncate_identifier(thisstart, s - thisstart,
- false);
- }
-
- /* Pass ident to caller */
- if (identctr < numidents)
- output[identctr++] = curident;
- else
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("qualified identifier cannot be used here: %s",
- sstart)));
-
- /* If not done, skip whitespace, dot, whitespace */
- if (*s)
- {
- while (*s && scanner_isspace(*s))
- s++;
- if (*s++ != '.')
- elog(ERROR, "expected dot between identifiers: %s", sstart);
- while (*s && scanner_isspace(*s))
- s++;
- if (*s == '\0')
- elog(ERROR, "expected another identifier: %s", sstart);
- }
- }
-
- if (identctr != numidents)
- elog(ERROR, "improperly qualified identifier: %s",
- sstart);
-}
-
-
/*
* Statement type as a string, for use in error messages etc.
*/
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * pl_scanner.c
+ * lexical scanning for PL/pgSQL
+ *
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_scanner.c,v 1.1 2009/11/12 00:13:00 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "plpgsql.h"
+
+#include "mb/pg_wchar.h"
+#include "parser/scanner.h"
+
+#include "pl_gram.h" /* must be after parser/scanner.h */
+
+#define PG_KEYWORD(a,b,c) {a,b,c},
+
+
+/* Klugy flag to tell scanner whether to lookup identifiers */
+bool plpgsql_LookupIdentifiers = true;
+
+/*
+ * A word about keywords:
+ *
+ * We keep reserved and unreserved keywords in separate arrays. The
+ * reserved keywords are passed to the core scanner, so they will be
+ * recognized before (and instead of) any variable name. Unreserved
+ * words are checked for separately, after determining that the identifier
+ * isn't a known variable name. If plpgsql_LookupIdentifiers is off then
+ * no variable names will be recognized, so the unreserved words always work.
+ * (Note in particular that this helps us avoid reserving keywords that are
+ * only needed in DECLARE sections, since we scan those sections with
+ * plpgsql_LookupIdentifiers off.)
+ *
+ * In certain contexts it is desirable to prefer recognizing an unreserved
+ * keyword over recognizing a variable name. Those cases are handled in
+ * gram.y using tok_is_keyword().
+ *
+ * For the most part, the reserved keywords are those that start a PL/pgSQL
+ * statement (and so would conflict with an assignment to a variable of the
+ * same name). We also don't sweat it much about reserving keywords that
+ * are reserved in the core grammar. Try to avoid reserving other words.
+ */
+
+/*
+ * Lists of keyword (name, token-value, category) entries.
+ *
+ * !!WARNING!!: These lists must be sorted by ASCII name, because binary
+ * search is used to locate entries.
+ *
+ * Be careful not to put the same word in both lists. Also be sure that
+ * gram.y's unreserved_keyword production agrees with the second list.
+ */
+
+static const ScanKeyword reserved_keywords[] = {
+ PG_KEYWORD("all", K_ALL, RESERVED_KEYWORD)
+ PG_KEYWORD("begin", K_BEGIN, RESERVED_KEYWORD)
+ PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
+ PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD)
+ PG_KEYWORD("close", K_CLOSE, RESERVED_KEYWORD)
+ PG_KEYWORD("continue", K_CONTINUE, RESERVED_KEYWORD)
+ PG_KEYWORD("declare", K_DECLARE, RESERVED_KEYWORD)
+ PG_KEYWORD("default", K_DEFAULT, RESERVED_KEYWORD)
+ PG_KEYWORD("diagnostics", K_DIAGNOSTICS, RESERVED_KEYWORD)
+ PG_KEYWORD("else", K_ELSE, RESERVED_KEYWORD)
+ PG_KEYWORD("elseif", K_ELSIF, RESERVED_KEYWORD)
+ PG_KEYWORD("elsif", K_ELSIF, RESERVED_KEYWORD)
+ PG_KEYWORD("end", K_END, RESERVED_KEYWORD)
+ PG_KEYWORD("exception", K_EXCEPTION, RESERVED_KEYWORD)
+ PG_KEYWORD("execute", K_EXECUTE, RESERVED_KEYWORD)
+ PG_KEYWORD("exit", K_EXIT, RESERVED_KEYWORD)
+ PG_KEYWORD("fetch", K_FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("for", K_FOR, RESERVED_KEYWORD)
+ PG_KEYWORD("from", K_FROM, RESERVED_KEYWORD)
+ PG_KEYWORD("get", K_GET, RESERVED_KEYWORD)
+ PG_KEYWORD("if", K_IF, RESERVED_KEYWORD)
+ PG_KEYWORD("in", K_IN, RESERVED_KEYWORD)
+ PG_KEYWORD("insert", K_INSERT, RESERVED_KEYWORD)
+ PG_KEYWORD("into", K_INTO, RESERVED_KEYWORD)
+ PG_KEYWORD("loop", K_LOOP, RESERVED_KEYWORD)
+ PG_KEYWORD("move", K_MOVE, RESERVED_KEYWORD)
+ PG_KEYWORD("not", K_NOT, RESERVED_KEYWORD)
+ PG_KEYWORD("null", K_NULL, RESERVED_KEYWORD)
+ PG_KEYWORD("open", K_OPEN, RESERVED_KEYWORD)
+ PG_KEYWORD("or", K_OR, RESERVED_KEYWORD)
+ PG_KEYWORD("perform", K_PERFORM, RESERVED_KEYWORD)
+ PG_KEYWORD("raise", K_RAISE, RESERVED_KEYWORD)
+ PG_KEYWORD("return", K_RETURN, RESERVED_KEYWORD)
+ PG_KEYWORD("strict", K_STRICT, RESERVED_KEYWORD)
+ PG_KEYWORD("then", K_THEN, RESERVED_KEYWORD)
+ PG_KEYWORD("to", K_TO, RESERVED_KEYWORD)
+ PG_KEYWORD("using", K_USING, RESERVED_KEYWORD)
+ PG_KEYWORD("when", K_WHEN, RESERVED_KEYWORD)
+ PG_KEYWORD("while", K_WHILE, RESERVED_KEYWORD)
+};
+
+static const int num_reserved_keywords = lengthof(reserved_keywords);
+
+static const ScanKeyword unreserved_keywords[] = {
+ PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
+ PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
+ PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
+ PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
+ PG_KEYWORD("errcode", K_ERRCODE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("first", K_FIRST, UNRESERVED_KEYWORD)
+ PG_KEYWORD("forward", K_FORWARD, UNRESERVED_KEYWORD)
+ PG_KEYWORD("hint", K_HINT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("info", K_INFO, UNRESERVED_KEYWORD)
+ PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
+ PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
+ PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
+ PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
+ PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
+ PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
+ PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD)
+ PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
+ PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("warning", K_WARNING, UNRESERVED_KEYWORD)
+};
+
+static const int num_unreserved_keywords = lengthof(unreserved_keywords);
+
+
+/* Auxiliary data about a token (other than the token type) */
+typedef struct
+{
+ YYSTYPE lval; /* semantic information */
+ YYLTYPE lloc; /* offset in scanbuf */
+ int leng; /* length in bytes */
+} TokenAuxData;
+
+/*
+ * Scanner working state. At some point we might wish to fold all this
+ * into a YY_EXTRA struct. For the moment, there is no need for plpgsql's
+ * lexer to be re-entrant, and the notational burden of passing a yyscanner
+ * pointer around is great enough to not want to do it without need.
+ */
+
+/* The stuff the core lexer needs */
+static core_yyscan_t yyscanner = NULL;
+static core_yy_extra_type core_yy;
+
+/* The original input string */
+static const char *scanorig;
+
+/* Current token's length (corresponds to plpgsql_yylval and plpgsql_yylloc) */
+static int plpgsql_yyleng;
+
+/* Token pushback stack */
+#define MAX_PUSHBACKS 4
+
+static int num_pushbacks;
+static int pushback_token[MAX_PUSHBACKS];
+static TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
+
+/* State for plpgsql_location_to_lineno() */
+static const char *cur_line_start;
+static const char *cur_line_end;
+static int cur_line_num;
+
+/* Internal functions */
+static int internal_yylex(TokenAuxData *auxdata);
+static void push_back_token(int token, TokenAuxData *auxdata);
+static void location_lineno_init(void);
+
+
+/*
+ * This is the yylex routine called from the PL/pgSQL grammar.
+ * It is a wrapper around the core lexer, with the ability to recognize
+ * PL/pgSQL variables and return them as special T_DATUM tokens. If a
+ * word or compound word does not match any variable name, or if matching
+ * is turned off by plpgsql_LookupIdentifiers, it is returned as
+ * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
+ * matches one of those.
+ */
+int
+plpgsql_yylex(void)
+{
+ int tok1;
+ TokenAuxData aux1;
+ const ScanKeyword *kw;
+
+ tok1 = internal_yylex(&aux1);
+ if (tok1 == IDENT || tok1 == PARAM)
+ {
+ int tok2;
+ TokenAuxData aux2;
+
+ tok2 = internal_yylex(&aux2);
+ if (tok2 == '.')
+ {
+ int tok3;
+ TokenAuxData aux3;
+
+ tok3 = internal_yylex(&aux3);
+ if (tok3 == IDENT)
+ {
+ int tok4;
+ TokenAuxData aux4;
+
+ tok4 = internal_yylex(&aux4);
+ if (tok4 == '.')
+ {
+ int tok5;
+ TokenAuxData aux5;
+
+ tok5 = internal_yylex(&aux5);
+ if (tok5 == IDENT)
+ {
+ if (plpgsql_parse_tripword(aux1.lval.str,
+ aux3.lval.str,
+ aux5.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ else
+ {
+ /* not A.B.C, so just process A.B */
+ push_back_token(tok5, &aux5);
+ push_back_token(tok4, &aux4);
+ if (plpgsql_parse_dblword(aux1.lval.str,
+ aux3.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ }
+ else
+ {
+ /* not A.B.C, so just process A.B */
+ push_back_token(tok4, &aux4);
+ if (plpgsql_parse_dblword(aux1.lval.str,
+ aux3.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ }
+ else
+ {
+ /* not A.B, so just process A */
+ push_back_token(tok3, &aux3);
+ push_back_token(tok2, &aux2);
+ if (plpgsql_parse_word(aux1.lval.str,
+ core_yy.scanbuf + aux1.lloc,
+ &aux1.lval.wdatum,
+ &aux1.lval.word))
+ tok1 = T_DATUM;
+ else if (!aux1.lval.word.quoted &&
+ (kw = ScanKeywordLookup(aux1.lval.word.ident,
+ unreserved_keywords,
+ num_unreserved_keywords)))
+ {
+ aux1.lval.keyword = kw->name;
+ tok1 = kw->value;
+ }
+ else
+ tok1 = T_WORD;
+ }
+ }
+ else
+ {
+ /* not A.B, so just process A */
+ push_back_token(tok2, &aux2);
+ if (plpgsql_parse_word(aux1.lval.str,
+ core_yy.scanbuf + aux1.lloc,
+ &aux1.lval.wdatum,
+ &aux1.lval.word))
+ tok1 = T_DATUM;
+ else if (!aux1.lval.word.quoted &&
+ (kw = ScanKeywordLookup(aux1.lval.word.ident,
+ unreserved_keywords,
+ num_unreserved_keywords)))
+ {
+ aux1.lval.keyword = kw->name;
+ tok1 = kw->value;
+ }
+ else
+ tok1 = T_WORD;
+ }
+ }
+ else
+ {
+ /* Not a potential plpgsql variable name, just return the data */
+ }
+
+ plpgsql_yylval = aux1.lval;
+ plpgsql_yylloc = aux1.lloc;
+ plpgsql_yyleng = aux1.leng;
+ return tok1;
+}
+
+/*
+ * Internal yylex function. This wraps the core lexer and adds one feature:
+ * a token pushback stack. We also make a couple of trivial single-token
+ * translations from what the core lexer does to what we want, in particular
+ * interfacing from the core_YYSTYPE to YYSTYPE union.
+ */
+static int
+internal_yylex(TokenAuxData *auxdata)
+{
+ int token;
+ const char *yytext;
+
+ if (num_pushbacks > 0)
+ {
+ num_pushbacks--;
+ token = pushback_token[num_pushbacks];
+ *auxdata = pushback_auxdata[num_pushbacks];
+ }
+ else
+ {
+ token = core_yylex(&auxdata->lval.core_yystype,
+ &auxdata->lloc,
+ yyscanner);
+
+ /* remember the length of yytext before it gets changed */
+ yytext = core_yy.scanbuf + auxdata->lloc;
+ auxdata->leng = strlen(yytext);
+
+ /* Check for << >> and #, which the core considers operators */
+ if (token == Op)
+ {
+ if (strcmp(auxdata->lval.str, "<<") == 0)
+ token = LESS_LESS;
+ else if (strcmp(auxdata->lval.str, ">>") == 0)
+ token = GREATER_GREATER;
+ else if (strcmp(auxdata->lval.str, "#") == 0)
+ token = '#';
+ }
+
+ /* The core returns PARAM as ival, but we treat it like IDENT */
+ else if (token == PARAM)
+ {
+ auxdata->lval.str = pstrdup(yytext);
+ }
+ }
+
+ return token;
+}
+
+/*
+ * Push back a token to be re-read by next internal_yylex() call.
+ */
+static void
+push_back_token(int token, TokenAuxData *auxdata)
+{
+ if (num_pushbacks >= MAX_PUSHBACKS)
+ elog(ERROR, "too many tokens pushed back");
+ pushback_token[num_pushbacks] = token;
+ pushback_auxdata[num_pushbacks] = *auxdata;
+ num_pushbacks++;
+}
+
+/*
+ * Push back a single token to be re-read by next plpgsql_yylex() call.
+ *
+ * NOTE: this does not cause yylval or yylloc to "back up". Also, it
+ * is not a good idea to push back a token code other than what you read.
+ */
+void
+plpgsql_push_back_token(int token)
+{
+ TokenAuxData auxdata;
+
+ auxdata.lval = plpgsql_yylval;
+ auxdata.lloc = plpgsql_yylloc;
+ auxdata.leng = plpgsql_yyleng;
+ push_back_token(token, &auxdata);
+}
+
+/*
+ * Append the function text starting at startlocation and extending to
+ * (not including) endlocation onto the existing contents of "buf".
+ */
+void
+plpgsql_append_source_text(StringInfo buf,
+ int startlocation, int endlocation)
+{
+ Assert(startlocation <= endlocation);
+ appendBinaryStringInfo(buf, scanorig + startlocation,
+ endlocation - startlocation);
+}
+
+/*
+ * plpgsql_scanner_errposition
+ * Report an error cursor position, if possible.
+ *
+ * This is expected to be used within an ereport() call. The return value
+ * is a dummy (always 0, in fact).
+ *
+ * Note that this can only be used for messages emitted during initial
+ * parsing of a plpgsql function, since it requires the scanorig string
+ * to still be available.
+ */
+int
+plpgsql_scanner_errposition(int location)
+{
+ int pos;
+
+ if (location < 0 || scanorig == NULL)
+ return 0; /* no-op if location is unknown */
+
+ /* Convert byte offset to character number */
+ pos = pg_mbstrlen_with_len(scanorig, location) + 1;
+ /* And pass it to the ereport mechanism */
+ (void) internalerrposition(pos);
+ /* Also pass the function body string */
+ return internalerrquery(scanorig);
+}
+
+/*
+ * plpgsql_yyerror
+ * Report a lexer or grammar error.
+ *
+ * The message's cursor position refers to the current token (the one
+ * last returned by plpgsql_yylex()).
+ * This is OK for syntax error messages from the Bison parser, because Bison
+ * parsers report error as soon as the first unparsable token is reached.
+ * Beware of using yyerror for other purposes, as the cursor position might
+ * be misleading!
+ */
+void
+plpgsql_yyerror(const char *message)
+{
+ char *yytext = core_yy.scanbuf + plpgsql_yylloc;
+
+ if (*yytext == '\0')
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: %s is typically the translation of "syntax error" */
+ errmsg("%s at end of input", _(message)),
+ plpgsql_scanner_errposition(plpgsql_yylloc)));
+ }
+ else
+ {
+ /*
+ * If we have done any lookahead then flex will have restored the
+ * character after the end-of-token. Zap it again so that we
+ * report only the single token here. This modifies scanbuf but
+ * we no longer care about that.
+ */
+ yytext[plpgsql_yyleng] = '\0';
+
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: first %s is typically the translation of "syntax error" */
+ errmsg("%s at or near \"%s\"", _(message), yytext),
+ plpgsql_scanner_errposition(plpgsql_yylloc)));
+ }
+}
+
+/*
+ * Given a location (a byte offset in the function source text),
+ * return a line number.
+ *
+ * We expect that this is typically called for a sequence of increasing
+ * location values, so optimize accordingly by tracking the endpoints
+ * of the "current" line.
+ */
+int
+plpgsql_location_to_lineno(int location)
+{
+ const char *loc;
+
+ if (location < 0 || scanorig == NULL)
+ return 0; /* garbage in, garbage out */
+ loc = scanorig + location;
+
+ /* be correct, but not fast, if input location goes backwards */
+ if (loc < cur_line_start)
+ location_lineno_init();
+
+ while (cur_line_end != NULL && loc > cur_line_end)
+ {
+ cur_line_start = cur_line_end + 1;
+ cur_line_num++;
+ cur_line_end = strchr(cur_line_start, '\n');
+ }
+
+ return cur_line_num;
+}
+
+/* initialize or reset the state for plpgsql_location_to_lineno */
+static void
+location_lineno_init(void)
+{
+ cur_line_start = scanorig;
+ 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++;
+
+ cur_line_end = strchr(cur_line_start, '\n');
+}
+
+/* return the most recently computed lineno */
+int
+plpgsql_latest_lineno(void)
+{
+ return cur_line_num;
+}
+
+
+/*
+ * Called before any actual parsing is done
+ *
+ * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
+ * Although it is not fed directly to flex, we need the original string
+ * to cite in error messages.
+ */
+void
+plpgsql_scanner_init(const char *str)
+{
+ /* Start up the core scanner */
+ yyscanner = scanner_init(str, &core_yy,
+ reserved_keywords, num_reserved_keywords);
+
+ /*
+ * scanorig points to the original string, which unlike the scanner's
+ * scanbuf won't be modified on-the-fly by flex. Notice that although
+ * yytext points into scanbuf, we rely on being able to apply locations
+ * (offsets from string start) to scanorig as well.
+ */
+ scanorig = str;
+
+ /* Other setup */
+ plpgsql_LookupIdentifiers = true;
+
+ num_pushbacks = 0;
+
+ location_lineno_init();
+}
+
+/*
+ * Called after parsing is done to clean up after plpgsql_scanner_init()
+ */
+void
+plpgsql_scanner_finish(void)
+{
+ /* release storage */
+ scanner_finish(yyscanner);
+ /* avoid leaving any dangling pointers */
+ yyscanner = NULL;
+ scanorig = NULL;
+}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.123 2009/11/10 02:13:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.124 2009/11/12 00:13:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Global variable declarations
**********************************************************************/
+extern bool plpgsql_check_syntax;
extern bool plpgsql_DumpExecTree;
extern bool plpgsql_LookupIdentifiers;
+
+extern PLpgSQL_stmt_block *plpgsql_parse_result;
+
extern int plpgsql_nDatums;
extern PLpgSQL_datum **plpgsql_Datums;
extern char *plpgsql_error_funcname;
-/* linkage to the real yytext variable */
-extern char *plpgsql_base_yytext;
-
-#define yytext plpgsql_base_yytext
-
extern PLpgSQL_function *plpgsql_curr_compile;
-extern bool plpgsql_check_syntax;
extern MemoryContext compile_tmp_cxt;
extern PLpgSQL_plugin **plugin_ptr;
extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source);
extern void plpgsql_parser_setup(struct ParseState *pstate,
PLpgSQL_expr *expr);
-extern int plpgsql_parse_word(const char *word);
-extern int plpgsql_parse_dblword(const char *word);
-extern int plpgsql_parse_tripword(const char *word);
+extern bool plpgsql_parse_word(char *word1, const char *yytxt,
+ PLwdatum *wdatum, PLword *word);
+extern bool plpgsql_parse_dblword(char *word1, char *word2,
+ PLwdatum *wdatum, PLcword *cword);
+extern bool plpgsql_parse_tripword(char *word1, char *word2, char *word3,
+ PLwdatum *wdatum, PLcword *cword);
extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident);
extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents);
extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident);
* Other functions in pl_funcs.c
* ----------
*/
-extern void plpgsql_convert_ident(const char *s, char **output, int numidents);
extern const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt);
extern void plpgsql_dumptree(PLpgSQL_function *func);
/* ----------
- * Externs in gram.y and scan.l
+ * Scanner functions in pl_scanner.c
* ----------
*/
-extern PLpgSQL_expr *plpgsql_read_expression(int until, const char *expected);
-extern int plpgsql_yyparse(void);
extern int plpgsql_base_yylex(void);
extern int plpgsql_yylex(void);
extern void plpgsql_push_back_token(int token);
extern void plpgsql_scanner_init(const char *str);
extern void plpgsql_scanner_finish(void);
+/* ----------
+ * Externs in gram.y
+ * ----------
+ */
+extern int plpgsql_yyparse(void);
+
#endif /* PLPGSQL_H */
+++ /dev/null
-%{
-/*-------------------------------------------------------------------------
- *
- * scan.l - Scanner for the PL/pgSQL procedural language
- *
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.76 2009/11/10 02:13:13 tgl Exp $
- *
- *-------------------------------------------------------------------------
- */
-
-#include "plpgsql.h"
-
-#include "mb/pg_wchar.h"
-
-
-/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
-#undef fprintf
-#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
-
-/*
- * Each call to yylex must set yylloc to the location of the found token
- * (expressed as a byte offset from the start of the input text).
- * When we parse a token that requires multiple lexer rules to process,
- * this should be done in the first such rule, else yylloc will point
- * into the middle of the token.
- */
-#define SET_YYLLOC() (yylloc = yytext - scanbuf)
-
-/* Handles to the buffer that the lexer uses internally */
-static YY_BUFFER_STATE scanbufhandle;
-static char *scanbuf;
-
-static const char *scanorig; /* original input string */
-
-static int pushback_token;
-static bool have_pushback_token;
-static const char *cur_line_start;
-static const char *cur_line_end;
-static int cur_line_num;
-static int xcdepth = 0; /* depth of nesting in slash-star comments */
-static char *dolqstart; /* current $foo$ quote start string */
-
-bool plpgsql_LookupIdentifiers = true;
-
-static void location_lineno_init(void);
-
-%}
-
-%option 8bit
-%option never-interactive
-%option nodefault
-%option noinput
-%option nounput
-%option noyywrap
-%option noyyalloc
-%option noyyrealloc
-%option noyyfree
-%option warn
-%option prefix="plpgsql_base_yy"
-
-%option case-insensitive
-
-/*
- * Exclusive states are a subset of the core lexer's:
- * <xc> extended C-style comments
- * <xq> standard quoted strings
- * <xe> extended quoted strings (support backslash escape sequences)
- * <xdolq> $foo$ quoted strings
- */
-
-%x xc
-%x xe
-%x xq
-%x xdolq
-
-/*
- * Definitions --- these generally must match the core lexer, but in some
- * cases we can simplify, since we only care about identifying the token
- * boundaries and not about deriving the represented value. Also, we
- * aren't trying to lex multicharacter operators so their interactions
- * with comments go away.
- */
-
-space [ \t\n\r\f]
-horiz_space [ \t\f]
-newline [\n\r]
-non_newline [^\n\r]
-
-comment ("--"{non_newline}*)
-
-whitespace ({space}+|{comment})
-special_whitespace ({space}+|{comment}{newline})
-horiz_whitespace ({horiz_space}|{comment})
-whitespace_with_newline ({horiz_whitespace}*{newline}{special_whitespace}*)
-
-quote '
-quotestop {quote}{whitespace}*
-quotecontinue {quote}{whitespace_with_newline}{quote}
-quotefail {quote}{whitespace}*"-"
-
-xestart [eE]{quote}
-xeinside [^\\']+
-xeescape [\\].
-
-xqstart {quote}
-xqdouble {quote}{quote}
-xqinside [^']+
-
-dolq_start [A-Za-z\200-\377_]
-dolq_cont [A-Za-z\200-\377_0-9]
-dolqdelim \$({dolq_start}{dolq_cont}*)?\$
-dolqfailed \${dolq_start}{dolq_cont}*
-dolqinside [^$]+
-
-xcstart \/\*
-xcstop \*+\/
-xcinside [^*/]+
-
-digit [0-9]
-ident_start [A-Za-z\200-\377_]
-ident_cont [A-Za-z\200-\377_0-9\$]
-
-/* This is a simpler treatment of quoted identifiers than the core uses */
-quoted_ident (\"[^\"]*\")+
-
-identifier ({ident_start}{ident_cont}*|{quoted_ident})
-
-param \${digit}+
-
-%%
- /* ----------
- * Reset the state when entering yylex()
- * ----------
- */
- BEGIN(INITIAL);
-
- /* ----------
- * The keyword rules
- * ----------
- */
-:= { SET_YYLLOC(); return K_ASSIGN; }
-= { SET_YYLLOC(); return K_ASSIGN; }
-\.\. { SET_YYLLOC(); return K_DOTDOT; }
-alias { SET_YYLLOC(); return K_ALIAS; }
-all { SET_YYLLOC(); return K_ALL; }
-begin { SET_YYLLOC(); return K_BEGIN; }
-by { SET_YYLLOC(); return K_BY; }
-case { SET_YYLLOC(); return K_CASE; }
-close { SET_YYLLOC(); return K_CLOSE; }
-constant { SET_YYLLOC(); return K_CONSTANT; }
-continue { SET_YYLLOC(); return K_CONTINUE; }
-cursor { SET_YYLLOC(); return K_CURSOR; }
-declare { SET_YYLLOC(); return K_DECLARE; }
-default { SET_YYLLOC(); return K_DEFAULT; }
-diagnostics { SET_YYLLOC(); return K_DIAGNOSTICS; }
-else { SET_YYLLOC(); return K_ELSE; }
-elseif { SET_YYLLOC(); return K_ELSIF; }
-elsif { SET_YYLLOC(); return K_ELSIF; }
-end { SET_YYLLOC(); return K_END; }
-exception { SET_YYLLOC(); return K_EXCEPTION; }
-execute { SET_YYLLOC(); return K_EXECUTE; }
-exit { SET_YYLLOC(); return K_EXIT; }
-fetch { SET_YYLLOC(); return K_FETCH; }
-for { SET_YYLLOC(); return K_FOR; }
-from { SET_YYLLOC(); return K_FROM; }
-get { SET_YYLLOC(); return K_GET; }
-if { SET_YYLLOC(); return K_IF; }
-in { SET_YYLLOC(); return K_IN; }
-insert { SET_YYLLOC(); return K_INSERT; }
-into { SET_YYLLOC(); return K_INTO; }
-is { SET_YYLLOC(); return K_IS; }
-loop { SET_YYLLOC(); return K_LOOP; }
-move { SET_YYLLOC(); return K_MOVE; }
-no{space}+scroll { SET_YYLLOC(); return K_NOSCROLL; }
-not { SET_YYLLOC(); return K_NOT; }
-null { SET_YYLLOC(); return K_NULL; }
-open { SET_YYLLOC(); return K_OPEN; }
-or { SET_YYLLOC(); return K_OR; }
-perform { SET_YYLLOC(); return K_PERFORM; }
-raise { SET_YYLLOC(); return K_RAISE; }
-return { SET_YYLLOC(); return K_RETURN; }
-scroll { SET_YYLLOC(); return K_SCROLL; }
-strict { SET_YYLLOC(); return K_STRICT; }
-then { SET_YYLLOC(); return K_THEN; }
-to { SET_YYLLOC(); return K_TO; }
-using { SET_YYLLOC(); return K_USING; }
-when { SET_YYLLOC(); return K_WHEN; }
-while { SET_YYLLOC(); return K_WHILE; }
-
-^#option { SET_YYLLOC(); return O_OPTION; }
-dump { SET_YYLLOC(); return O_DUMP; }
-
-
- /* ----------
- * Special word rules
- * ----------
- */
-{identifier} {
- SET_YYLLOC();
- return plpgsql_parse_word(yytext); }
-{identifier}{space}*\.{space}*{identifier} {
- SET_YYLLOC();
- return plpgsql_parse_dblword(yytext); }
-{identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier} {
- SET_YYLLOC();
- return plpgsql_parse_tripword(yytext); }
-{param} {
- SET_YYLLOC();
- return plpgsql_parse_word(yytext); }
-{param}{space}*\.{space}*{identifier} {
- SET_YYLLOC();
- return plpgsql_parse_dblword(yytext); }
-{param}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier} {
- SET_YYLLOC();
- return plpgsql_parse_tripword(yytext); }
-
-{digit}+ { SET_YYLLOC(); return T_NUMBER; }
-
-\". { SET_YYLLOC(); yyerror("unterminated quoted identifier"); }
-
- /* ----------
- * Comment and literal handling is mostly copied from the core lexer
- * ----------
- */
-{whitespace} {
- /* ignore */
- }
-
-{xcstart} {
- SET_YYLLOC();
- xcdepth = 0;
- BEGIN(xc);
- }
-
-<xc>{xcstart} {
- xcdepth++;
- }
-
-<xc>{xcstop} {
- if (xcdepth <= 0)
- BEGIN(INITIAL);
- else
- xcdepth--;
- }
-
-<xc>{xcinside} {
- /* ignore */
- }
-
-<xc>\/+ {
- /* ignore */
- }
-
-<xc>\*+ {
- /* ignore */
- }
-
-<xc><<EOF>> { yyerror("unterminated /* comment"); }
-
-{xqstart} {
- SET_YYLLOC();
- if (standard_conforming_strings)
- BEGIN(xq);
- else
- BEGIN(xe);
- }
-{xestart} {
- SET_YYLLOC();
- BEGIN(xe);
- }
-<xq,xe>{quotestop} |
-<xq,xe>{quotefail} {
- yyless(1);
- BEGIN(INITIAL);
- /* adjust yytext/yyleng to describe whole string token */
- yyleng += (yytext - (scanbuf + yylloc));
- yytext = scanbuf + yylloc;
- return T_STRING;
- }
-<xq,xe>{xqdouble} {
- }
-<xq>{xqinside} {
- }
-<xe>{xeinside} {
- }
-<xe>{xeescape} {
- }
-<xq,xe>{quotecontinue} {
- /* ignore */
- }
-<xe>. {
- /* This is only needed for \ just before EOF */
- }
-<xq,xe><<EOF>> { yyerror("unterminated quoted string"); }
-
-{dolqdelim} {
- SET_YYLLOC();
- dolqstart = pstrdup(yytext);
- BEGIN(xdolq);
- }
-{dolqfailed} {
- /* throw back all but the initial "$" */
- yyless(1);
- /* and treat it as {other} */
- SET_YYLLOC(); return yytext[0];
- }
-<xdolq>{dolqdelim} {
- if (strcmp(yytext, dolqstart) == 0)
- {
- pfree(dolqstart);
- BEGIN(INITIAL);
- /* adjust yytext/yyleng to describe whole string */
- yyleng += (yytext - (scanbuf + yylloc));
- yytext = scanbuf + yylloc;
- return T_STRING;
- }
- else
- {
- /*
- * When we fail to match $...$ to dolqstart, transfer
- * the $... part to the output, but put back the final
- * $ for rescanning. Consider $delim$...$junk$delim$
- */
- yyless(yyleng-1);
- }
- }
-<xdolq>{dolqinside} {
- }
-<xdolq>{dolqfailed} {
- }
-<xdolq>. {
- /* This is only needed for $ inside the quoted text */
- }
-<xdolq><<EOF>> { yyerror("unterminated dollar-quoted string"); }
-
- /* ----------
- * Any unmatched character is returned as is
- * ----------
- */
-. {
- SET_YYLLOC(); return yytext[0];
- }
-
-%%
-
-
-/*
- * This is the yylex routine called from outside. It exists to provide
- * a one-token pushback facility. Beware of trying to push back more;
- * for the most part, plpgsql's gram.y assumes that yytext and yylloc
- * are in step with the "current token". In particular it is assumed that
- * those are in step with the result immediately after any yylex() call.
- */
-int
-plpgsql_yylex(void)
-{
- if (have_pushback_token)
- {
- have_pushback_token = false;
- return pushback_token;
- }
- return yylex();
-}
-
-/*
- * Push back a single token to be re-read by next plpgsql_yylex() call.
- *
- * NOTE: this does not cause yytext or yylloc to "back up". Also, it
- * is not a good idea to push back a token other than what you read.
- */
-void
-plpgsql_push_back_token(int token)
-{
- if (have_pushback_token)
- elog(ERROR, "cannot push back multiple tokens");
- pushback_token = token;
- have_pushback_token = true;
-}
-
-/*
- * Append the function text starting at startlocation and extending to
- * (not including) endlocation onto the existing contents of "buf".
- */
-void
-plpgsql_append_source_text(StringInfo buf,
- int startlocation, int endlocation)
-{
- Assert(startlocation <= endlocation);
- appendBinaryStringInfo(buf, scanorig + startlocation,
- endlocation - startlocation);
-}
-
-/*
- * plpgsql_scanner_errposition
- * Report an error cursor position, if possible.
- *
- * This is expected to be used within an ereport() call. The return value
- * is a dummy (always 0, in fact).
- *
- * Note that this can only be used for messages emitted during initial
- * parsing of a plpgsql function, since it requires the scanorig string
- * to still be available.
- */
-int
-plpgsql_scanner_errposition(int location)
-{
- int pos;
-
- if (location < 0 || scanorig == NULL)
- return 0; /* no-op if location is unknown */
-
- /* Convert byte offset to character number */
- pos = pg_mbstrlen_with_len(scanorig, location) + 1;
- /* And pass it to the ereport mechanism */
- (void) internalerrposition(pos);
- /* Also pass the function body string */
- return internalerrquery(scanorig);
-}
-
-/*
- * plpgsql_yyerror
- * Report a lexer or grammar error.
- *
- * The message's cursor position is whatever YYLLOC was last set to,
- * ie, the start of the current token if called within yylex(), or the
- * most recently lexed token if called from the grammar.
- * This is OK for syntax error messages from the Bison parser, because Bison
- * parsers report error as soon as the first unparsable token is reached.
- * Beware of using yyerror for other purposes, as the cursor position might
- * be misleading!
- */
-void
-plpgsql_yyerror(const char *message)
-{
- const char *loc = scanbuf + yylloc;
-
- if (*loc == YY_END_OF_BUFFER_CHAR)
- {
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- /* translator: %s is typically the translation of "syntax error" */
- errmsg("%s at end of input", _(message)),
- plpgsql_scanner_errposition(yylloc)));
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- /* translator: first %s is typically the translation of "syntax error" */
- errmsg("%s at or near \"%s\"", _(message), loc),
- plpgsql_scanner_errposition(yylloc)));
- }
-}
-
-/*
- * Given a location (a byte offset in the function source text),
- * return a line number.
- *
- * We expect that this is typically called for a sequence of increasing
- * location values, so optimize accordingly by tracking the endpoints
- * of the "current" line.
- */
-int
-plpgsql_location_to_lineno(int location)
-{
- const char *loc;
-
- if (location < 0 || scanorig == NULL)
- return 0; /* garbage in, garbage out */
- loc = scanorig + location;
-
- /* be correct, but not fast, if input location goes backwards */
- if (loc < cur_line_start)
- location_lineno_init();
-
- while (cur_line_end != NULL && loc > cur_line_end)
- {
- cur_line_start = cur_line_end + 1;
- cur_line_num++;
- cur_line_end = strchr(cur_line_start, '\n');
- }
-
- return cur_line_num;
-}
-
-/* initialize or reset the state for plpgsql_location_to_lineno */
-static void
-location_lineno_init(void)
-{
- cur_line_start = scanorig;
- 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++;
-
- cur_line_end = strchr(cur_line_start, '\n');
-}
-
-/* return the most recently computed lineno */
-int
-plpgsql_latest_lineno(void)
-{
- return cur_line_num;
-}
-
-
-/*
- * Called before any actual parsing is done
- *
- * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
- * Although it is not fed directly to flex, we need the original string
- * to cite in error messages.
- */
-void
-plpgsql_scanner_init(const char *str)
-{
- Size slen = strlen(str);
-
- /*
- * Reset flex internal state. Whatever data it might think it has
- * has long since been pfree'd.
- */
- yy_init_globals();
-
- /*
- * Make a scan buffer with special termination needed by flex.
- */
- scanbuf = (char *) palloc(slen + 2);
- memcpy(scanbuf, str, slen);
- scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
- scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
-
- /*
- * scanorig points to the original string, which unlike scanbuf won't
- * be modified on-the-fly by flex. Notice that although yytext points
- * into scanbuf, we rely on being able to apply locations (offsets from
- * string start) to scanorig as well.
- */
- scanorig = str;
-
- /* Other setup */
- have_pushback_token = false;
-
- location_lineno_init();
-
- BEGIN(INITIAL);
- plpgsql_LookupIdentifiers = true;
-}
-
-/*
- * Called after parsing is done to clean up after plpgsql_scanner_init()
- */
-void
-plpgsql_scanner_finish(void)
-{
- /* release storage */
- yy_delete_buffer(scanbufhandle);
- pfree(scanbuf);
- /* avoid leaving any dangling pointers */
- scanbufhandle = NULL;
- scanbuf = NULL;
- scanorig = NULL;
-}
-
-/*
- * Interface functions to make flex use palloc() instead of malloc().
- * It'd be better to make these static, but flex insists otherwise.
- */
-
-void *
-plpgsql_base_yyalloc(yy_size_t bytes)
-{
- return palloc(bytes);
-}
-
-void *
-plpgsql_base_yyrealloc(void *ptr, yy_size_t bytes)
-{
- if (ptr)
- return repalloc(ptr, bytes);
- else
- return palloc(bytes);
-}
-
-void
-plpgsql_base_yyfree(void *ptr)
-{
- if (ptr)
- pfree(ptr);
-}
LINE 4: return 'foo\\bar\041baz';
^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
+WARNING: nonstandard use of \\ in a string literal
+LINE 4: return 'foo\\bar\041baz';
+ ^
+HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest();
NOTICE: foo\bar!baz
WARNING: nonstandard use of \\ in a string literal
(5 rows)
drop function conflict_test();
+-- Check that an unreserved keyword can be used as a variable name
+create function unreserved_test() returns int as $$
+declare
+ forward int := 21;
+begin
+ forward := forward * 2;
+ return forward;
+end
+$$ language plpgsql;
+select unreserved_test();
+ unreserved_test
+-----------------
+ 42
+(1 row)
+
+drop function unreserved_test();
select * from conflict_test();
drop function conflict_test();
+
+-- Check that an unreserved keyword can be used as a variable name
+
+create function unreserved_test() returns int as $$
+declare
+ forward int := 21;
+begin
+ forward := forward * 2;
+ return forward;
+end
+$$ language plpgsql;
+
+select unreserved_test();
+
+drop function unreserved_test();
#
# Package that generates build files for msvc build
#
-# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.43 2009/10/01 01:58:58 tgl Exp $
+# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.44 2009/11/12 00:13:00 tgl Exp $
#
use Carp;
use Win32;
$snowball->AddReference($postgres);
my $plpgsql = $solution->AddProject('plpgsql','dll','PLs','src\pl\plpgsql\src');
- $plpgsql->AddFiles('src\pl\plpgsql\src','scan.l','gram.y');
+ $plpgsql->AddFiles('src\pl\plpgsql\src', 'gram.y');
$plpgsql->AddReference($postgres);
if ($solution->{options}->{perl})
#
# Package that encapsulates a Visual C++ project file generation
#
-# $PostgreSQL: pgsql/src/tools/msvc/Project.pm,v 1.20 2009/07/27 07:11:15 mha Exp $
+# $PostgreSQL: pgsql/src/tools/msvc/Project.pm,v 1.21 2009/11/12 00:13:00 tgl Exp $
#
use Carp;
use strict;
{
my $of = $f;
$of =~ s/\.l$/.c/;
- $of =~ s{^src\\pl\\plpgsql\\src\\scan.c$}{src\\pl\\plpgsql\\src\\pl_scan.c};
print F '>'
. GenerateCustomTool('Running flex on ' . $f, 'src\tools\msvc\pgflex.bat ' . $f,$of)
. '</File>' . "\n";
@echo off
-REM $PostgreSQL: pgsql/src/tools/msvc/clean.bat,v 1.15 2008/08/30 02:32:24 tgl Exp $
+REM $PostgreSQL: pgsql/src/tools/msvc/clean.bat,v 1.16 2009/11/12 00:13:00 tgl Exp $
set DIST=0
if "%1"=="dist" set DIST=1
if exist src\port\pg_config_paths.h del /q src\port\pg_config_paths.h
if exist src\pl\plperl\spi.c del /q src\pl\plperl\spi.c
-if %DIST%==1 if exist src\pl\plpgsql\src\pl_scan.c del /q src\pl\plpgsql\src\pl_scan.c
if %DIST%==1 if exist src\pl\plpgsql\src\pl_gram.c del /q src\pl\plpgsql\src\pl_gram.c
if %DIST%==1 if exist src\pl\plpgsql\src\pl_gram.h del /q src\pl\plpgsql\src\pl_gram.h
@echo off
-REM $PostgreSQL: pgsql/src/tools/msvc/pgflex.bat,v 1.5 2007/12/19 12:29:36 mha Exp $
+REM $PostgreSQL: pgsql/src/tools/msvc/pgflex.bat,v 1.6 2009/11/12 00:13:00 tgl Exp $
IF NOT EXIST src\tools\msvc\buildenv.pl goto nobuildenv
perl -e "require 'src/tools/msvc/buildenv.pl'; while(($k,$v) = each %ENV) { print qq[\@SET $k=$v\n]; }" > bldenv.bat
if "%1" == "src\backend\parser\scan.l" call :generate %1 src\backend\parser\scan.c -CF
if "%1" == "src\backend\bootstrap\bootscanner.l" call :generate %1 src\backend\bootstrap\bootscanner.c
if "%1" == "src\backend\utils\misc\guc-file.l" call :generate %1 src\backend\utils\misc\guc-file.c
-if "%1" == "src\pl\plpgsql\src\scan.l" call :generate %1 src\pl\plpgsql\src\pl_scan.c
if "%1" == "src\interfaces\ecpg\preproc\pgc.l" call :generate %1 src\interfaces\ecpg\preproc\pgc.c
if "%1" == "src\bin\psql\psqlscan.l" call :generate %1 src\bin\psql\psqlscan.c
if "%1" == "contrib\cube\cubescan.l" call :generate %1 contrib\cube\cubescan.c