问题描述
我正在编写一个 flownake 数字计算器,并且刚刚创建了一个解析器。该程序是用 C++ 编写的,我不得不通过查看生成的代码来弄清楚 Bison 与 C++ 一起使用的一些细节。 (calc++ 示例使用 Flex,但我没有使用 Flex。)我是 Bison 的新手。这是到目前为止的解析器:
%require "3.2"
%language "c++"
%{
#include "flowsnake.h"
#include "parser.h"
int yylex(yy::parser::semantic_type *val);
%}
%token
PLUS "+"
MINUS "-"
TIMES "*"
OVER "/"
LPAR "("
RPAR ")"
PREC "prec"
INT
FLOWNUM
;
%%
input:
%empty
| line input
;
line:
'\n'
| cmd '\n'
| exp '\n'
;
cmd:
PREC INT
;
exp:
FLOWNUM
| exp PLUS exp
;
%%
int yylex(yy::parser::semantic_type *val)
{
*val=0;
return 0;
}
void yy::parser::error(const string &msg)
{
cerr<<msg<<endl;
}
我必须解析两种数字(最终是三种,第三种是具有实部和虚部的复数):有理整数,使用数字 0-9,可以有符号但没有小数点,以及流蛇数,使用数字 0-6 并且可以有小数点但没有符号。如果我说 261+34
,那么 261
是一个等于 8 的流蛇数,我正在向它添加另一个流蛇数。但是如果我说 prec 261
,那么 261 是十进制的,我将精度设置为 261 位。 28+34
和 prec 26.1
均无效,但 26.1+34
和 prec 28
均有效。如何编写词法分析器来对这些数字进行词法分析?
代码在 https://github.com/phma/flowbound 中。
解决方法
根据您对目标语言的不太详细的描述,扫描器似乎不需要知道它正在查看什么类型的数字来识别令牌。 (标志可能会影响它,但见下文。)它可能无法明确识别错误,但这对于标记化无关紧要。 (这是基于 281+34
是错误的想法。如果有一个上下文,您可能希望将其分解为单独的标记 - 例如,2
、81
、{{ 1}},+
-- 那么以下将不适用。但这似乎不太可能。)
在这种情况下,最好的解决方案通常是让词法扫描器简单地识别字符串并将其传递给解析器。然后解析器可以进行任何必要的转换,或者如果字符串无法转换,则发出错误消息。
如果扫描器知道预期的数字令牌类型,它确实可以识别错误,并且我建议的架构的最终结果是错误令牌被有效地扫描两次以查找错误(尽管第二次扫描没有t 实际上成本很高,因为它会来自转换失败)。因此可以更快地检测到错误。但是针对错误进行优化实际上从来都不是一种有效的优化策略(除非您正在编写一个 linter)。最常见的情况是输入正确,在代码中引入复杂性以“更有效”地处理错误几乎总是一种错误的节约。
正如您在评论中所说,您也可以让扫描仪进行两种转换,以指明哪些转换是有效的。同样,这似乎是虚假的经济。实际上只需要一次转换,所以在两种可能的情况下,其中一个是浪费时间。如果转换成本高,则尤其如此。
在解析器中,转换将由一个单元产生式表示,它很好地封装了过程:
34
上面遗漏的是错误报告。实际上,cmd:
"prec" integer
exp:
flownum
| exp "+" exp
integer:
INT { $$ = convert_to_int($1); }
flownum:
FLOWNUM { $$ = convert_to_flownum($1); }
和 convert_to_int
可能有某种机制来返回错误条件,解析器会检查该返回。
正如上面所提到的,符号可能是一个问题,因为您希望整数可选地有符号。一个简单的扫描器总是将一个标志作为一个单独的标记来扫描,这主要是你想要的。但是,上面的 convert_to_flownum
定义可以很容易地扩展为允许有符号整数:
integer