如何在 BISON 中访问一个在 FLEX 中使用 REJECT 被拒绝的令牌?
How to access in BISON, a token rejected using REJECT in FLEX?
我知道可以拒绝 lex 中的标记,如:
.* {
//Reject the entire line but get its value
yylval->strval = strdup(yytext);
REJECT;
};
我这样做是为了获取整行的内容,以便稍后报错。所以我想保留这一行并在野牛代码中访问它。拒绝整行后,该行中的标记与其他 lex 规则匹配,预计将被解析并匹配 .y 中的解析规则。 (还是我错了?)
保留被拒绝行的一种方法是将其分配给一个全局变量,这当然不优雅,并且由于其他原因也不正确(这是可重入的parse/lexer)。
有什么建议吗?
首先,不要为此使用 REJECT
。 REJECT
是一个可怕的 hack,它会大大降低你的词法分析器的性能,并且在解析器中的实际用例很少。
此外,它不会像你写的那样工作。在您拒绝与 .*
的匹配后,返回的下一个匹配将是下一个较短的 .*
匹配——即除了行中最后一个字符之外的所有匹配——而不是另一个模式的匹配(除非所有但行中的最后一个字符恰好匹配另一个具有更高优先级的模式。
如果您想捕获并临时存储读取的每一行,最简单的方法是提供您自己的 YY_INPUT
,它一次读取一行输入。 (如果您的 C 库支持,我建议使用 Posix 2008 getline()
接口。)只需保存此行(及其长度)即可。
现在,回答您的实际问题:将内容保存在可重入词法分析器的何处,以便解析器可以看到它们。
幸运的是,flex 为 a way for you to augment the yyscan_t
context object 提供了您自己的任意“额外”数据,yyextra
。 (在扫描器代码本身之外,您需要使用访问器 yyget_extra(scanner)
来获取此值。)默认情况下,yyextra
具有类型 void *
,但您可以使用 %option extra-type
。
通常 yyextra
是指向您自己的上下文对象的指针,但如果您不需要太多上下文,也可以使 yyextra
成为一个小结构。 (yyget_extra
和 yyset_extra
按值传递 yyextra
,这对许多上下文对象来说不合适。)
这里有一个(有点)简单的例子。下面的大部分样板文件取自 中的骨架;请参阅 link 以了解对大多数奇怪情况的解释。
文件sample.l
%option noyywrap 8bit noinput nounput nodefault
%option reentrant bison-bridge bison-locations yylineno
%option extra-type="struct ScanExtra*"
%{
#include "sample.tab.h"
#define YY_INPUT(buf,result,max_size) do { \
yyextra->linebuf_len = getline(&yyextra->linebuf, \
&yyextra->linebuf_size, yyin); \
if (yyextra->linebuf_len == -1) result = YY_NULL; \
else if (yyextra->linebuf_len <= max_size) { \
memcpy(buf, yyextra->linebuf, yyextra->linebuf_len); \
result = yyextra->linebuf_len; \
} \
else { /* Handle long lines */ } \
} while(0)
%}
%%
[[:space:]] ; /* Ignore whitespace */
#.* ; /* Ignore comments */
[[:alnum:]]+ { yylval->str = strdup(yytext); return WORD; }
. { return *yytext; }
%%
/* These functions are here for simplicity. Normally, I'd put them in a
* separate parse_utils.c file.
* They're declared in sample.h (except for yyerror).
*/
/* Creates a scanner with an initialised ScanExtra */
yyscan_t myscanner_create(void) {
struct ScanExtra* extra = calloc(1, sizeof *extra);
yyscan_t scanner;
yylex_init_extra(extra, &scanner);
return scanner;
}
/* Destroys a scanner */
void myscanner_destroy(yyscan_t scanner) {
free(yyget_extra(scanner));
yylex_destroy(scanner);
}
#include <ctype.h>
void yyerror(YYLTYPE* yyllocp, yyscan_t scanner, const char* msg) {
/* Get rid of trailing whitespace in the current line */
const char* buf = yyget_extra(scanner)->linebuf;
int len = yyget_extra(scanner)->linebuf_len;
while (len && isspace(buf[len - 1])) --len;
fprintf(stderr, "Syntax error near '%s' in line %d: '%.*s'\n",
yyget_text(scanner),
yyget_lineno(scanner),
len, buf);
}
文件sample.y
%define api.pure full
%locations
%param { yyscan_t scanner }
%code top {
#include <stdio.h>
}
%code requires {
typedef void* yyscan_t;
/* I define ScanExtra here so that it goes into the generated header file */
struct ScanExtra {
char* linebuf;
size_t linebuf_size;
ssize_t linebuf_len;
};
}
%code {
int yylex(YYSTYPE* yylvalp, YYLTYPE* yyllocp, yyscan_t scanner);
void yyerror(YYLTYPE* yyllocp, yyscan_t scanner, const char* msg);
}
%union {
char* str;
}
%token <str> WORD
%%
item: WORD { free(); }
| '(' opt_item_list ')'
item_list: item | item_list ',' item
opt_item_list: %empty | item_list
文件sample.h
#include "sample.tab.h"
#include "sample.lex.h"
yyscan_t myscanner_create(void);
void myscanner_destroy(yyscan_t);
文件:生成文件
all: sample
sample.lex.c: sample.l
flex -o $@ --header-file=$(patsubst %.c,%.h,$@) --debug $<
sample.tab.c: sample.y
bison -o $@ --defines=$(patsubst %.c,%.h,$@) --debug $<
sample: main.c sample.tab.c sample.lex.c sample.h
$(CC) -o $@ -Wall --std=c11 -ggdb -D_XOPEN_SOURCE=700 $(filter %.c,$^)
clean:
rm -f sample.tab.c sample.lex.c sample.tab.h sample.lex.h sample
我知道可以拒绝 lex 中的标记,如:
.* {
//Reject the entire line but get its value
yylval->strval = strdup(yytext);
REJECT;
};
我这样做是为了获取整行的内容,以便稍后报错。所以我想保留这一行并在野牛代码中访问它。拒绝整行后,该行中的标记与其他 lex 规则匹配,预计将被解析并匹配 .y 中的解析规则。 (还是我错了?)
保留被拒绝行的一种方法是将其分配给一个全局变量,这当然不优雅,并且由于其他原因也不正确(这是可重入的parse/lexer)。
有什么建议吗?
首先,不要为此使用 REJECT
。 REJECT
是一个可怕的 hack,它会大大降低你的词法分析器的性能,并且在解析器中的实际用例很少。
此外,它不会像你写的那样工作。在您拒绝与 .*
的匹配后,返回的下一个匹配将是下一个较短的 .*
匹配——即除了行中最后一个字符之外的所有匹配——而不是另一个模式的匹配(除非所有但行中的最后一个字符恰好匹配另一个具有更高优先级的模式。
如果您想捕获并临时存储读取的每一行,最简单的方法是提供您自己的 YY_INPUT
,它一次读取一行输入。 (如果您的 C 库支持,我建议使用 Posix 2008 getline()
接口。)只需保存此行(及其长度)即可。
现在,回答您的实际问题:将内容保存在可重入词法分析器的何处,以便解析器可以看到它们。
幸运的是,flex 为 a way for you to augment the yyscan_t
context object 提供了您自己的任意“额外”数据,yyextra
。 (在扫描器代码本身之外,您需要使用访问器 yyget_extra(scanner)
来获取此值。)默认情况下,yyextra
具有类型 void *
,但您可以使用 %option extra-type
。
通常 yyextra
是指向您自己的上下文对象的指针,但如果您不需要太多上下文,也可以使 yyextra
成为一个小结构。 (yyget_extra
和 yyset_extra
按值传递 yyextra
,这对许多上下文对象来说不合适。)
这里有一个(有点)简单的例子。下面的大部分样板文件取自
文件sample.l
%option noyywrap 8bit noinput nounput nodefault
%option reentrant bison-bridge bison-locations yylineno
%option extra-type="struct ScanExtra*"
%{
#include "sample.tab.h"
#define YY_INPUT(buf,result,max_size) do { \
yyextra->linebuf_len = getline(&yyextra->linebuf, \
&yyextra->linebuf_size, yyin); \
if (yyextra->linebuf_len == -1) result = YY_NULL; \
else if (yyextra->linebuf_len <= max_size) { \
memcpy(buf, yyextra->linebuf, yyextra->linebuf_len); \
result = yyextra->linebuf_len; \
} \
else { /* Handle long lines */ } \
} while(0)
%}
%%
[[:space:]] ; /* Ignore whitespace */
#.* ; /* Ignore comments */
[[:alnum:]]+ { yylval->str = strdup(yytext); return WORD; }
. { return *yytext; }
%%
/* These functions are here for simplicity. Normally, I'd put them in a
* separate parse_utils.c file.
* They're declared in sample.h (except for yyerror).
*/
/* Creates a scanner with an initialised ScanExtra */
yyscan_t myscanner_create(void) {
struct ScanExtra* extra = calloc(1, sizeof *extra);
yyscan_t scanner;
yylex_init_extra(extra, &scanner);
return scanner;
}
/* Destroys a scanner */
void myscanner_destroy(yyscan_t scanner) {
free(yyget_extra(scanner));
yylex_destroy(scanner);
}
#include <ctype.h>
void yyerror(YYLTYPE* yyllocp, yyscan_t scanner, const char* msg) {
/* Get rid of trailing whitespace in the current line */
const char* buf = yyget_extra(scanner)->linebuf;
int len = yyget_extra(scanner)->linebuf_len;
while (len && isspace(buf[len - 1])) --len;
fprintf(stderr, "Syntax error near '%s' in line %d: '%.*s'\n",
yyget_text(scanner),
yyget_lineno(scanner),
len, buf);
}
文件sample.y
%define api.pure full
%locations
%param { yyscan_t scanner }
%code top {
#include <stdio.h>
}
%code requires {
typedef void* yyscan_t;
/* I define ScanExtra here so that it goes into the generated header file */
struct ScanExtra {
char* linebuf;
size_t linebuf_size;
ssize_t linebuf_len;
};
}
%code {
int yylex(YYSTYPE* yylvalp, YYLTYPE* yyllocp, yyscan_t scanner);
void yyerror(YYLTYPE* yyllocp, yyscan_t scanner, const char* msg);
}
%union {
char* str;
}
%token <str> WORD
%%
item: WORD { free(); }
| '(' opt_item_list ')'
item_list: item | item_list ',' item
opt_item_list: %empty | item_list
文件sample.h
#include "sample.tab.h"
#include "sample.lex.h"
yyscan_t myscanner_create(void);
void myscanner_destroy(yyscan_t);
文件:生成文件
all: sample
sample.lex.c: sample.l
flex -o $@ --header-file=$(patsubst %.c,%.h,$@) --debug $<
sample.tab.c: sample.y
bison -o $@ --defines=$(patsubst %.c,%.h,$@) --debug $<
sample: main.c sample.tab.c sample.lex.c sample.h
$(CC) -o $@ -Wall --std=c11 -ggdb -D_XOPEN_SOURCE=700 $(filter %.c,$^)
clean:
rm -f sample.tab.c sample.lex.c sample.tab.h sample.lex.h sample