我有一个字符串,其中包含一个自定义表达式,我必须解析并评估:
例如:
(FUNCTION_A(5,4,5) UNION FUNCTION_B(3,3))
INTERSECT (FUNCTION_C(5,4,5) UNION FUNCTION_D(3,3))
FUNCTION_X表示在C#中实现并返回IList的函数。 UNION或INTERSECT是自定义函数,应该应用于从这些函数返回的列表。
通过Enumerable.Intersect/Enumerable.Union
实现联盟和交叉。
如何以优雅和可扩展的方式实现解析和评估?
答案 0 :(得分:5)
这取决于你的表达式将变得多么复杂,有多少不同的运算符可用,以及许多不同的变量。无论您采用哪种方式,您都可能需要先为您的迷你语言确定grammar。
对于简单的语法,您只需编写自定义解析器即可。在许多计算器和类似应用程序的情况下,recursive descent解析器足以表达语法并且直观易读。链接的Wikipedia页面提供了示例语法和C解析器的实现。 Eric White在C#中也有blog post构建递归下降解析器。
对于更复杂的语法,您可能希望自己跳过创建它的工作并使用lex / yacc类型词法分析器和解析器工具集。通常,您以EBNF或类似的语法为这些语法提供输入,并且它们将生成为您解析输入所需的代码。解析器通常会返回一个可以遍历的syntax tree,允许您为输入流中的每个标记应用逻辑(树中的每个节点)。对于C#,我使用了GPLex和GPPG,但ANTLR等其他人也可以使用。
通常,您希望能够将输入中的每个项目拆分为有意义的令牌,并基于这些令牌构建树。构建树后,您可以遍历树并在每个节点上执行必要的操作。 FUNCTION_A(5,4,5) UNION FUNCTION_B(3,3)
的语法树可能如下所示,其中节点类型为大写字母,其值在括号中:
PROGRAM
|
|
UNION
|
------------------------------
| |
FUNCTION (FUNCTION_A) FUNCTION(FUNCTION_B)
| |
------------- ----------
| | | | |
INT(5) INT(4) INT(5) INT(3) INT(3)
解析器需要足够聪明才能知道当找到UNION
时,需要为联合提供两个项目等。给定这个树,你将从根开始({{1并且进行深度优先遍历。在PROGRAM
节点,操作是首先访问所有子节点,然后将结果合并在一起。在UNION
节点,操作是首先访问所有子节点,找到它们的值,并将这些值用作函数的参数,然后评估这些输入上的函数并返回值。
对于您可以提出的任何表达,这将继续用于所有令牌。通过这种方式,如果您花时间让解析器生成正确的树,并且每个节点都知道如何执行它需要的任何操作,那么您的设计是非常可扩展的,并且可以处理与其设计的语法相匹配的任何输入。