我想评估用户可以为许多数据点输入的公式,因此效率是一个问题。这是针对Fortran项目的,但到目前为止我的解决方案都集中在使用yacc / bison语法,所以我可能会使用Fortran的iso_c_binding功能来连接yyparse()。
首选(目前为止)解决方案是Bison手册中经典mfcalc
计算器示例的一个小扩展,其中bison语法也用于识别(单个)变量名称(这并不难) )。
问题是在可执行语句中该怎么做。我在那里看到两个选择。
首先,我可以简单地评估解析时的表达式,如mfcalc
示例中那样。
其次,我可以调用一次bison解析器进行解析,并创建一个基于堆栈(反向抛光)的被解析公式表示,所以
2 + 3*x
将被翻译为2 3 * +
(当然,作为相关数据结构)。
语法的相关部分如下所示:
%union {
double val;
char *c;
int fcn;
}
%type <val> NUMBER
%type <c> VAR
%type <fcn> Function
/* Tokens and %left PLUS MINUS etc. left out for brevity */
%%
...
Function:
SIN { $$=SIN; }
| COS { $$=COS; }
| TAN { $$=TAN; }
| SQRT { $$=SQRT; }
Expression:
NUMBER { push_number($1); }
| VAR { push_var($1); }
| Expression PLUS Expression { push_operand(PLUS); }
| Expression MINUS Expression { push_operand(MINUS); }
| Expression DIVIDE Expression { push_operand(DIVIDE); }
| MINUS Expression %prec NEG { push_operand(NEG); }
| LEFT_PARENTHESIS Expression RIGHT_PARENTHESIS;
| Function LEFT_PARENTHESIS Expression RIGHT_PARENTHESIS { push_function($1); }
| Expression POWER Expression { push_operand(POWER); }
函数push _...会将公式放入一个结构数组中,这些结构包含一个包含令牌和yacc联合的结构。
然后使用非常简单(并且希望很快)的解释器来解释RPN。
所以,问题。
第二种方法有效吗?我认为它来自我对bison(或yacc)处理shift和reduce的方式的理解(基本上,这会改变数字并减少表达式,所以顺序应该保证为了正确的RPN),但我不太确定。
另外,使用$$构造(第一种方法)简单地评估函数是否值得付出额外的努力?
最后,还有其他更好的解决方案吗?我曾考虑使用语法树,但我认为额外的努力实际上是值得的。此外,我倾向于认为使用树是一种过度杀戮,阵列可以做得很好: - )
答案 0 :(得分:1)
生成三地址虚拟操作比RPN稍微困难一点。实际上,RPN是一个虚拟堆栈机器。三个地址操作 - 也可以很容易地进入数组 - 可能更快解释,并且从长远来看可能更灵活。
将表达式解析为某种内部形式的主要优点是,评估内部表单可能比重新分析原始字符串更快。情况可能并非如此,但通常是因为将浮点文字转换为浮点数(相对而言)非常慢。
还有一种中间情况,即将表达式标记(放入数组),然后在解析标记流时直接进行评估。 (实际上,这会使野牛成为您的虚拟机。)
这些策略中哪一项最好取决于您的用例的详细信息,但这些策略都不困难,因此您可以尝试所有三种策略并进行比较。