Parsing 如何处理隐含的';cat&x27;运算符生成用于RE的语法树时(使用堆栈求值)

Parsing 如何处理隐含的';cat&x27;运算符生成用于RE的语法树时(使用堆栈求值),parsing,compiler-construction,grammar,recursive-descent,expression-evaluation,Parsing,Compiler Construction,Grammar,Recursive Descent,Expression Evaluation,我正在尝试为正则表达式构建语法树。我使用类似于算术表达式求值的策略(我知道有递归下降的方法),也就是说,使用两个堆栈,OPND堆栈和OPTR堆栈,然后进行处理 我使用不同类型的节点来表示不同类型的RE。例如,SymbolExpression,CatExpression,oreexpression和StarExpression,它们都是从正则表达式派生出来的 因此OPND堆栈存储正则表达式* while(c || optr.top()): if(!isOp(c): opnd

我正在尝试为正则表达式构建语法树。我使用类似于算术表达式求值的策略(我知道有递归下降的方法),也就是说,使用两个堆栈,OPND堆栈和OPTR堆栈,然后进行处理

我使用不同类型的节点来表示不同类型的RE。例如,
SymbolExpression
CatExpression
oreexpression
StarExpression
,它们都是从
正则表达式
派生出来的

因此OPND堆栈存储
正则表达式*

while(c || optr.top()):
    if(!isOp(c):
        opnd.push(c)
        c = getchar();
    else:
        switch(precede(optr.top(), c){
        case Less:
          optr.push(c)
          c = getchar();
        case Equal:
          optr.pop()
          c = getchar();
        case Greater:
          pop from opnd and optr then do operation, 
          then push the result back to opnd
        }
但我的主要问题是,在典型的RE中,
cat
操作符是隐式的。
a | bc
代表
a | b.c
(a | b)*abb
代表
(a | b)*.a.b.b
。因此,当遇到非操作员时,我应该如何确定是否有cat操作员?我应该如何处理cat操作员,以正确执行转换

更新 现在我了解到有一种叫做“运算符优先文法”的文法,它的求值类似于算术表达式。它要求语法模式不能具有S->…AB…的形式(A和B是非终结符)。所以我猜我不能直接使用这个方法来解析正则表达式

更新二 我试图设计一个LL(1)语法来解析基本正则表达式。 这是原始语法。(\ |是转义字符,因为|是语法模式中的特殊字符)

要删除左递归,请导入新变量

E -> TE'
E' -> \| TE' | ε
T -> FT'
T' -> FT' | ε
F -> P* | P   
P -> (E) | i
现在,对于模式F->p*| p,导入p'

P' -> * | ε
F -> PP'
但是,模式<代码> t′-> f'ε< /COD>存在问题。 在这里,我们的人类知道我们应该用ε替换变量
T'
,但程序只会调用
T'->FT'
,这是错误的


那么,这个语法有什么问题?我应该如何重写它以使其适合递归后代方法。

我认为只跟踪上一个字符就足够了。如果我们有

(a|b)*abb
      ^--- we are here
c = a
pc = *
我们知道,
*
是一元数,所以“a”不能是它的操作数。所以我们必须有concateration。下一步类似

(a|b)*abb
       ^--- we are here
c = b
pc = a
a
不是运算符,
b
不是运算符,因此我们的隐藏运算符位于它们之间。还有一个:

(a|b)*abb
   ^--- we are here
c = b
pc = |
|
是一个需要右操作数的二进制运算符,因此我们不进行串联

完整的解决方案可能涉及为每台可能的
pc
构建一个表,这听起来很痛苦,但它应该给您足够的上下文来完成

如果你不想打乱循环,你可以做一个预处理过程,使用类似的逻辑插入你自己的连接字符。不能告诉你这是好是坏,但这是个好主意。

1.LL(1)语法 我看你的LL(1)语法没有任何问题。你正在解析字符串

(a|b)
你已经做到了这一点:

(a   T'E')T'E'   |b)
前瞻符号为|,您有两种可能的产品:

T' ⇒ FT'
T' ⇒ ε
第一个(F)是
{(,i}
,因此第一个结果对于人类和LL(1)解析器来说显然是不正确的。(没有前瞻的解析器无法做出决定,但是没有前瞻的解析器对于实际解析几乎是无用的。)

2.运算符优先解析 您在技术上是正确的。您的原始语法不是运算符语法。但是,使用小型状态机扩充运算符优先级解析器是正常的(否则,例如,无法正确解析包含一元减号的代数表达式),完成后,隐式连接运算符的位置就很清楚了

状态机在逻辑上相当于对输入进行预处理,以便在必要时插入一个显式的串联运算符——即,只要
a
{,*,i}
中,
b
{),i}
中,状态机就在
a
b
之间


您应该注意,除非使用显式ε原语来表示空字符串,否则您的原始语法不会真正处理正则表达式。否则,您无法表示可选选项,这些选项通常在正则表达式中表示为隐式操作数(例如
(a |))
,也常写成
a?
)但是,状态机也很容易检测隐式操作数,因为隐式连接和隐式ε之间在实践中没有冲突。< /P>你的正则表达式支持什么算子?我不明白为什么如果字符是非运算符,就不能仅仅说是级联。,如何在遇到“b”时直接推concat运算符?如果我关于“运算符优先语法”的推论是正确的,我认为用这种策略很难解析基本正则表达式。可能需要预处理。谢谢你的回答。如何“合并”表达式求值的插入阶段?您只需在运算符堆栈上插入隐式运算符(或操作数堆栈上的隐式操作数),就好像您已经读取了它一样,然后执行与运算符(或操作数)相同的状态转换.非常直截了当。您能更详细地解释一下吗?以
*i
为例,在这种情况下,我不能直接将cat操作符推入操作符堆栈,因为必须首先计算*操作。但是如果我将当前
c
设置为操作符cat,那么如何处理循环的下一步,如
prev
/
getchar
?@user8510613:尝试将
put(stack,operator)
设置为一个函数(它可以执行将运算符放入堆栈所需的任何操作),它可能会显示算法的简单性。例如,当遇到
i(a   T'E')T'E'   |b)
T' ⇒ FT'
T' ⇒ ε