LCOV - code coverage report
Current view: top level - src/pl/plpgsql/src - pl_scanner.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 137 151 90.7 %
Date: 2026-01-20 08:17:19 Functions: 16 16 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pl_scanner.c
       4             :  *    lexical scanning for PL/pgSQL
       5             :  *
       6             :  *
       7             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  *
      11             :  * IDENTIFICATION
      12             :  *    src/pl/plpgsql/src/pl_scanner.c
      13             :  *
      14             :  *-------------------------------------------------------------------------
      15             :  */
      16             : #include "postgres.h"
      17             : 
      18             : #include "mb/pg_wchar.h"
      19             : #include "parser/scanner.h"
      20             : 
      21             : #include "plpgsql.h"
      22             : #include "pl_gram.h"          /* must be after parser/scanner.h */
      23             : 
      24             : 
      25             : /* Klugy flag to tell scanner how to look up identifiers */
      26             : IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
      27             : 
      28             : /*
      29             :  * A word about keywords:
      30             :  *
      31             :  * We keep reserved and unreserved keywords in separate headers.  Be careful
      32             :  * not to put the same word in both headers.  Also be sure that pl_gram.y's
      33             :  * unreserved_keyword production agrees with the unreserved header.  The
      34             :  * reserved keywords are passed to the core scanner, so they will be
      35             :  * recognized before (and instead of) any variable name.  Unreserved words
      36             :  * are checked for separately, usually after determining that the identifier
      37             :  * isn't a known variable name.  If plpgsql_IdentifierLookup is DECLARE then
      38             :  * no variable names will be recognized, so the unreserved words always work.
      39             :  * (Note in particular that this helps us avoid reserving keywords that are
      40             :  * only needed in DECLARE sections.)
      41             :  *
      42             :  * In certain contexts it is desirable to prefer recognizing an unreserved
      43             :  * keyword over recognizing a variable name.  In particular, at the start
      44             :  * of a statement we should prefer unreserved keywords unless the statement
      45             :  * looks like an assignment (i.e., first token is followed by ':=' or '[').
      46             :  * This rule allows most statement-introducing keywords to be kept unreserved.
      47             :  * (We still have to reserve initial keywords that might follow a block
      48             :  * label, unfortunately, since the method used to determine if we are at
      49             :  * start of statement doesn't recognize such cases.  We'd also have to
      50             :  * reserve any keyword that could legitimately be followed by ':=' or '['.)
      51             :  * Some additional cases are handled in pl_gram.y using tok_is_keyword().
      52             :  *
      53             :  * We try to avoid reserving more keywords than we have to; but there's
      54             :  * little point in not reserving a word if it's reserved in the core grammar.
      55             :  * Currently, the following words are reserved here but not in the core:
      56             :  * BEGIN BY DECLARE FOREACH IF LOOP WHILE
      57             :  */
      58             : 
      59             : /* ScanKeywordList lookup data for PL/pgSQL keywords */
      60             : #include "pl_reserved_kwlist_d.h"
      61             : #include "pl_unreserved_kwlist_d.h"
      62             : 
      63             : /* Token codes for PL/pgSQL keywords */
      64             : #define PG_KEYWORD(kwname, value) value,
      65             : 
      66             : static const uint16 ReservedPLKeywordTokens[] = {
      67             : #include "pl_reserved_kwlist.h"
      68             : };
      69             : 
      70             : static const uint16 UnreservedPLKeywordTokens[] = {
      71             : #include "pl_unreserved_kwlist.h"
      72             : };
      73             : 
      74             : #undef PG_KEYWORD
      75             : 
      76             : /*
      77             :  * This macro must recognize all tokens that can immediately precede a
      78             :  * PL/pgSQL executable statement (that is, proc_sect or proc_stmt in the
      79             :  * grammar).  Fortunately, there are not very many, so hard-coding in this
      80             :  * fashion seems sufficient.
      81             :  */
      82             : #define AT_STMT_START(prev_token) \
      83             :     ((prev_token) == ';' || \
      84             :      (prev_token) == K_BEGIN || \
      85             :      (prev_token) == K_THEN || \
      86             :      (prev_token) == K_ELSE || \
      87             :      (prev_token) == K_LOOP)
      88             : 
      89             : 
      90             : /* Auxiliary data about a token (other than the token type) */
      91             : typedef struct
      92             : {
      93             :     YYSTYPE     lval;           /* semantic information */
      94             :     YYLTYPE     lloc;           /* offset in scanbuf */
      95             :     int         leng;           /* length in bytes */
      96             : } TokenAuxData;
      97             : 
      98             : #define MAX_PUSHBACKS 4
      99             : 
     100             : /*
     101             :  * Scanner working state.
     102             :  */
     103             : struct plpgsql_yy_extra_type
     104             : {
     105             :     /* The stuff the core lexer needs */
     106             :     core_yy_extra_type core_yy_extra;
     107             : 
     108             :     /* The original input string */
     109             :     const char *scanorig;
     110             : 
     111             :     /*
     112             :      * Current token's length (corresponds to plpgsql_yylval and
     113             :      * plpgsql_yylloc)
     114             :      */
     115             :     int         plpgsql_yyleng;
     116             : 
     117             :     /* Current token's code (corresponds to plpgsql_yylval and plpgsql_yylloc) */
     118             :     int         plpgsql_yytoken;
     119             : 
     120             :     /* Token pushback stack */
     121             :     int         num_pushbacks;
     122             :     int         pushback_token[MAX_PUSHBACKS];
     123             :     TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
     124             : 
     125             :     /* State for plpgsql_location_to_lineno() */
     126             :     const char *cur_line_start;
     127             :     const char *cur_line_end;
     128             :     int         cur_line_num;
     129             : };
     130             : 
     131             : /* Internal functions */
     132             : static int  internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner);
     133             : static void push_back_token(int token, TokenAuxData *auxdata, yyscan_t yyscanner);
     134             : static void location_lineno_init(yyscan_t yyscanner);
     135             : 
     136             : /*
     137             :  * This is normally provided by the generated flex code, but we don't have
     138             :  * that here, so we make a minimal version ourselves.
     139             :  */
     140             : struct yyguts_t
     141             : {
     142             :     struct plpgsql_yy_extra_type *yyextra_r;
     143             : };
     144             : 
     145             : /* see scan.l */
     146             : #undef yyextra
     147             : #define yyextra (((struct yyguts_t *) yyscanner)->yyextra_r)
     148             : 
     149             : 
     150             : /*
     151             :  * This is the yylex routine called from the PL/pgSQL grammar.
     152             :  * It is a wrapper around the core lexer, with the ability to recognize
     153             :  * PL/pgSQL variables and return them as special T_DATUM tokens.  If a
     154             :  * word or compound word does not match any variable name, or if matching
     155             :  * is turned off by plpgsql_IdentifierLookup, it is returned as
     156             :  * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
     157             :  * matches one of those.
     158             :  */
     159             : int
     160      416282 : plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
     161             : {
     162             :     int         tok1;
     163             :     TokenAuxData aux1;
     164             :     int         kwnum;
     165             : 
     166      416282 :     tok1 = internal_yylex(&aux1, yyscanner);
     167      416282 :     if (tok1 == IDENT || tok1 == PARAM)
     168             :     {
     169             :         int         tok2;
     170             :         TokenAuxData aux2;
     171             : 
     172      139602 :         tok2 = internal_yylex(&aux2, yyscanner);
     173      139602 :         if (tok2 == '.')
     174             :         {
     175             :             int         tok3;
     176             :             TokenAuxData aux3;
     177             : 
     178        8690 :             tok3 = internal_yylex(&aux3, yyscanner);
     179        8690 :             if (tok3 == IDENT)
     180             :             {
     181             :                 int         tok4;
     182             :                 TokenAuxData aux4;
     183             : 
     184        8488 :                 tok4 = internal_yylex(&aux4, yyscanner);
     185        8488 :                 if (tok4 == '.')
     186             :                 {
     187             :                     int         tok5;
     188             :                     TokenAuxData aux5;
     189             : 
     190          60 :                     tok5 = internal_yylex(&aux5, yyscanner);
     191          60 :                     if (tok5 == IDENT)
     192             :                     {
     193          60 :                         if (plpgsql_parse_tripword(aux1.lval.str,
     194             :                                                    aux3.lval.str,
     195             :                                                    aux5.lval.str,
     196             :                                                    &aux1.lval.wdatum,
     197             :                                                    &aux1.lval.cword))
     198          42 :                             tok1 = T_DATUM;
     199             :                         else
     200          18 :                             tok1 = T_CWORD;
     201             :                         /* Adjust token length to include A.B.C */
     202          60 :                         aux1.leng = aux5.lloc - aux1.lloc + aux5.leng;
     203             :                     }
     204             :                     else
     205             :                     {
     206             :                         /* not A.B.C, so just process A.B */
     207           0 :                         push_back_token(tok5, &aux5, yyscanner);
     208           0 :                         push_back_token(tok4, &aux4, yyscanner);
     209           0 :                         if (plpgsql_parse_dblword(aux1.lval.str,
     210             :                                                   aux3.lval.str,
     211             :                                                   &aux1.lval.wdatum,
     212             :                                                   &aux1.lval.cword))
     213           0 :                             tok1 = T_DATUM;
     214             :                         else
     215           0 :                             tok1 = T_CWORD;
     216             :                         /* Adjust token length to include A.B */
     217           0 :                         aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
     218             :                     }
     219             :                 }
     220             :                 else
     221             :                 {
     222             :                     /* not A.B.C, so just process A.B */
     223        8428 :                     push_back_token(tok4, &aux4, yyscanner);
     224        8428 :                     if (plpgsql_parse_dblword(aux1.lval.str,
     225             :                                               aux3.lval.str,
     226             :                                               &aux1.lval.wdatum,
     227             :                                               &aux1.lval.cword))
     228        7436 :                         tok1 = T_DATUM;
     229             :                     else
     230         992 :                         tok1 = T_CWORD;
     231             :                     /* Adjust token length to include A.B */
     232        8428 :                     aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
     233             :                 }
     234             :             }
     235             :             else
     236             :             {
     237             :                 /* not A.B, so just process A */
     238         202 :                 push_back_token(tok3, &aux3, yyscanner);
     239         202 :                 push_back_token(tok2, &aux2, yyscanner);
     240         202 :                 if (plpgsql_parse_word(aux1.lval.str,
     241         202 :                                        yyextra->core_yy_extra.scanbuf + aux1.lloc,
     242             :                                        true,
     243             :                                        &aux1.lval.wdatum,
     244             :                                        &aux1.lval.word))
     245           0 :                     tok1 = T_DATUM;
     246         404 :                 else if (!aux1.lval.word.quoted &&
     247         202 :                          (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
     248             :                                                     &UnreservedPLKeywords)) >= 0)
     249             :                 {
     250           0 :                     aux1.lval.keyword = GetScanKeyword(kwnum,
     251             :                                                        &UnreservedPLKeywords);
     252           0 :                     tok1 = UnreservedPLKeywordTokens[kwnum];
     253             :                 }
     254             :                 else
     255         202 :                     tok1 = T_WORD;
     256             :             }
     257             :         }
     258             :         else
     259             :         {
     260             :             /* not A.B, so just process A */
     261      130912 :             push_back_token(tok2, &aux2, yyscanner);
     262             : 
     263             :             /*
     264             :              * See if it matches a variable name, except in the context where
     265             :              * we are at start of statement and the next token isn't
     266             :              * assignment or '['.  In that case, it couldn't validly be a
     267             :              * variable name, and skipping the lookup allows variable names to
     268             :              * be used that would conflict with plpgsql or core keywords that
     269             :              * introduce statements (e.g., "comment").  Without this special
     270             :              * logic, every statement-introducing keyword would effectively be
     271             :              * reserved in PL/pgSQL, which would be unpleasant.
     272             :              *
     273             :              * If it isn't a variable name, try to match against unreserved
     274             :              * plpgsql keywords.  If not one of those either, it's T_WORD.
     275             :              *
     276             :              * Note: we must call plpgsql_parse_word even if we don't want to
     277             :              * do variable lookup, because it sets up aux1.lval.word for the
     278             :              * non-variable cases.
     279             :              */
     280      130912 :             if (plpgsql_parse_word(aux1.lval.str,
     281      130912 :                                    yyextra->core_yy_extra.scanbuf + aux1.lloc,
     282      162624 :                                    (!AT_STMT_START(yyextra->plpgsql_yytoken) ||
     283       31712 :                                     (tok2 == '=' || tok2 == COLON_EQUALS ||
     284             :                                      tok2 == '[')),
     285             :                                    &aux1.lval.wdatum,
     286             :                                    &aux1.lval.word))
     287       15962 :                 tok1 = T_DATUM;
     288      229876 :             else if (!aux1.lval.word.quoted &&
     289      114926 :                      (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
     290             :                                                 &UnreservedPLKeywords)) >= 0)
     291             :             {
     292       33724 :                 aux1.lval.keyword = GetScanKeyword(kwnum,
     293             :                                                    &UnreservedPLKeywords);
     294       33724 :                 tok1 = UnreservedPLKeywordTokens[kwnum];
     295             :             }
     296             :             else
     297       81226 :                 tok1 = T_WORD;
     298             :         }
     299             :     }
     300             :     else
     301             :     {
     302             :         /*
     303             :          * Not a potential plpgsql variable name, just return the data.
     304             :          *
     305             :          * Note that we also come through here if the grammar pushed back a
     306             :          * T_DATUM, T_CWORD, T_WORD, or unreserved-keyword token returned by a
     307             :          * previous lookup cycle; thus, pushbacks do not incur extra lookup
     308             :          * work, since we'll never do the above code twice for the same token.
     309             :          * This property also makes it safe to rely on the old value of
     310             :          * plpgsql_yytoken in the is-this-start-of-statement test above.
     311             :          */
     312             :     }
     313             : 
     314      416282 :     *yylvalp = aux1.lval;
     315      416282 :     *yyllocp = aux1.lloc;
     316      416282 :     yyextra->plpgsql_yyleng = aux1.leng;
     317      416282 :     yyextra->plpgsql_yytoken = tok1;
     318      416282 :     return tok1;
     319             : }
     320             : 
     321             : /*
     322             :  * Return the length of the token last returned by plpgsql_yylex().
     323             :  *
     324             :  * In the case of compound tokens, the length includes all the parts.
     325             :  */
     326             : int
     327      130736 : plpgsql_token_length(yyscan_t yyscanner)
     328             : {
     329      130736 :     return yyextra->plpgsql_yyleng;
     330             : }
     331             : 
     332             : /*
     333             :  * Internal yylex function.  This wraps the core lexer and adds one feature:
     334             :  * a token pushback stack.  We also make a couple of trivial single-token
     335             :  * translations from what the core lexer does to what we want, in particular
     336             :  * interfacing from the core_YYSTYPE to YYSTYPE union.
     337             :  */
     338             : static int
     339      579470 : internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner)
     340             : {
     341             :     int         token;
     342             :     const char *yytext;
     343             : 
     344      579470 :     if (yyextra->num_pushbacks > 0)
     345             :     {
     346      181448 :         yyextra->num_pushbacks--;
     347      181448 :         token = yyextra->pushback_token[yyextra->num_pushbacks];
     348      181448 :         *auxdata = yyextra->pushback_auxdata[yyextra->num_pushbacks];
     349             :     }
     350             :     else
     351             :     {
     352      398022 :         token = core_yylex(&auxdata->lval.core_yystype,
     353             :                            &auxdata->lloc,
     354             :                            yyscanner);
     355             : 
     356             :         /* remember the length of yytext before it gets changed */
     357      398022 :         yytext = yyextra->core_yy_extra.scanbuf + auxdata->lloc;
     358      398022 :         auxdata->leng = strlen(yytext);
     359             : 
     360             :         /* Check for << >> and #, which the core considers operators */
     361      398022 :         if (token == Op)
     362             :         {
     363        2818 :             if (strcmp(auxdata->lval.str, "<<") == 0)
     364         100 :                 token = LESS_LESS;
     365        2718 :             else if (strcmp(auxdata->lval.str, ">>") == 0)
     366          92 :                 token = GREATER_GREATER;
     367        2626 :             else if (strcmp(auxdata->lval.str, "#") == 0)
     368          24 :                 token = '#';
     369             :         }
     370             : 
     371             :         /* The core returns PARAM as ival, but we treat it like IDENT */
     372      395204 :         else if (token == PARAM)
     373             :         {
     374        1638 :             auxdata->lval.str = pstrdup(yytext);
     375             :         }
     376             :     }
     377             : 
     378      579470 :     return token;
     379             : }
     380             : 
     381             : /*
     382             :  * Push back a token to be re-read by next internal_yylex() call.
     383             :  */
     384             : static void
     385      181522 : push_back_token(int token, TokenAuxData *auxdata, yyscan_t yyscanner)
     386             : {
     387      181522 :     if (yyextra->num_pushbacks >= MAX_PUSHBACKS)
     388           0 :         elog(ERROR, "too many tokens pushed back");
     389      181522 :     yyextra->pushback_token[yyextra->num_pushbacks] = token;
     390      181522 :     yyextra->pushback_auxdata[yyextra->num_pushbacks] = *auxdata;
     391      181522 :     yyextra->num_pushbacks++;
     392      181522 : }
     393             : 
     394             : /*
     395             :  * Push back a single token to be re-read by next plpgsql_yylex() call.
     396             :  *
     397             :  * NOTE: this does not cause yylval or yylloc to "back up".  Also, it
     398             :  * is not a good idea to push back a token code other than what you read.
     399             :  */
     400             : void
     401       35430 : plpgsql_push_back_token(int token, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
     402             : {
     403             :     TokenAuxData auxdata;
     404             : 
     405       35430 :     auxdata.lval = *yylvalp;
     406       35430 :     auxdata.lloc = *yyllocp;
     407       35430 :     auxdata.leng = yyextra->plpgsql_yyleng;
     408       35430 :     push_back_token(token, &auxdata, yyscanner);
     409       35430 : }
     410             : 
     411             : /*
     412             :  * Tell whether a token is an unreserved keyword.
     413             :  *
     414             :  * (If it is, its lowercased form was returned as the token value, so we
     415             :  * do not need to offer that data here.)
     416             :  */
     417             : bool
     418          76 : plpgsql_token_is_unreserved_keyword(int token)
     419             : {
     420             :     int         i;
     421             : 
     422        6396 :     for (i = 0; i < lengthof(UnreservedPLKeywordTokens); i++)
     423             :     {
     424        6324 :         if (UnreservedPLKeywordTokens[i] == token)
     425           4 :             return true;
     426             :     }
     427          72 :     return false;
     428             : }
     429             : 
     430             : /*
     431             :  * Append the function text starting at startlocation and extending to
     432             :  * (not including) endlocation onto the existing contents of "buf".
     433             :  */
     434             : void
     435       52472 : plpgsql_append_source_text(StringInfo buf,
     436             :                            int startlocation, int endlocation,
     437             :                            yyscan_t yyscanner)
     438             : {
     439             :     Assert(startlocation <= endlocation);
     440       52472 :     appendBinaryStringInfo(buf, yyextra->scanorig + startlocation,
     441             :                            endlocation - startlocation);
     442       52472 : }
     443             : 
     444             : /*
     445             :  * Peek one token ahead in the input stream.  Only the token code is
     446             :  * made available, not any of the auxiliary info such as location.
     447             :  *
     448             :  * NB: no variable or unreserved keyword lookup is performed here, they will
     449             :  * be returned as IDENT. Reserved keywords are resolved as usual.
     450             :  */
     451             : int
     452        6060 : plpgsql_peek(yyscan_t yyscanner)
     453             : {
     454             :     int         tok1;
     455             :     TokenAuxData aux1;
     456             : 
     457        6060 :     tok1 = internal_yylex(&aux1, yyscanner);
     458        6060 :     push_back_token(tok1, &aux1, yyscanner);
     459        6060 :     return tok1;
     460             : }
     461             : 
     462             : /*
     463             :  * Peek two tokens ahead in the input stream. The first token and its
     464             :  * location in the query are returned in *tok1_p and *tok1_loc, second token
     465             :  * and its location in *tok2_p and *tok2_loc.
     466             :  *
     467             :  * NB: no variable or unreserved keyword lookup is performed here, they will
     468             :  * be returned as IDENT. Reserved keywords are resolved as usual.
     469             :  */
     470             : void
     471         144 : plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, int *tok2_loc, yyscan_t yyscanner)
     472             : {
     473             :     int         tok1,
     474             :                 tok2;
     475             :     TokenAuxData aux1,
     476             :                 aux2;
     477             : 
     478         144 :     tok1 = internal_yylex(&aux1, yyscanner);
     479         144 :     tok2 = internal_yylex(&aux2, yyscanner);
     480             : 
     481         144 :     *tok1_p = tok1;
     482         144 :     if (tok1_loc)
     483         144 :         *tok1_loc = aux1.lloc;
     484         144 :     *tok2_p = tok2;
     485         144 :     if (tok2_loc)
     486           0 :         *tok2_loc = aux2.lloc;
     487             : 
     488         144 :     push_back_token(tok2, &aux2, yyscanner);
     489         144 :     push_back_token(tok1, &aux1, yyscanner);
     490         144 : }
     491             : 
     492             : /*
     493             :  * plpgsql_scanner_errposition
     494             :  *      Report an error cursor position, if possible.
     495             :  *
     496             :  * This is expected to be used within an ereport() call.  The return value
     497             :  * is a dummy (always 0, in fact).
     498             :  *
     499             :  * Note that this can only be used for messages emitted during initial
     500             :  * parsing of a plpgsql function, since it requires the scanorig string
     501             :  * to still be available.
     502             :  */
     503             : int
     504         174 : plpgsql_scanner_errposition(int location, yyscan_t yyscanner)
     505             : {
     506             :     int         pos;
     507             : 
     508         174 :     if (location < 0 || yyextra->scanorig == NULL)
     509           0 :         return 0;               /* no-op if location is unknown */
     510             : 
     511             :     /* Convert byte offset to character number */
     512         174 :     pos = pg_mbstrlen_with_len(yyextra->scanorig, location) + 1;
     513             :     /* And pass it to the ereport mechanism */
     514         174 :     (void) internalerrposition(pos);
     515             :     /* Also pass the function body string */
     516         174 :     return internalerrquery(yyextra->scanorig);
     517             : }
     518             : 
     519             : /*
     520             :  * plpgsql_yyerror
     521             :  *      Report a lexer or grammar error.
     522             :  *
     523             :  * The message's cursor position refers to the current token (the one
     524             :  * last returned by plpgsql_yylex()).
     525             :  * This is OK for syntax error messages from the Bison parser, because Bison
     526             :  * parsers report error as soon as the first unparsable token is reached.
     527             :  * Beware of using yyerror for other purposes, as the cursor position might
     528             :  * be misleading!
     529             :  *
     530             :  * (The second argument is enforced by Bison to match the second argument of
     531             :  * yyparse(), but it is not used here.)
     532             :  */
     533             : void
     534           8 : plpgsql_yyerror(YYLTYPE *yyllocp, PLpgSQL_stmt_block **plpgsql_parse_result_p, yyscan_t yyscanner, const char *message)
     535             : {
     536           8 :     char       *yytext = yyextra->core_yy_extra.scanbuf + *yyllocp;
     537             : 
     538           8 :     if (*yytext == '\0')
     539             :     {
     540           0 :         ereport(ERROR,
     541             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     542             :         /* translator: %s is typically the translation of "syntax error" */
     543             :                  errmsg("%s at end of input", _(message)),
     544             :                  plpgsql_scanner_errposition(*yyllocp, yyscanner)));
     545             :     }
     546             :     else
     547             :     {
     548             :         /*
     549             :          * If we have done any lookahead then flex will have restored the
     550             :          * character after the end-of-token.  Zap it again so that we report
     551             :          * only the single token here.  This modifies scanbuf but we no longer
     552             :          * care about that.
     553             :          */
     554           8 :         yytext[yyextra->plpgsql_yyleng] = '\0';
     555             : 
     556           8 :         ereport(ERROR,
     557             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     558             :         /* translator: first %s is typically the translation of "syntax error" */
     559             :                  errmsg("%s at or near \"%s\"", _(message), yytext),
     560             :                  plpgsql_scanner_errposition(*yyllocp, yyscanner)));
     561             :     }
     562             : }
     563             : 
     564             : /*
     565             :  * Given a location (a byte offset in the function source text),
     566             :  * return a line number.
     567             :  *
     568             :  * We expect that this is typically called for a sequence of increasing
     569             :  * location values, so optimize accordingly by tracking the endpoints
     570             :  * of the "current" line.
     571             :  */
     572             : int
     573       60376 : plpgsql_location_to_lineno(int location, yyscan_t yyscanner)
     574             : {
     575             :     const char *loc;
     576             : 
     577       60376 :     if (location < 0 || yyextra->scanorig == NULL)
     578           0 :         return 0;               /* garbage in, garbage out */
     579       60376 :     loc = yyextra->scanorig + location;
     580             : 
     581             :     /* be correct, but not fast, if input location goes backwards */
     582       60376 :     if (loc < yyextra->cur_line_start)
     583       17528 :         location_lineno_init(yyscanner);
     584             : 
     585      276362 :     while (yyextra->cur_line_end != NULL && loc > yyextra->cur_line_end)
     586             :     {
     587      215986 :         yyextra->cur_line_start = yyextra->cur_line_end + 1;
     588      215986 :         yyextra->cur_line_num++;
     589      215986 :         yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
     590             :     }
     591             : 
     592       60376 :     return yyextra->cur_line_num;
     593             : }
     594             : 
     595             : /* initialize or reset the state for plpgsql_location_to_lineno */
     596             : static void
     597       27306 : location_lineno_init(yyscan_t yyscanner)
     598             : {
     599       27306 :     yyextra->cur_line_start = yyextra->scanorig;
     600       27306 :     yyextra->cur_line_num = 1;
     601             : 
     602       27306 :     yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
     603       27306 : }
     604             : 
     605             : /* return the most recently computed lineno */
     606             : int
     607          60 : plpgsql_latest_lineno(yyscan_t yyscanner)
     608             : {
     609          60 :     return yyextra->cur_line_num;
     610             : }
     611             : 
     612             : 
     613             : /*
     614             :  * Called before any actual parsing is done
     615             :  *
     616             :  * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
     617             :  * Although it is not fed directly to flex, we need the original string
     618             :  * to cite in error messages.
     619             :  */
     620             : yyscan_t
     621        9778 : plpgsql_scanner_init(const char *str)
     622             : {
     623             :     yyscan_t    yyscanner;
     624        9778 :     struct plpgsql_yy_extra_type *yyext = palloc0_object(struct plpgsql_yy_extra_type);
     625             : 
     626             :     /* Start up the core scanner */
     627        9778 :     yyscanner = scanner_init(str, (core_yy_extra_type *) yyext,
     628             :                              &ReservedPLKeywords, ReservedPLKeywordTokens);
     629             : 
     630             :     /*
     631             :      * scanorig points to the original string, which unlike the scanner's
     632             :      * scanbuf won't be modified on-the-fly by flex.  Notice that although
     633             :      * yytext points into scanbuf, we rely on being able to apply locations
     634             :      * (offsets from string start) to scanorig as well.
     635             :      */
     636        9778 :     yyext->scanorig = str;
     637             : 
     638             :     /* Other setup */
     639        9778 :     plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
     640        9778 :     yyext->plpgsql_yytoken = 0;
     641             : 
     642        9778 :     yyext->num_pushbacks = 0;
     643             : 
     644        9778 :     location_lineno_init(yyscanner);
     645             : 
     646        9778 :     return yyscanner;
     647             : }
     648             : 
     649             : /*
     650             :  * Called after parsing is done to clean up after plpgsql_scanner_init()
     651             :  */
     652             : void
     653        9600 : plpgsql_scanner_finish(yyscan_t yyscanner)
     654             : {
     655             :     /* release storage */
     656        9600 :     scanner_finish(yyscanner);
     657        9600 : }

Generated by: LCOV version 1.16