src/backend/parser/scan.l --> lexical scanner for PostgreSQL 該文件中的規則需要和psql lexer一致。Lex用來生成掃描器,其工作是識別一個一個的模式,比如數字、字符串、特殊符號等,然后將其傳給Yacc。
定義段
定義段包括文字塊(literal block)、定義(definition)、內部表聲明(internal table declaration)、起始條件(start condition)和轉換(translation)。
1 %{ 2 ... 3 #include "postgres.h" 4 #include <unistd.h> 5 #include "parser/gramparse.h" 6 #include "parser/keywords.h" 7 #include "parser/scansup.h" 8 #include "mb/pg_wchar.h" 9 ... 10 static unsigned char unescape_single_char(unsigned char c); 11 %}
定義段包含了需要引入的頭文件,定義的全局變量,靜態變量,聲明函數,聲明靜態函數和宏定義。這里主要關注以下幾種定義:
GUC變量。 這直接違反了gram.y開頭給出的警告,即flex / bison代碼不得依賴於任何GUC變量; 因此,更改其值可能會導致非常不直觀的行為。 但是,我們必須將它作為短期的事情來忍受,直到完成對SQL標准字符串語法的轉換為止。
1 int backslash_quote = BACKSLASH_QUOTE_SAFE_ENCODING; 2 bool escape_string_warning = true; 3 bool standard_conforming_strings = false; 4 static bool warn_on_first_escape; 5 static bool saw_non_ascii = false;
當需要多個規則來解析單個文字時,literalbuf用於累積文字值。調用startlit將緩沖區重置為空,調用addlit添加文本。 請注意,緩沖區是palloc分配的,並在每個解析周期重新開始生命。
1 static char *literalbuf; /* expandable buffer */ 2 static int literallen; /* actual current length */ 3 static int literalalloc; /* current allocated buffer size */ 4 #define startlit() (literalbuf[0] = '\0', literallen = 0) 5 static void addlit(char *ytext, int yleng); 6 static void addlitchar(unsigned char ychar); 7 static char *litbufdup(void); 8 static char *litbuf_udeescape(unsigned char escape); 9 #define lexer_errposition() scanner_errposition(yylloc) 10 static void check_escape_warning(void); 11 static void check_string_escape_warning(unsigned char ychar);
每次對yylex的調用都必須將yylloc設置為找到的標記的位置(表示為距輸入文本開頭的字節偏移量)。 當我們解析需要多個詞法分析器規則來處理的令牌時,應在第一個此類規則中完成,否則yylloc將指向令牌的中間。
1 #define SET_YYLLOC() (yylloc = yytext - scanbuf)
處理到詞法分析器內部使用的緩沖區
1 static YY_BUFFER_STATE scanbufhandle; 2 static char *scanbuf;
flex的選項(%option)影響最終生成的詞法分析器的屬性和行為。這些選項可以在運行flex命令時在終端輸入,也可以在.l文件中使用%option指定。option的主要分類:Options for Specifying Filenames、Options Affecting Scanner Behavior、Code-Level And API Options、Options for Scanner Speed and Size、Debugging Options、Miscellaneous Options
下面說明幾個常用的選項
1. Options for Specifying Filenames
--header-file=FILE,%option header-file="FILE":逗號前的用於終端輸入,逗號前后用於.l文件。該選項告訴flex生成名為"FILE"的頭文件,該文件包含XX.yy.c文件中的一些類型和定義。
-oFILE, --outfile=FILE, %option outfile="FILE":指明詞法分析源文件名,如果沒有指明該選項,那么生成的詞法分析源文件被命名XX.yy.c。
2. Options Affecting Scanner Behavior
-i, --case-insensitive, %option case-insensitive:忽略符號的大小寫,符號即人們要分析的各種字符。
-l, --lex-compat, %option lex-compat:最大程度兼容AT&T的flex實現。
-B, --batch, %option batch:關閉超前搜索。
-I, --interactive, %option interactive:打開超前搜索。
--default, %option default:使用默認規則,不明白默認規則是什么。。。
--stack, %option stack:激活開始條件棧。
--yylineno, %option yylineno:記錄符號所在行號。如果使用了%option lex-compat,則隱含地使用了該選項。
--yywrap, %option yywrap:noyywrap表示在該.l文件中不會調用yywrap(),而是假設生成的掃描器只掃描單個文件;%option yywrap自然與之相反
3. Code-Level And API Options
--bison-bridge, %option bison-bridge:生成的掃描器API能夠被bision調用。API為與bision兼容而作了些小改變。
-R, --reentrant, %option reentrant:生成可重用的掃描器API,這些API用於多線程環境。
-+, --c++, %option c++:如果沒有指定該選項,生成的掃描器.c文件是C語言格式的,指定后則生成C++文件。
--array, %option array:yytext的類型由char *變為數組。
--array, %option pointer:與--array, %option array相反。
-PPREFIX, --prefix=PREFIX, %option prefix="PREFIX":將flex中所有yy前綴改為PREFIX,例如指定%option prefix="foo"后,yytext變成footext,yylex變成foolex。
4. Options for Scanner Speed and Size
5. Debugging Options
-b, --backup, %option backup:生成備份信息文件lex.backup,包含一些需要備份的掃描器狀態信息和相關的輸入符號。
-d, --debug, %option debug:掃描器在debug模式下運行
1 %option 8bit 2 %option never-interactive 3 %option nodefault 4 %option noinput 5 %option nounput 6 %option noyywrap 7 %option prefix="base_yy"
下面是lex / flex規則行為的簡短描述。始終選擇與輸入字符串匹配的最長模式。對於等長模式,選擇規則列表中的第一個。INITIAL是所有非條件規則都適用的起始狀態。處於活動狀態時,排他狀態會更改解析規則。 處於排他狀態時,僅適用於為該狀態定義的那些規則。我們將獨占狀態用於加引號的字符串,擴展的注釋,並消除數字字符串的解析麻煩。
1 /* 2 * We use exclusive states for quoted strings, extended comments, 3 * and to eliminate parsing troubles for numeric strings. 4 * Exclusive states: 5 * <xb> bit string literal 6 * <xc> extended C-style comments 7 * <xd> delimited identifiers (double-quoted identifiers) 8 * <xh> hexadecimal numeric string 9 * <xq> standard quoted strings 10 * <xe> extended quoted strings (support backslash escape sequences) 11 * <xdolq> $foo$ quoted strings 12 * <xui> quoted identifier with Unicode escapes 13 * <xus> quoted string with Unicode escapes 14 */ 15 %x xb 16 %x xc 17 %x xd 18 %x xh 19 %x xe 20 %x xq 21 %x xdolq 22 %x xui 23 %x xus
為了使Windows和Mac客戶端以及Unix客戶端更加安全,我們接受\ n或\ r作為換行符。 DOS風格的\ r \ n序列將被視為兩個連續的換行符,但這不會引起任何問題。 以-開頭並擴展到下一個換行符的注釋被視為等效於單個空格字符。注意一點:如果在-后面沒有換行符,我們將把所有輸入內容作為注釋。 這是對的。 較舊的Postgres版本無法識別-如果輸入內容不以換行符結尾,則將其作為注釋。XXX也許\ f(換頁符)也應被視為換行符? 如果您更改了空白字符集,則為XXX,請修復scanner_isspace()以使其同意,另請參閱plpgsql lexer。
1 space [ \t\n\r\f] 2 horiz_space [ \t\f] 3 newline [\n\r] 4 non_newline [^\n\r] 5 comment ("--"{non_newline}*) 6 whitespace ({space}+|{comment})
SQL要求在空格中至少有一個換行符,以分隔要串聯的字符串文字。 傻了,但是我們要爭論誰呢? 請注意,{whitespace_with_newline}之后不應帶有*,而{whitespace}通常應帶有* ...
1 special_whitespace ({space}+|{comment}{newline}) 2 horiz_whitespace ({horiz_space}|{comment}) 3 whitespace_with_newline ({horiz_whitespace}*{newline}{special_whitespace}*)
為了確保{quotecontinue}可以被掃描而無需在完整模式不匹配的情況下進行備份,我們在{quotestop}中包含尾隨空格。 這匹配所有{quotecontinue}均不匹配的情況,除了{quote}后跟空格和僅一個“-”(不是兩個,將開始一個{comment})。 為了解決這個問題,我們有{quotefail}。 {quotestop}和{quotefail}的操作必須拋出超出引號本身的字符。
1 quote ' 2 quotestop {quote}{whitespace}* 3 quotecontinue {quote}{whitespace_with_newline}{quote} 4 quotefail {quote}{whitespace}*"-"
位串
誘使(tempting)的是僅在字符串中掃描那些允許的字符。 但是,如果字符串中包含非法字符,則會導致無聲地吞下字符。 例如,如果xbinside為[01],則B'ABCD'被解釋為零長度的字符串,而ABCD'將丟失! 最好向前傳遞字符串,並讓輸入例程驗證內容。
1 xbstart [bB]{quote} 2 xbinside [^']*
十六進制數字
1 xhstart [xX]{quote} 2 xhinside [^']*
國際字符
1 xnstart [nN]{quote}
帶引號的字符串,允許反斜杠轉義
1 xestart [eE]{quote} 2 xeinside [^\\']+ 3 xeescape [\\][^0-7] 4 xeoctesc [\\][0-7]{1,3} 5 xehexesc [\\]x[0-9A-Fa-f]{1,2}
擴展報價xqdouble實現嵌入式報價''''
xqstart {quote} xqdouble {quote}{quote} xqinside [^']+
$ foo $樣式引號(“美元引號”)用引號引起來的字符串以$ foo $開頭,其中“ foo”是標識符形式的可選字符串,但它可能不包含“ $”,並一直擴展到第一次出現 相同的字符串。 引號文本沒有處理。
{dolqfailed}是一條錯誤規則,可避免{dolqdelim}尾部的“ $”不匹配時避免掃描儀備份。
1 dolq_start [A-Za-z\200-\377_] 2 dolq_cont [A-Za-z\200-\377_0-9] 3 dolqdelim \$({dolq_start}{dolq_cont}*)?\$ 4 dolqfailed \${dolq_start}{dolq_cont}* 5 dolqinside [^$]+
雙引號
允許在標識符中嵌入空格和其他特殊字符。
1 dquote \" 2 xdstart {dquote} 3 xdstop {dquote} 4 xddouble {dquote}{dquote} 5 xdinside [^"]+
Unicode轉義
1 uescape [uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}[^']{quote}
錯誤規則,以避免備份
1 uescapefail ("-"|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*"-"|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}[^']|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*|[uU][eE][sS][cC][aA][pP]|[uU][eE][sS][cC][aA]|[uU][eE][sS][cC]|[uU][eE][sS]|[uU][eE]|[uU])
帶Unicode轉義的帶引號的標識符
1 xuistart [uU]&{dquote} 2 xuistop1 {dquote}{whitespace}*{uescapefail}? 3 xuistop2 {dquote}{whitespace}*{uescape}
帶Unicode轉義的帶引號的字符串
1 xusstart [uU]&{quote} 2 xusstop1 {quote}{whitespace}*{uescapefail}? 3 xusstop2 {quote}{whitespace}*{uescape}
錯誤規則,以避免備份
1 xufailed [uU]&
C風格評論
“擴展注釋”語法與允許的運算符語法非常相似。 這里最棘手的部分是讓lex識別以斜杠星號開頭的字符串作為注釋,當將其解釋為運算符時會產生更長的匹配---請記住lex會更喜歡更長的匹配! 另外,如果我們有類似plus-slash-star的內容,那么lex會認為這是3個字符的運算符,而我們希望將其視為+運算符和注釋開頭。
解決方案有兩個:
1.將{op_chars} *附加到xcstart,以便與{operator}匹配盡可能多的文本。 然后,決勝局(相同長度的第一個匹配規則)確保xcstart獲勝。 我們用yyless()放回了多余的東西,以防它包含一個星號斜線,以終止注釋。
2.在運算符規則中,檢查運算符內是否有斜杠星號,如果找到,請使用yyless()將其返回。 這樣可以解決加號-斜杠-星號的問題。
短划線注釋與運算符規則具有相似的交互作用。
1 xcstart \/\*{op_chars}* 2 xcstop \*+\/ 3 xcinside [^*/]+ 4 digit [0-9] 5 ident_start [A-Za-z\200-\377_] 6 ident_cont [A-Za-z\200-\377_0-9\$] 7 identifier {ident_start}{ident_cont}* 8 typecast "::"
“ self”是應作為單字符令牌返回的一組字符。 “ op_chars”是可以組成“ Op”令牌的一組字符,長度可以是一個或多個字符(但是,如果單個字符令牌出現在“ self”集中,則不應將其作為Op返回。 )。 請注意,這些集合重疊,但是每個集合都有一些不在另一個集合中的字符。 如果更改其中任何一個,請調整出現在“操作員”規則中的字符列表!
1 self [,()\[\].;\:\+\-\*\/\%\^\<\>\=] 2 op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=] 3 operator {op_chars}+
我們不再允許一元減號。 相反,我們將其分別傳遞給解析器。 在那里它通過doNegate()被強制-萊昂1999年8月20日
添加了{realfail1}和{realfail2}以防止在{real}規則完全不匹配時需要掃描程序備份。
integer {digit}+ decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*)) real ({integer}|{decimal})[Ee][-+]?{digit}+ realfail1 ({integer}|{decimal})[Ee] realfail2 ({integer}|{decimal})[Ee][-+] param \${integer} other .
以美元引用的字符串是完全不透明的,並且不會對其進行轉義。 其他帶引號的字符串必須允許一些特殊字符,例如單引號和換行符。
嵌入式單引號既以兩個相鄰的單引號“''”的SQL標准樣式實現,又以逸出引號“ \'”的Postgres / Java樣式實現。
其他嵌入的轉義字符會顯式匹配,並且從字符串中刪除前導反斜杠。
請注意,如上所述,xcstart必須出現在運算符之前! 同樣,空格(注釋)必須出現在運算符之前。
規則段
規則段包含模式行和C代碼。以空白開始的行或包圍在"%{"和"%}"中的內容是C代碼。以任何其他東西開始的行是模式行。C代碼被逐字拷貝到生成的C文件中。規則段開頭的行靠近生成的yylex()函數的開頭,並且應該是與模式相關的代碼所使用的變量聲明或掃描程序的初始化代碼。
當lex掃描程序運行時,它把輸入與規則段的模式進行匹配。每次發現一個匹配(被匹配的輸入稱為標記)時就執行與那種模式相關的C代碼。如果模式后面跟着一條豎線而不是C代碼,那么這個模式將使用與文件中的下一個模式相同的C代碼。當輸入字符不匹配模式時,詞法分析程序的動作就好像它匹配上了代碼為"ECHO;"的模式,ECHO將標記的拷貝寫到輸出。
1 {whitespace} { 2 /* ignore */ 3 } 4 5 {xcstart} { 6 /* Set location in case of syntax error in comment */ 7 SET_YYLLOC(); 8 xcdepth = 0; 9 BEGIN(xc); 10 /* Put back any characters past slash-star; see above */ 11 yyless(2); 12 } 13 14 <xc>{xcstart} { 15 xcdepth++; 16 /* Put back any characters past slash-star; see above */ 17 yyless(2); 18 } 19 ...
用戶子例程段
用戶子例程段的內容被lex逐字拷貝到C文件。這一部分通常包括從規則中調用的例程。
Scanner_errposition
如果可能,報告一個詞法分析器或語法錯誤的光標位置。預期將在ereport()調用中使用它。 返回值是一個虛擬值(實際上始終為0)。請注意,這只能用於原始解析期間發出的消息(本質上是scan.l和gram.y),因為它要求scanbuf仍然有效。
1 int scanner_errposition(int location) 2 { 3 int pos; 4 Assert(scanbuf != NULL); /* else called from wrong place */ 5 if (location < 0) 6 return 0; /* no-op if location is unknown */ 7 /* Convert byte offset to character number */ 8 pos = pg_mbstrlen_with_len(scanbuf, location) + 1; 9 /* And pass it to the ereport mechanism */ 10 return errposition(pos); 11 }
yyerror
報告詞法分析器或語法錯誤。
消息的光標位置標識了最近詞匯化的標記。 對於來自Bison解析器的語法錯誤消息,這是可以的,因為一旦到達第一個不可解析的令牌,Bison解析器就會報告錯誤。 注意不要將yyerror用於其他目的,因為光標位置可能會引起誤解!
void yyerror(const char *message){ const char *loc = scanbuf + yylloc; if (*loc == YY_END_OF_BUFFER_CHAR){ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: %s is typically the translation of "syntax error" */ errmsg("%s at end of input", _(message)), lexer_errposition())); }else{ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: first %s is typically the translation of "syntax error" */ errmsg("%s at or near \"%s\"", _(message), loc), lexer_errposition())); } }
在任何實際解析完成之前調用
1 void scanner_init(const char *str) { 2 Size slen = strlen(str); 3 /* Might be left over after ereport() */ 4 if (YY_CURRENT_BUFFER) 5 yy_delete_buffer(YY_CURRENT_BUFFER); 6 /* Make a scan buffer with special termination needed by flex.*/ 7 scanbuf = palloc(slen + 2); 8 memcpy(scanbuf, str, slen); 9 scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; 10 scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); 11 /* initialize literal buffer to a reasonable but expansible size */ 12 literalalloc = 1024; 13 literalbuf = (char *) palloc(literalalloc); 14 startlit(); 15 BEGIN(INITIAL); 16 }
解析完成后調用以在scanner_init()之后進行清理
1 void scanner_finish(void) { 2 yy_delete_buffer(scanbufhandle); 3 pfree(scanbuf); 4 scanbuf = NULL; 5 }
其他函數
1 static void addlit(char *ytext, int yleng) { 2 /* enlarge buffer if needed */ 3 if ((literallen+yleng) >= literalalloc){ 4 do { 5 literalalloc *= 2; 6 } while ((literallen+yleng) >= literalalloc); 7 literalbuf = (char *) repalloc(literalbuf, literalalloc); 8 } 9 /* append new data, add trailing null */ 10 memcpy(literalbuf+literallen, ytext, yleng); 11 literallen += yleng; 12 literalbuf[literallen] = '\0'; 13 } 14 15 static void addlitchar(unsigned char ychar) { 16 /* enlarge buffer if needed */ 17 if ((literallen+1) >= literalalloc){ 18 literalalloc *= 2; 19 literalbuf = (char *) repalloc(literalbuf, literalalloc); 20 } 21 /* append new data, add trailing null */ 22 literalbuf[literallen] = ychar; 23 literallen += 1; 24 literalbuf[literallen] = '\0'; 25 } 26 27 28 /* One might be tempted to write pstrdup(literalbuf) instead of this, but for long literals this is much faster because the length is already known. 29 */ 30 static char * litbufdup(void){ 31 char *new; 32 new = palloc(literallen + 1); 33 memcpy(new, literalbuf, literallen+1); 34 return new; 35 } 36 37 static int hexval(unsigned char c) { 38 if (c >= '0' && c <= '9') 39 return c - '0'; 40 if (c >= 'a' && c <= 'f') 41 return c - 'a' + 0xA; 42 if (c >= 'A' && c <= 'F') 43 return c - 'A' + 0xA; 44 elog(ERROR, "invalid hexadecimal digit"); 45 return 0; /* not reached */ 46 } 47 48 static void check_unicode_value(pg_wchar c, char * loc) { 49 if (GetDatabaseEncoding() == PG_UTF8) 50 return; 51 if (c > 0x7F) { 52 yylloc += (char *) loc - literalbuf + 3; /* 3 for U&" */ 53 yyerror("Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8"); 54 } 55 } 56 57 static char * litbuf_udeescape(unsigned char escape) { 58 char *new; 59 char *in, *out; 60 if (isxdigit(escape)|| escape == '+'|| escape == '\''|| escape == '"'|| scanner_isspace(escape)){ 61 yylloc += literallen + yyleng + 1; 62 yyerror("invalid Unicode escape character"); 63 } 64 /* This relies on the subtle assumption that a UTF-8 expansion cannot be longer than its escaped representation.*/ 65 new = palloc(literallen + 1); 66 in = literalbuf; 67 out = new; 68 while (*in){ 69 if (in[0] == escape){ 70 if (in[1] == escape){ 71 *out++ = escape; 72 in += 2; 73 }else if (isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4])){ 74 pg_wchar unicode = hexval(in[1]) * 16*16*16 + hexval(in[2]) * 16*16 + hexval(in[3]) * 16 + hexval(in[4]); 75 check_unicode_value(unicode, in); 76 unicode_to_utf8(unicode, (unsigned char *) out); 77 in += 5; 78 out += pg_mblen(out); 79 } 80 else if (in[1] == '+'&& isxdigit(in[2]) && isxdigit(in[3])&& isxdigit(in[4]) && isxdigit(in[5])&& isxdigit(in[6]) && isxdigit(in[7])){ 81 pg_wchar unicode = hexval(in[2]) * 16*16*16*16*16 + hexval(in[3]) * 16*16*16*16 + hexval(in[4]) * 16*16*16 82 + hexval(in[5]) * 16*16 + hexval(in[6]) * 16 + hexval(in[7]); 83 check_unicode_value(unicode, in); 84 unicode_to_utf8(unicode, (unsigned char *) out); 85 in += 8; 86 out += pg_mblen(out); 87 }else{ 88 yylloc += in - literalbuf + 3; /* 3 for U&" */ 89 yyerror("invalid Unicode escape value"); 90 } 91 }else 92 *out++ = *in++; 93 } 94 *out = '\0'; 95 /* We could skip pg_verifymbstr if we didn't process any non-7-bit-ASCII codes; but it's probably not worth the trouble, since this isn't likely to be a performance-critical path. */ 96 pg_verifymbstr(new, out - new, false); 97 return new; 98 } 99 100 static unsigned char unescape_single_char(unsigned char c) { 101 switch (c){ 102 case 'b': 103 return '\b'; 104 case 'f': 105 return '\f'; 106 case 'n': 107 return '\n'; 108 case 'r': 109 return '\r'; 110 case 't': 111 return '\t'; 112 default: 113 /* check for backslash followed by non-7-bit-ASCII */ 114 if (c == '\0' || IS_HIGHBIT_SET(c)) 115 saw_non_ascii = true; 116 return c; 117 } 118 } 119 120 static void check_string_escape_warning(unsigned char ychar) { 121 if (ychar == '\''){ 122 if (warn_on_first_escape && escape_string_warning) 123 ereport(WARNING,(errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 124 errmsg("nonstandard use of \\' in a string literal"), 125 errhint("Use '' to write quotes in strings, or use the escape string syntax (E'...')."), 126 lexer_errposition())); 127 warn_on_first_escape = false; /* warn only once per string */ 128 }else if (ychar == '\\'){ 129 if (warn_on_first_escape && escape_string_warning) 130 ereport(WARNING, 131 (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 132 errmsg("nonstandard use of \\\\ in a string literal"), 133 errhint("Use the escape string syntax for backslashes, e.g., E'\\\\'."), 134 lexer_errposition())); 135 warn_on_first_escape = false; /* warn only once per string */ 136 }else 137 check_escape_warning(); 138 } 139 140 static void check_escape_warning(void) { 141 if (warn_on_first_escape && escape_string_warning) 142 ereport(WARNING, 143 (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 144 errmsg("nonstandard use of escape in a string literal"), 145 errhint("Use the escape string syntax for escapes, e.g., E'\\r\\n'."), 146 lexer_errposition())); 147 warn_on_first_escape = false; /* warn only once per string */ 148 }