我想传递一个令牌的实际字符串。如果我有一个名为 ID 的令牌,那么我希望我的 yacc 文件真正知道调用了什么 ID。我必须使用 yylval 将字符串从 flex 文件传递给 yacc 文件。我怎么做?
3 回答
通过 yylval 返回字符串或任何复杂类型的关键是 yacc 在 y.tab.h 文件中创建的 YYSTYPE 联合。YYSTYPE 是一个联合,其中包含在 yacc 源文件中定义的每种令牌类型的成员。例如,要返回与 yacc 源文件中的 SYMBOL 标记关联的字符串,您可以在 yacc 源文件中使用%union声明此 YYSTYPE 联合:
/*** Yacc's YYSTYPE Union ***/
/* The yacc parser maintains a stack (array) of token values while
it is parsing. This union defines all the possible values tokens
may have. Yacc creates a typedef of YYSTYPE for this union. All
token types (see %type declarations below) are taken from
the field names of this union. The global variable yylval which lex
uses to return token values is declared as a YYSTYPE union.
*/
%union {
long int4; /* Constant integer value */
float fp; /* Constant floating point value */
char *str; /* Ptr to constant string (strings are malloc'd) */
exprT expr; /* Expression - constant or address */
operatorT *operatorP; /* Pointer to run-time expression operator */
};
%type <str> SYMBOL
然后在 LEX 源文件中有一个与 SYMBOL 标记匹配的模式。与该规则相关的代码负责返回代表 SYMBOL 的实际字符串。您不能只将指针传递给 yytext 缓冲区,因为它是一个静态缓冲区,可用于每个匹配的标记。要返回匹配的文本,必须使用 _strdup() 在堆上复制静态 yytext 缓冲区,并通过 yyval.str 传递指向该字符串的指针。然后是 yacc 规则与 SYMBOL 令牌的职责相匹配,以在完成后释放堆分配的字符串。
[A-Za-z_][A-Za-z0-9_]* {{
int i;
/*
* condition letter followed by zero or more letters
* digits or underscores
* Convert matched text to uppercase
* Search keyword table
* if found
* return <keyword>
* endif
*
* set lexical value string to matched text
* return <SYMBOL>
*/
/*** KEYWORDS and SYMBOLS ***/
/* Here we match a keywords or SYMBOL as a letter
* followed by zero or more letters, digits or
* underscores.
*/
/* Convert the matched input text to uppercase */
_strupr(yytext); /* Convert to uppercase */
/* First we search the keyword table */
for (i = 0; i<NITEMS(keytable); i++) {
if (strcmp(keytable[i].name, yytext)==0)
return (keytable[i].token);
}
/* Return a SYMBOL since we did not match a keyword */
yylval.str=_strdup(yytext);
return (SYMBOL);
}}
15 与 Yacc 的接口
flex 的主要用途之一是作为 yacc 解析器生成器的伴侣。yacc 解析器期望调用一个名为 yylex() 的例程来查找下一个输入标记。该例程应该返回下一个标记的类型,并将任何关联的值放入全局 yylval。要将 flex 与 yacc 一起使用,需要为 yacc 指定 `-d' 选项,以指示它生成文件 y.tab.h,其中包含出现在 yacc 输入中的所有 %tokens 的定义。该文件然后包含在 flex 扫描仪中。例如,如果其中一个令牌是 TOK_NUMBER,则扫描仪的一部分可能如下所示:
%{ #include "y.tab.h" %} %% [0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;
设置上下文
语法分析(检查输入文本是否遵循指定的语法)包括两个阶段:
- 标记化,由 lex 或 flex 等工具完成,带有接口 yylex()) 和
- 解析步骤 1 中生成的令牌流(根据用户指定的语法),这是由带有接口 yyparse() 的 bison/yacc 等工具完成的。
在执行阶段 1时,给定一个输入流,对 yylex() 的每次调用都标识一个标记(一个字符字符串),并且 yytext 指向该字符串的第一个字符。例如:输入流为“int x = 10;” 并且使用符合 C 语言的标记化 lex 规则,那么对 yylex() 的前 5 次调用将识别以下 5 个标记“int”、“x”、“=”、“10”、“;” 并且每次 yytext 将指向返回令牌的第一个字符。
第 2 阶段,解析器(你提到的 yacc )是一个程序,它每次调用这个 yylex 函数来获取一个标记并使用这些标记来查看它是否匹配语法规则。这些对 yylex 的调用将返回令牌作为一些整数代码。例如,在前面的示例中,对 yylex() 的前 5 次调用可能会向解析器返回以下整数:TYPE、ID、EQ_OPERATOR 和 INTEGER(其实际整数值在某些头文件中定义)。
现在所有解析器都可以看到那些整数代码,它们有时可能没有用。例如,在运行的示例中,您可能希望将 TYPE 关联到 int,将 ID 关联到某个符号表指针,并将 INTEGER 关联到十进制 10。为了便于实现,yylex 返回的每个标记都与另一个默认类型为 int 的 VALUE 关联,但是你可能有自定义类型。在 lex 环境中,此 VALUE 作为 yylval 访问。
例如,再次按照运行示例,yylex 可能具有以下规则来识别 10
[0-9]+ { yylval.intval = atoi(yytext); return INTEGER; }
并遵循以识别 x
[a-zA-Z][a-zA-Z0-9]* {yylval.sym_tab_ptr = SYM_TABLE(yytext); return ID;}
请注意,这里我将 VALUE(或 yylval)类型定义为包含一个 int (intval) 和一个 int* 指针 (sym_tab_ptr) 的联合。
但是在 yacc 世界中,这个 VALUE 被标识/访问为 $n。例如,考虑以下 yacc 规则来识别特定的赋值语句
TYPE ID '=' VAL: { //In this action part of the yacc rule, use $2 to get the symbol table pointer associated with ID, use $4 to get decimal 10.}
回答你的问题
如果要访问 yacc world 中某个令牌(与 lex world 相关)的 yytext 值,请使用该老朋友 VALUE,如下所示:
- 增加 VALUE 的联合类型以添加另一个字段,例如 char* lex_token_str
- 在 lex 规则中,执行 yylval.lex_token_str = strdup(yytext)
- 然后在 yacc world 中使用适当的 $n 访问它。
- 如果您想要访问的不仅仅是单个令牌值(例如,对于 lex 标识的令牌 ID,解析器可能希望同时访问名称和符号表指针),然后使用 VALUE 增加联合类型结构成员,包含 char*(用于名称)和 int*(用于 symtab 指针)。