Make pgbench's expression lexer reentrant.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 19 Mar 2016 20:35:41 +0000 (16:35 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 19 Mar 2016 20:35:41 +0000 (16:35 -0400)
This is a necessary preliminary step for making it play with psqlscan.l
given the way I set up the lexer input-buffer sharing mechanism in commit
0ea9efbe9ec1bf07.

I've not tried to make it *actually* reentrant; there's still some static
variables laying about.  But flex thinks it's reentrant, and that's what
counts.

In support of that, fix exprparse.y to pass through the yyscan_t from the
caller.  Also do some minor code beautification, like not casting away
const.

src/bin/pgbench/exprparse.y
src/bin/pgbench/exprscan.l
src/bin/pgbench/pgbench.c
src/bin/pgbench/pgbench.h

index cac4d5e4f44ab14122b2e816dd31b973bf7d6022..55fda5b254c224b804514367b7e742659987582b 100644 (file)
@@ -7,6 +7,8 @@
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
+ * src/bin/pgbench/exprparse.y
+ *
  *-------------------------------------------------------------------------
  */
 
@@ -19,16 +21,19 @@ PgBenchExpr *expr_parse_result;
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_variable(char *varname);
-static PgBenchExpr *make_op(const char *operator, PgBenchExpr *lexpr,
-       PgBenchExpr *rexpr);
-static int find_func(const char *fname);
-static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
+                           PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static int find_func(yyscan_t yyscanner, const char *fname);
+static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
 
 %}
 
 %expect 0
 %name-prefix="expr_yy"
 
+%parse-param {yyscan_t yyscanner}
+%lex-param   {yyscan_t yyscanner}
+
 %union
 {
    int64       ival;
@@ -43,7 +48,6 @@ static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args);
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER VARIABLE FUNCTION
-%token CHAR_ERROR /* never used, will raise a syntax error */
 
 /* Precedence: lowest to highest */
 %left  '+' '-'
@@ -61,18 +65,19 @@ elist:                      { $$ = NULL; }
 
 expr: '(' expr ')'         { $$ = $2; }
    | '+' expr %prec UMINUS { $$ = $2; }
-   | '-' expr %prec UMINUS { $$ = make_op("-", make_integer_constant(0), $2); }
-   | expr '+' expr         { $$ = make_op("+", $1, $3); }
-   | expr '-' expr         { $$ = make_op("-", $1, $3); }
-   | expr '*' expr         { $$ = make_op("*", $1, $3); }
-   | expr '/' expr         { $$ = make_op("/", $1, $3); }
-   | expr '%' expr         { $$ = make_op("%", $1, $3); }
+   | '-' expr %prec UMINUS { $$ = make_op(yyscanner, "-",
+                                          make_integer_constant(0), $2); }
+   | expr '+' expr         { $$ = make_op(yyscanner, "+", $1, $3); }
+   | expr '-' expr         { $$ = make_op(yyscanner, "-", $1, $3); }
+   | expr '*' expr         { $$ = make_op(yyscanner, "*", $1, $3); }
+   | expr '/' expr         { $$ = make_op(yyscanner, "/", $1, $3); }
+   | expr '%' expr         { $$ = make_op(yyscanner, "%", $1, $3); }
    | INTEGER               { $$ = make_integer_constant($1); }
    | VARIABLE              { $$ = make_variable($1); }
-   | function '(' elist ')'{ $$ = make_func($1, $3); }
+   | function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
    ;
 
-function: FUNCTION         { $$ = find_func($1); pg_free($1); }
+function: FUNCTION         { $$ = find_func(yyscanner, $1); pg_free($1); }
    ;
 
 %%
@@ -98,9 +103,10 @@ make_variable(char *varname)
 }
 
 static PgBenchExpr *
-make_op(const char *operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr)
+make_op(yyscan_t yyscanner, const char *operator,
+       PgBenchExpr *lexpr, PgBenchExpr *rexpr)
 {
-   return make_func(find_func(operator),
+   return make_func(yyscanner, find_func(yyscanner, operator),
                     make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
@@ -139,7 +145,7 @@ static struct
  * or fail if the function is unknown.
  */
 static int
-find_func(const char * fname)
+find_func(yyscan_t yyscanner, const char *fname)
 {
    int i = 0;
 
@@ -150,7 +156,7 @@ find_func(const char * fname)
        i++;
    }
 
-   expr_yyerror_more("unexpected function name", fname);
+   expr_yyerror_more(yyscanner, "unexpected function name", fname);
 
    /* not reached */
    return -1;
@@ -198,7 +204,7 @@ elist_length(PgBenchExprList *list)
 
 /* Build function call expression */
 static PgBenchExpr *
-make_func(const int fnumber, PgBenchExprList *args)
+make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 {
    PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
 
@@ -206,13 +212,13 @@ make_func(const int fnumber, PgBenchExprList *args)
 
    if (PGBENCH_FUNCTIONS[fnumber].nargs >= 0 &&
        PGBENCH_FUNCTIONS[fnumber].nargs != elist_length(args))
-       expr_yyerror_more("unexpected number of arguments",
+       expr_yyerror_more(yyscanner, "unexpected number of arguments",
                          PGBENCH_FUNCTIONS[fnumber].fname);
 
    /* check at least one arg for min & max */
    if (PGBENCH_FUNCTIONS[fnumber].nargs == -1 &&
        elist_length(args) == 0)
-       expr_yyerror_more("at least one argument expected",
+       expr_yyerror_more(yyscanner, "at least one argument expected",
                          PGBENCH_FUNCTIONS[fnumber].fname);
 
    expr->etype = ENODE_FUNCTION;
@@ -226,4 +232,15 @@ make_func(const int fnumber, PgBenchExprList *args)
    return expr;
 }
 
+/*
+ * exprscan.l is compiled as part of exprparse.y.  Currently, this is
+ * unavoidable because exprparse does not create a .h file to export
+ * its token symbols.  If these files ever grow large enough to be
+ * worth compiling separately, that could be fixed; but for now it
+ * seems like useless complication.
+ */
+
+/* First, get rid of "#define yyscan_t" from pgbench.h */
+#undef yyscan_t
+
 #include "exprscan.c"
index fc7615f55859552c1fa72b8ef4cc8bd3a2275366..00cb74d7dadd70cc0caece072294e977abacd981 100644 (file)
@@ -7,6 +7,8 @@
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
+ * src/bin/pgbench/exprscan.l
+ *
  *-------------------------------------------------------------------------
  */
 
@@ -16,16 +18,27 @@ static int  yyline = 0, yycol = 0;
 /* Handles to the buffer that the lexer uses internally */
 static YY_BUFFER_STATE scanbufhandle;
 static char *scanbuf;
-static int scanbuflen;
 
 /* context information for error reporting */
-static char *expr_source = NULL;
+static const char *expr_source = NULL;
 static int expr_lineno = 0;
-static char *expr_full_line = NULL;
-static char *expr_command = NULL;
+static const char *expr_full_line = NULL;
+static const char *expr_command = NULL;
 static int expr_col = 0;
+
+/*
+ * Work around a bug in flex 2.5.35: it emits a couple of functions that
+ * it forgets to emit declarations for.  Since we use -Wmissing-prototypes,
+ * this would cause warnings.  Providing our own declarations should be
+ * harmless even when the bug gets fixed.
+ */
+extern int expr_yyget_column(yyscan_t yyscanner);
+extern void expr_yyset_column(int column_no, yyscan_t yyscanner);
+
 %}
 
+/* Except for the prefix, these options should match psqlscan.l */
+%option reentrant
 %option 8bit
 %option never-interactive
 %option nodefault
@@ -42,6 +55,15 @@ space            [ \t\r\f]
 
 %%
 
+%{
+       /*
+        * Force flex into the appropriate start state ... which, for this
+        * case, is always INITIAL.  This ensures that we can transition
+        * between different lexers sharing the same yyscan_t.
+        */
+       BEGIN(INITIAL);
+%}
+
 "+"                { yycol += yyleng; return '+'; }
 "-"                { yycol += yyleng; return '-'; }
 "*"                { yycol += yyleng; return '*'; }
@@ -69,77 +91,76 @@ space           [ \t\r\f]
 
 [\n]           { yycol = 0; yyline++; }
 
-{space}+       { yycol += yyleng; /* ignore */ }
+{space}+       { yycol += yyleng; /* otherwise ignore */ }
 
 .              {
                    yycol += yyleng;
                    syntax_error(expr_source, expr_lineno, expr_full_line, expr_command,
                                 "unexpected character", yytext, expr_col + yycol);
-                   /* dead code, exit is called from syntax_error */
-                   return CHAR_ERROR;
+                   /* NOTREACHED, exit is called from syntax_error */
+                   return 0;
                }
 %%
 
 void
-expr_yyerror_more(const char *message, const char *more)
+expr_yyerror_more(yyscan_t yyscanner, const char *message, const char *more)
 {
    syntax_error(expr_source, expr_lineno, expr_full_line, expr_command,
                 message, more, expr_col + yycol);
 }
 
 void
-yyerror(const char *message)
+yyerror(yyscan_t yyscanner, const char *message)
 {
-   expr_yyerror_more(message, NULL);
+   expr_yyerror_more(yyscanner, message, NULL);
 }
 
 /*
  * Called before any actual parsing is done
  */
-void
+yyscan_t
 expr_scanner_init(const char *str, const char *source,
-                 const int lineno, const char *line,
-                 const char *cmd, const int ecol)
+                 int lineno, const char *line,
+                 const char *cmd, int ecol)
 {
+   yyscan_t yyscanner;
    Size    slen = strlen(str);
 
-   /* save context informations for error messages */
-   expr_source = (char *) source;
-   expr_lineno = (int) lineno;
-   expr_full_line = (char *) line;
-   expr_command = (char *) cmd;
-   expr_col = (int) ecol;
+   /* Set up yyscan_t */
+   yylex_init(&yyscanner);
+
+   /* save context information for error messages */
+   expr_source = source;
+   expr_lineno = lineno;
+   expr_full_line = line;
+   expr_command = cmd;
+   expr_col = ecol;
 
    /* reset error pointers for this scan */
    yycol = yyline = 0;
 
-   /*
-    * Might be left over after error
-    */
-   if (YY_CURRENT_BUFFER)
-       yy_delete_buffer(YY_CURRENT_BUFFER);
-
    /*
     * Make a scan buffer with special termination needed by flex.
     */
-   scanbuflen = slen;
    scanbuf = pg_malloc(slen + 2);
    memcpy(scanbuf, str, slen);
    scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
-   scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
+   scanbufhandle = yy_scan_buffer(scanbuf, slen + 2, yyscanner);
 
-   BEGIN(INITIAL);
+   return yyscanner;
 }
 
 
 /*
- * Called after parsing is done to clean up after seg_scanner_init()
+ * Called after parsing is done to clean up after expr_scanner_init()
  */
 void
-expr_scanner_finish(void)
+expr_scanner_finish(yyscan_t yyscanner)
 {
-   yy_delete_buffer(scanbufhandle);
+   yy_delete_buffer(scanbufhandle, yyscanner);
    pg_free(scanbuf);
+   yylex_destroy(yyscanner);
+
    expr_source = NULL;
    expr_lineno = 0;
    expr_full_line = NULL;
index 4606fb001589e1fed1d22dc43f391e52a49b8ab5..dab1ed4114e96da1c4a3a6d0a92d62be846251d8 100644 (file)
@@ -2495,17 +2495,22 @@ process_commands(char *buf, const char *source, const int lineno)
        }
        else if (pg_strcasecmp(my_commands->argv[0], "set") == 0)
        {
+           yyscan_t    yyscanner;
+
            if (my_commands->argc < 3)
            {
                syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
                             "missing argument", NULL, -1);
            }
 
-           expr_scanner_init(my_commands->argv[2], source, lineno,
-                             my_commands->line, my_commands->argv[0],
-                             my_commands->cols[2] - 1);
+           yyscanner = expr_scanner_init(my_commands->argv[2],
+                                         source,
+                                         lineno,
+                                         my_commands->line,
+                                         my_commands->argv[0],
+                                         my_commands->cols[2] - 1);
 
-           if (expr_yyparse() != 0)
+           if (expr_yyparse(yyscanner) != 0)
            {
                /* dead code: exit done from syntax_error called by yyerror */
                exit(1);
@@ -2513,7 +2518,7 @@ process_commands(char *buf, const char *source, const int lineno)
 
            my_commands->expr = expr_parse_result;
 
-           expr_scanner_finish();
+           expr_scanner_finish(yyscanner);
        }
        else if (pg_strcasecmp(my_commands->argv[0], "sleep") == 0)
        {
index c6aeb5b6dbd077d4db2b414d7fc8df192a35a4b0..ba2c51acc96d547cbd731e4cead364e4f07d560a 100644 (file)
 #ifndef PGBENCH_H
 #define PGBENCH_H
 
+/*
+ * This file is included outside exprscan.l, in places where we can't see
+ * flex's definition of typedef yyscan_t.  Fortunately, it's documented as
+ * being "void *", so we can use a macro to keep the function declarations
+ * here looking like the definitions in exprscan.l.  exprparse.y also
+ * uses this to be able to declare things as "yyscan_t".
+ */
+#define yyscan_t  void *
+
 /* Types of expression nodes */
 typedef enum PgBenchExprType
 {
@@ -73,17 +82,18 @@ struct PgBenchExprList
 
 extern PgBenchExpr *expr_parse_result;
 
-extern int expr_yyparse(void);
-extern int expr_yylex(void);
-extern void expr_yyerror(const char *str);
-extern void expr_yyerror_more(const char *str, const char *more);
-extern void expr_scanner_init(const char *str, const char *source,
-                 const int lineno, const char *line,
-                 const char *cmd, const int ecol);
+extern int expr_yyparse(yyscan_t yyscanner);
+extern int expr_yylex(yyscan_t yyscanner);
+extern void expr_yyerror(yyscan_t yyscanner, const char *str);
+extern void expr_yyerror_more(yyscan_t yyscanner, const char *str,
+                 const char *more);
+extern yyscan_t expr_scanner_init(const char *str, const char *source,
+                 int lineno, const char *line,
+                 const char *cmd, int ecol);
 extern void syntax_error(const char *source, const int lineno, const char *line,
             const char *cmd, const char *msg, const char *more,
             const int col);
-extern void expr_scanner_finish(void);
+extern void expr_scanner_finish(yyscan_t yyscanner);
 
 extern int64 strtoint64(const char *str);