这不是您希望的答案。我认为您没有看到您想要的示例的原因是在语法文件(.y)中强制执行输入规则是不切实际的;相反,开发人员在程序 .c 或 .cpp 代码中完成此操作。通常,无论如何您都会对已解析的输入进行一些分析,因此在执行语义规则时执行语义规则是一个副产品。
顺便说一句,鉴于您在问题中重现的语法片段,我不太了解您是如何解析表达式的。
这就是为什么我声称这是不切实际的。(1) 您的类型信息必须渗透到语法的所有非终结符中。(2) 更糟糕的是,它必须反映在变量名中。
考虑这个解析可以使用标识符、数字常量和四个桌面计算器运算符的简单赋值语句的玩具示例。NUMBER 标记可以是像 42 这样的整数或像 3.14 这样的浮点数。假设 IDENTIFIER 是一个字母 AZ。
%token IDENTIFIER NUMBER
%%
stmt : IDENTIFIER '=' expr
;
expr : expr '+' term
| expr '-' term
| term
;
term : term '*' factor
| term '/' factor
| factor
;
factor : '(' expr ')'
| '-' factor
| NUMBER
| IDENTIFIER
;
现在让我们尝试引入打字规则。我们将 NUMBER 令牌分为 FLT_NUMBER 和 INT_NUMBER。我们的expr
, term
, 和factor
非终结符也分为两部分:
%token IDENTIFIER FLT_NUMBER INT_NUMBER
stmt : IDENTIFIER '=' int_expr
| IDENTIFIER '=' flt_expr
;
int_expr : int_expr '+' int_term
| int_expr '-' int_term
| int_term
;
flt_expr : flt_expr '+' flt_term
| flt_expr '-' flt_term
| flt_term
;
int_term : int_term '*' int_factor
| int_term '/' int_factor
| int_factor
;
flt_term : flt_term '*' flt_factor
| flt_term '/' flt_factor
| flt_factor
;
int_factor : '(' int_expr ')'
| '-' int_factor
| INT_NUMBER
| int_identifier
;
flt_factor : '(' flt_expr ')'
| '-' flt_factor
| FLT_NUMBER
| flt_identifier
;
int_identifier : IDENTIFIER ;
flt_identifier : IDENTIFIER ;
由于我们的语法在这一点上存在冲突:解析器无法判断是将 IDENTIFIER 识别为 aint_identifier
还是 a flt_identifier
。所以它不知道是减少A = B
asIDENTIFIER = int_expr
还是IDENTIFIER = flt_expr
。
(这就是我对 Ruby 的理解有点软的地方:) Ruby(像大多数语言一样)没有提供在词汇级别确定标识符的数字类型的方法。将此与老式 BASIC 进行对比,其中 A 表示数字,A$ 表示字符串。换句话说,如果你发明了一种语言,例如,A# 表示整数,A@ 表示浮点数,那么你可以做到这一点。
如果您想允许有限的混合类型表达式,例如 an int_term '*' flt_factor
,那么您的语法会变得更加复杂。
可能有一些方法可以解决这些问题。使用 yacc/bison 以外的技术构建的解析器可能会更容易。至少,也许我的草图会给你一些进一步追求的想法。