postgresql/src/bin/pgbench/exprscan.l

355 lines
8.5 KiB
Plaintext

%{
/*-------------------------------------------------------------------------
*
* exprscan.l
* lexical scanner for pgbench backslash commands
*
* This lexer supports two operating modes:
*
* In INITIAL state, just parse off whitespace-separated words (this mode
* is basically equivalent to strtok(), which is what we used to use).
*
* In EXPR state, lex for the simple expression syntax of exprparse.y.
*
* In either mode, stop upon hitting newline or end of string.
*
* Note that this lexer operates within the framework created by psqlscan.l,
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/bin/pgbench/exprscan.l
*
*-------------------------------------------------------------------------
*/
#include "fe_utils/psqlscan_int.h"
/* context information for reporting errors in expressions */
static const char *expr_source = NULL;
static int expr_lineno = 0;
static int expr_start_offset = 0;
static const char *expr_command = NULL;
/* indicates whether last yylex() call read a newline */
static bool last_was_newline = false;
/*
* 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 bison-bridge
%option 8bit
%option never-interactive
%option nodefault
%option noinput
%option nounput
%option noyywrap
%option warn
%option prefix="expr_yy"
/* Character classes */
alpha [a-zA-Z_]
digit [0-9]
alnum [a-zA-Z0-9_]
/* {space} + {nonspace} + {newline} should cover all characters */
space [ \t\r\f\v]
nonspace [^ \t\r\f\v\n]
newline [\n]
/* Exclusive states */
%x EXPR
%%
%{
/* Declare some local variables inside yylex(), for convenience */
PsqlScanState cur_state = yyextra;
/*
* Force flex into the state indicated by start_state. This has a
* couple of purposes: it lets some of the functions below set a new
* starting state without ugly direct access to flex variables, and it
* allows us to transition from one flex lexer to another so that we
* can lex different parts of the source string using separate lexers.
*/
BEGIN(cur_state->start_state);
/* Reset was-newline flag */
last_was_newline = false;
%}
/* INITIAL state */
{nonspace}+ {
/* Found a word, emit and return it */
psqlscan_emit(cur_state, yytext, yyleng);
return 1;
}
{space}+ { /* ignore */ }
{newline} {
/* report end of command */
last_was_newline = true;
return 0;
}
/* EXPR state */
<EXPR>{
"+" { return '+'; }
"-" { return '-'; }
"*" { return '*'; }
"/" { return '/'; }
"%" { return '%'; }
"(" { return '('; }
")" { return ')'; }
"," { return ','; }
:{alnum}+ {
yylval->str = pg_strdup(yytext + 1);
return VARIABLE;
}
{digit}+ {
yylval->ival = strtoint64(yytext);
return INTEGER;
}
{alpha}{alnum}* {
yylval->str = pg_strdup(yytext);
return FUNCTION;
}
{newline} {
/* report end of command */
last_was_newline = true;
return 0;
}
{space}+ { /* ignore */ }
. {
/*
* must strdup yytext so that expr_yyerror_more doesn't
* change it while finding end of line
*/
expr_yyerror_more(yyscanner, "unexpected character",
pg_strdup(yytext));
/* NOTREACHED, syntax_error calls exit() */
return 0;
}
}
<<EOF>> {
if (cur_state->buffer_stack == NULL)
return 0; /* end of input reached */
/*
* We were expanding a variable, so pop the inclusion
* stack and keep lexing
*/
psqlscan_pop_buffer_stack(cur_state);
psqlscan_select_top_buffer(cur_state);
}
%%
void
expr_yyerror_more(yyscan_t yyscanner, const char *message, const char *more)
{
PsqlScanState state = yyget_extra(yyscanner);
int error_detection_offset = expr_scanner_offset(state) - 1;
YYSTYPE lval;
char *full_line;
size_t l;
/*
* While parsing an expression, we may not have collected the whole line
* yet from the input source. Lex till EOL so we can report whole line.
* (If we're at EOF, it's okay to call yylex() an extra time.)
*/
if (!last_was_newline)
{
while (yylex(&lval, yyscanner))
/* skip */ ;
}
full_line = expr_scanner_get_substring(state,
expr_start_offset,
expr_scanner_offset(state));
/* Trim trailing newline if any */
l = strlen(full_line);
while (l > 0 && full_line[l - 1] == '\n')
full_line[--l] = '\0';
syntax_error(expr_source, expr_lineno, full_line, expr_command,
message, more, error_detection_offset - expr_start_offset);
}
void
expr_yyerror(yyscan_t yyscanner, const char *message)
{
expr_yyerror_more(yyscanner, message, NULL);
}
/*
* Collect a space-separated word from a backslash command and return it
* in word_buf, along with its starting string offset in *offset.
* Returns true if successful, false if at end of command.
*/
bool
expr_lex_one_word(PsqlScanState state, PQExpBuffer word_buf, int *offset)
{
int lexresult;
YYSTYPE lval;
/* Must be scanning already */
Assert(state->scanbufhandle != NULL);
/* Set current output target */
state->output_buf = word_buf;
resetPQExpBuffer(word_buf);
/* Set input source */
if (state->buffer_stack != NULL)
yy_switch_to_buffer(state->buffer_stack->buf, state->scanner);
else
yy_switch_to_buffer(state->scanbufhandle, state->scanner);
/* Set start state */
state->start_state = INITIAL;
/* And lex. */
lexresult = yylex(&lval, state->scanner);
/*
* Save start offset of word, if any. We could do this more efficiently,
* but for now this seems fine.
*/
if (lexresult)
*offset = expr_scanner_offset(state) - word_buf->len;
else
*offset = -1;
/*
* In case the caller returns to using the regular SQL lexer, reselect the
* appropriate initial state.
*/
psql_scan_reselect_sql_lexer(state);
return (bool) lexresult;
}
/*
* Prepare to lex an expression via expr_yyparse().
*
* Returns the yyscan_t that is to be passed to expr_yyparse().
* (This is just state->scanner, but callers don't need to know that.)
*/
yyscan_t
expr_scanner_init(PsqlScanState state,
const char *source, int lineno, int start_offset,
const char *command)
{
/* Save error context info */
expr_source = source;
expr_lineno = lineno;
expr_start_offset = start_offset;
expr_command = command;
/* Must be scanning already */
Assert(state->scanbufhandle != NULL);
/* Set current output target */
state->output_buf = NULL;
/* Set input source */
if (state->buffer_stack != NULL)
yy_switch_to_buffer(state->buffer_stack->buf, state->scanner);
else
yy_switch_to_buffer(state->scanbufhandle, state->scanner);
/* Set start state */
state->start_state = EXPR;
return state->scanner;
}
/*
* Finish lexing an expression.
*/
void
expr_scanner_finish(yyscan_t yyscanner)
{
PsqlScanState state = yyget_extra(yyscanner);
/*
* Reselect appropriate initial state for SQL lexer.
*/
psql_scan_reselect_sql_lexer(state);
}
/*
* Get offset from start of string to end of current lexer token.
*
* We rely on the knowledge that flex modifies the scan buffer by storing
* a NUL at the end of the current token (yytext). Note that this might
* not work quite right if we were parsing a sub-buffer, but since pgbench
* never invokes that functionality, it doesn't matter.
*/
int
expr_scanner_offset(PsqlScanState state)
{
return strlen(state->scanbuf);
}
/*
* Get a malloc'd copy of the lexer input string from start_offset
* to just before end_offset.
*/
char *
expr_scanner_get_substring(PsqlScanState state,
int start_offset, int end_offset)
{
char *result;
int slen = end_offset - start_offset;
Assert(slen >= 0);
Assert(end_offset <= strlen(state->scanbuf));
result = (char *) pg_malloc(slen + 1);
memcpy(result, state->scanbuf + start_offset, slen);
result[slen] = '\0';
return result;
}
/*
* Get the line number associated with the given string offset
* (which must not be past the end of where we've lexed to).
*/
int
expr_scanner_get_lineno(PsqlScanState state, int offset)
{
int lineno = 1;
const char *p = state->scanbuf;
while (*p && offset > 0)
{
if (*p == '\n')
lineno++;
p++, offset--;
}
return lineno;
}