LCOV - code coverage report
Current view: top level - src/pl/plpgsql/src - pl_scanner.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19beta1 Lines: 90.8 % 152 138
Test Date: 2026-06-11 06:16:27 Functions: 100.0 % 16 16
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       265060 : plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
     161              : {
     162              :     int         tok1;
     163              :     TokenAuxData aux1;
     164              :     int         kwnum;
     165              : 
     166       265060 :     tok1 = internal_yylex(&aux1, yyscanner);
     167       265060 :     if (tok1 == IDENT || tok1 == PARAM)
     168              :     {
     169              :         int         tok2;
     170              :         TokenAuxData aux2;
     171              : 
     172        88549 :         tok2 = internal_yylex(&aux2, yyscanner);
     173        88549 :         if (tok2 == '.')
     174              :         {
     175              :             int         tok3;
     176              :             TokenAuxData aux3;
     177              : 
     178         5817 :             tok3 = internal_yylex(&aux3, yyscanner);
     179         5817 :             if (tok3 == IDENT)
     180              :             {
     181              :                 int         tok4;
     182              :                 TokenAuxData aux4;
     183              : 
     184         5687 :                 tok4 = internal_yylex(&aux4, yyscanner);
     185         5687 :                 if (tok4 == '.')
     186              :                 {
     187              :                     int         tok5;
     188              :                     TokenAuxData aux5;
     189              : 
     190           33 :                     tok5 = internal_yylex(&aux5, yyscanner);
     191           33 :                     if (tok5 == IDENT)
     192              :                     {
     193           33 :                         if (plpgsql_parse_tripword(aux1.lval.str,
     194              :                                                    aux3.lval.str,
     195              :                                                    aux5.lval.str,
     196              :                                                    &aux1.lval.wdatum,
     197              :                                                    &aux1.lval.cword))
     198           23 :                             tok1 = T_DATUM;
     199              :                         else
     200           10 :                             tok1 = T_CWORD;
     201              :                         /* Adjust token length to include A.B.C */
     202           33 :                         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         5654 :                     push_back_token(tok4, &aux4, yyscanner);
     224         5654 :                     if (plpgsql_parse_dblword(aux1.lval.str,
     225              :                                               aux3.lval.str,
     226              :                                               &aux1.lval.wdatum,
     227              :                                               &aux1.lval.cword))
     228         5041 :                         tok1 = T_DATUM;
     229              :                     else
     230          613 :                         tok1 = T_CWORD;
     231              :                     /* Adjust token length to include A.B */
     232         5654 :                     aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
     233              :                 }
     234              :             }
     235              :             else
     236              :             {
     237              :                 /* not A.B, so just process A */
     238          130 :                 push_back_token(tok3, &aux3, yyscanner);
     239          130 :                 push_back_token(tok2, &aux2, yyscanner);
     240          130 :                 if (plpgsql_parse_word(aux1.lval.str,
     241          130 :                                        yyextra->core_yy_extra.scanbuf + aux1.lloc,
     242              :                                        true,
     243              :                                        &aux1.lval.wdatum,
     244              :                                        &aux1.lval.word))
     245            0 :                     tok1 = T_DATUM;
     246          260 :                 else if (!aux1.lval.word.quoted &&
     247          130 :                          (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          130 :                     tok1 = T_WORD;
     256              :             }
     257              :         }
     258              :         else
     259              :         {
     260              :             /* not A.B, so just process A */
     261        82732 :             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        82732 :             if (plpgsql_parse_word(aux1.lval.str,
     281        82732 :                                    yyextra->core_yy_extra.scanbuf + aux1.lloc,
     282       102903 :                                    (!AT_STMT_START(yyextra->plpgsql_yytoken) ||
     283        20171 :                                     (tok2 == '=' || tok2 == COLON_EQUALS ||
     284              :                                      tok2 == '[')),
     285              :                                    &aux1.lval.wdatum,
     286        82732 :                                    &aux1.lval.word))
     287         9716 :                 tok1 = T_DATUM;
     288       146016 :             else if (!aux1.lval.word.quoted &&
     289        73000 :                      (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
     290              :                                                 &UnreservedPLKeywords)) >= 0)
     291              :             {
     292        21813 :                 aux1.lval.keyword = GetScanKeyword(kwnum,
     293              :                                                    &UnreservedPLKeywords);
     294        21813 :                 tok1 = UnreservedPLKeywordTokens[kwnum];
     295              :             }
     296              :             else
     297        51203 :                 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       265060 :     *yylvalp = aux1.lval;
     315       265060 :     *yyllocp = aux1.lloc;
     316       265060 :     yyextra->plpgsql_yyleng = aux1.leng;
     317       265060 :     yyextra->plpgsql_yytoken = tok1;
     318       265060 :     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        84624 : plpgsql_token_length(yyscan_t yyscanner)
     328              : {
     329        84624 :     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       369283 : internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner)
     340              : {
     341              :     int         token;
     342              :     const char *yytext;
     343              : 
     344       369283 :     if (yyextra->num_pushbacks > 0)
     345              :     {
     346       114419 :         yyextra->num_pushbacks--;
     347       114419 :         token = yyextra->pushback_token[yyextra->num_pushbacks];
     348       114419 :         *auxdata = yyextra->pushback_auxdata[yyextra->num_pushbacks];
     349              :     }
     350              :     else
     351              :     {
     352       254864 :         token = core_yylex(&auxdata->lval.core_yystype,
     353              :                            &auxdata->lloc,
     354              :                            yyscanner);
     355              : 
     356              :         /* remember the length of yytext before it gets changed */
     357       254864 :         yytext = yyextra->core_yy_extra.scanbuf + auxdata->lloc;
     358       254864 :         auxdata->leng = strlen(yytext);
     359              : 
     360              :         /* Check for << >> and #, which the core considers operators */
     361       254864 :         if (token == Op)
     362              :         {
     363         1674 :             if (strcmp(auxdata->lval.str, "<<") == 0)
     364           54 :                 token = LESS_LESS;
     365         1620 :             else if (strcmp(auxdata->lval.str, ">>") == 0)
     366           50 :                 token = GREATER_GREATER;
     367         1570 :             else if (strcmp(auxdata->lval.str, "#") == 0)
     368           16 :                 token = '#';
     369              :         }
     370              : 
     371              :         /* The core returns PARAM as ival, but we treat it like IDENT */
     372       253190 :         else if (token == PARAM)
     373              :         {
     374         1085 :             auxdata->lval.str = pstrdup(yytext);
     375              :         }
     376              :     }
     377              : 
     378       369283 :     return token;
     379              : }
     380              : 
     381              : /*
     382              :  * Push back a token to be re-read by next internal_yylex() call.
     383              :  */
     384              : static void
     385       114462 : push_back_token(int token, TokenAuxData *auxdata, yyscan_t yyscanner)
     386              : {
     387       114462 :     if (yyextra->num_pushbacks >= MAX_PUSHBACKS)
     388            0 :         elog(ERROR, "too many tokens pushed back");
     389       114462 :     yyextra->pushback_token[yyextra->num_pushbacks] = token;
     390       114462 :     yyextra->pushback_auxdata[yyextra->num_pushbacks] = *auxdata;
     391       114462 :     yyextra->num_pushbacks++;
     392       114462 : }
     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        21679 : plpgsql_push_back_token(int token, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
     402              : {
     403              :     TokenAuxData auxdata;
     404              : 
     405        21679 :     auxdata.lval = *yylvalp;
     406        21679 :     auxdata.lloc = *yyllocp;
     407        21679 :     auxdata.leng = yyextra->plpgsql_yyleng;
     408        21679 :     push_back_token(token, &auxdata, yyscanner);
     409        21679 : }
     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           43 : plpgsql_token_is_unreserved_keyword(int token)
     419              : {
     420              :     int         i;
     421              : 
     422         3628 :     for (i = 0; i < lengthof(UnreservedPLKeywordTokens); i++)
     423              :     {
     424         3587 :         if (UnreservedPLKeywordTokens[i] == token)
     425            2 :             return true;
     426              :     }
     427           41 :     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        32512 : plpgsql_append_source_text(StringInfo buf,
     436              :                            int startlocation, int endlocation,
     437              :                            yyscan_t yyscanner)
     438              : {
     439              :     Assert(startlocation <= endlocation);
     440        32512 :     appendBinaryStringInfo(buf, yyextra->scanorig + startlocation,
     441              :                            endlocation - startlocation);
     442        32512 : }
     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         3945 : plpgsql_peek(yyscan_t yyscanner)
     453              : {
     454              :     int         tok1;
     455              :     TokenAuxData aux1;
     456              : 
     457         3945 :     tok1 = internal_yylex(&aux1, yyscanner);
     458         3945 :     push_back_token(tok1, &aux1, yyscanner);
     459         3945 :     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           96 : 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           96 :     tok1 = internal_yylex(&aux1, yyscanner);
     479           96 :     tok2 = internal_yylex(&aux2, yyscanner);
     480              : 
     481           96 :     *tok1_p = tok1;
     482           96 :     if (tok1_loc)
     483           96 :         *tok1_loc = aux1.lloc;
     484           96 :     *tok2_p = tok2;
     485           96 :     if (tok2_loc)
     486            0 :         *tok2_loc = aux2.lloc;
     487              : 
     488           96 :     push_back_token(tok2, &aux2, yyscanner);
     489           96 :     push_back_token(tok1, &aux1, yyscanner);
     490           96 : }
     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          105 : plpgsql_scanner_errposition(int location, yyscan_t yyscanner)
     505              : {
     506              :     int         pos;
     507              : 
     508          105 :     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          105 :     pos = pg_mbstrlen_with_len(yyextra->scanorig, location) + 1;
     513              :     /* And pass it to the ereport mechanism */
     514          105 :     (void) internalerrposition(pos);
     515              :     /* Also pass the function body string */
     516          105 :     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            5 : plpgsql_yyerror(YYLTYPE *yyllocp, PLpgSQL_stmt_block **plpgsql_parse_result_p, yyscan_t yyscanner, const char *message)
     535              : {
     536            5 :     char       *yytext = yyextra->core_yy_extra.scanbuf + *yyllocp;
     537              : 
     538            5 :     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            5 :         yytext[yyextra->plpgsql_yyleng] = '\0';
     555              : 
     556            5 :         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        37651 : plpgsql_location_to_lineno(int location, yyscan_t yyscanner)
     574              : {
     575              :     const char *loc;
     576              : 
     577        37651 :     if (location < 0 || yyextra->scanorig == NULL)
     578            0 :         return 0;               /* garbage in, garbage out */
     579        37651 :     loc = yyextra->scanorig + location;
     580              : 
     581              :     /* be correct, but not fast, if input location goes backwards */
     582        37651 :     if (loc < yyextra->cur_line_start)
     583        11207 :         location_lineno_init(yyscanner);
     584              : 
     585       176953 :     while (yyextra->cur_line_end != NULL && loc > yyextra->cur_line_end)
     586              :     {
     587       139302 :         yyextra->cur_line_start = yyextra->cur_line_end + 1;
     588       139302 :         yyextra->cur_line_num++;
     589       139302 :         yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
     590              :     }
     591              : 
     592        37651 :     return yyextra->cur_line_num;
     593              : }
     594              : 
     595              : /* initialize or reset the state for plpgsql_location_to_lineno */
     596              : static void
     597        17299 : location_lineno_init(yyscan_t yyscanner)
     598              : {
     599        17299 :     yyextra->cur_line_start = yyextra->scanorig;
     600        17299 :     yyextra->cur_line_num = 1;
     601              : 
     602        17299 :     yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
     603        17299 : }
     604              : 
     605              : /* return the most recently computed lineno */
     606              : int
     607           35 : plpgsql_latest_lineno(yyscan_t yyscanner)
     608              : {
     609           35 :     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         6092 : plpgsql_scanner_init(const char *str)
     622              : {
     623              :     yyscan_t    yyscanner;
     624         6092 :     struct plpgsql_yy_extra_type *yyext = palloc0_object(struct plpgsql_yy_extra_type);
     625              : 
     626              :     /* Start up the core scanner */
     627         6092 :     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         6092 :     yyext->scanorig = str;
     637              : 
     638              :     /* Other setup */
     639         6092 :     plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
     640         6092 :     yyext->plpgsql_yytoken = 0;
     641              : 
     642         6092 :     yyext->num_pushbacks = 0;
     643              : 
     644         6092 :     location_lineno_init(yyscanner);
     645              : 
     646         6092 :     return yyscanner;
     647              : }
     648              : 
     649              : /*
     650              :  * Called after parsing is done to clean up after plpgsql_scanner_init()
     651              :  */
     652              : void
     653         5985 : plpgsql_scanner_finish(yyscan_t yyscanner)
     654              : {
     655              :     /* release storage */
     656         5985 :     scanner_finish(yyscanner);
     657         5985 : }
        

Generated by: LCOV version 2.0-1