Parsing 解析复杂数学表达式

Parsing 解析复杂数学表达式,parsing,math,Parsing,Math,我目前正在写(为了好玩)一种深受数学启发的编程语言 运算符(及其优先级/关联性)在语言中定义,例如: let factorial be an operator[int <- int!] with precedence of 2 and left-to-right associativity; let factorial(n) = ...; let abs be an operator[number <- |number|] with precedence o

我目前正在写(为了好玩)一种深受数学启发的编程语言

运算符(及其优先级/关联性)在语言中定义,例如:

let factorial be an operator[int <- int!]
    with precedence of 2
    and left-to-right associativity;
let factorial(n) = ...;

let abs be an operator[number <- |number|]
    with precedence of 2
    and no associativity;
let abs(x) = ...;

let not be an operator[bool <- !bool]
   with precedence of 2
   and right-to-left associativity;
m! / (n! * (m - n)!)
将被解析为(此处为简化AST):

在第一步之后,我知道定义了哪些操作符,我知道它们的优先级顺序,以及它们的关联性

我在实现第二步时遇到了困难,该步骤应该为表达式生成树(而不是列表)

第一步是使用以EBNF语法作为输入的方法实现的。 对于第二步,我尝试在表达式中“查找”操作符的模式(使用自制的:


让factorial成为一个运算符[int您可以使用一个运算符优先解析器,比如臭名昭著的解析器(可能也有帮助,也可能没有)

调车场算法(包括在维基百科的那篇文章中)经常被作为一种“将中缀转换为反向波兰语”的方式提出。它不是。它是一种解析算法,它可以很容易地生成AST或三地址代码,或者任何你想用它生成的东西。但我同意AST是最好的选择

要将Wikipedia上的算法修改为生成语法树的算法,请执行以下操作:

  • 用一堆树节点替换他们所称的“输出队列”
  • 当它们“将一个值推送到输出队列”时,创建该值的AST节点(最有可能是一个文本或标识符),并将其推送到堆栈上。}
  • 当它们“将运算符推送到输出队列上”时:堆栈顶部的节点是运算符语法节点的子节点。因此,请弹出它们(记住,对于使用多个操作数的运算符,堆栈顶部的节点是最右边的参数)并将它们组合成一个新的操作符节点。然后将该节点推回到堆栈上
  • 在解析结束时,堆栈将只包含一个元素(如果所有运算符都使用了正确数量的参数),即整个表达式的AST节点
许多允许在源代码中定义运算符语法的语言正是使用这种算法的

在我自己做了几件事情之后,让我提出几条建议,你可以随意忽略:

  • 尽管现在Unicode提供了使用各种单一“字符”作为数学运算符的可能性,但现实是⊛ 输入并不是那么容易,而且一个坚持你将其用于某些功能的库可能并不那么受用户欢迎。当然,你可以尝试编写自己的支持Unicode的跨平台IDE,但这给我的印象是,这是一个非常深且复杂的兔子洞,一个迷宫般曲折的通道。因此,你很可能想要一个允许将其键入类似于
    (*)
    。但问题是,在阅读运算符声明之前,您不知道标记是什么。没有真正令人满意的解决方案(在有人编写上述IDE之前),我所见过的大多数允许自定义运算符的语言在指定了一组可以用作运算符字符的字符后,都坚持使用空格分隔


  • <> LI> < P>正如在C或C++中编写过位表达式的任何人都知道的,有很多不同的运算符优先级并不都是用户友好的。我们中的大多数人没有直觉指导我们理解<代码> x + 2谢谢你的答案!对于你的第一点我不打算使用Unicode,我喜欢坚持你能说的。在键盘上键入sily(我不想使用APL 2.0并提供键盘)。至于你的第二点,这是Perl 6 way(),实际上它的可读性更高!使用括号帮助可读性应该是开发人员关心的问题,IMHO,该语言仍然需要可预测的规则(即使是复杂的规则)@linkdd:是的,像
    multi-sub-infix:is-stricker(&infix:)
    这样的相对优先级声明基本上就是我所提倡的。它肯定比使用任意数字作为优先级级别要好。但是,至少从Raku文档中可以看出,运算符优先级几乎是一个整体排序(除了非关联优先级别)。我的方法要严格得多(但在实践中,效果很好)。我在文档中找不到任何东西可以解释当定义不明确时会发生什么,这当然是可能的。(例如,后缀运算符也是中缀)。有可能检测到歧义,因此至少可以想象会产生错误消息。我没有尝试安装Raku来查找。总之,祝您的项目好运。
    ["m", "!", "/", ["n", "!", "*", ["m", "-", "n"], "!"]]
    
    let factorial be an operator[int <- int!] ...;
    # here the pattern is ["int", "!"]