在保持左结合性的同时避免左递归解析 Lambda 微积分

Avoiding Left Recursion parsing Lambda Calculus while maintaining Left Associativity

我正在尝试利用 JavaScript 和 PEG.JS 将 lambda 演算项解析为 AST。语法相当简单:

/*****************************************************************
        t ::=
                x                                       variable
                λx.t                                    abstraction
                t t                                     application
*****************************************************************/

我从中编码出 PEG:

TERM "term"
    = ABSTRACTION
    / APPLICATION
    / VARIABLE

APPLICATION "application"
    /*****************************************************************
        application ::= t t
    *****************************************************************/
    = APPLICATION_W_PARENS
    / APPLICATION_WO_PARENS

ABSTRACTION "abstraction"
    /*****************************************************************
        abstraction ::= λx.t
    *****************************************************************/
    = ABSTRACTION_W_PARENS
    / ABSTRACTION_WO_PARENS

VARIABLE "variable"
    /*****************************************************************
        variable ::= x
    *****************************************************************/
    =  x:CHARACTER
    {
        return Variable(location(), x)
    }

//////////////////////////////////////////////////////////////////////
// Application
//////////////////////////////////////////////////////////////////////

ABSTRACTION_OR_VARIABLE
    //////////////////////////////////////////////////////////////////
    // "Left recursive grammar" workaround "term term" enters a loop
    //      assuming the left side cannot match Application
    //      remediates the left recursion issue
    //////////////////////////////////////////////////////////////////
    = ABSTRACTION / VARIABLE

APPLICATION_W_PARENS
    /*****************************************************************
        '(' -> Abstraction | Variable -> Term -> ')'
    *****************************************************************/
    = L_PARENS lhs:ABSTRACTION_OR_VARIABLE rhs:TERM R_PARENS
    {
        return Application(location(), lhs, rhs, true)
    }

APPLICATION_WO_PARENS
    /*****************************************************************
        Abstraction | Variable -> Term
    *****************************************************************/
    = lhs:ABSTRACTION_OR_VARIABLE rhs:TERM
    {
        return Application(location(), lhs, rhs, false)
    }

//////////////////////////////////////////////////////////////////////
// Abstraction
//////////////////////////////////////////////////////////////////////

ABSTRACTION_W_PARENS "abstraction"
    /*****************************************************************
            '(' -> 'λ' -> Variable -> '.' -> TERM -> ')'
    *****************************************************************/
    = L_PARENS LAMBDA x:CHARACTER DOT term:TERM R_PARENS
    {
        return Abstraction(location(), x, term, true)
    }

ABSTRACTION_WO_PARENS
    /*****************************************************************
            'λ' -> Variable -> '.' -> Term
    *****************************************************************/
   = LAMBDA x:CHARACTER DOT term:TERM
   {
        return Abstraction(location(), x, term, false)
   }

//////////////////////////////////////////////////////////////////////
// Atoms
//////////////////////////////////////////////////////////////////////

LAMBDA "lambda"
    = 'λ'

L_PARENS "lParens"
    = '('

R_PARENS "rParens"
    = ')'

DOT "dot"
    = [\.]

CHARACTER "character"
    = [A-Za-z]
    {
        return text().trim() ;
    }

这在简单的输入上编译和运行良好。当我开始通过示例来测试实现时,我发现了一些问题。给定术语

λl.λm.λn.lmn

解析为

{
    "expr": "λl.λm.λn.lmn",
    "ast": " Abstraction( l,  Abstraction( m,  Abstraction( n, Application(  Variable( l ), Application(  Variable( m ),  Variable( n ) ) ) ) ) )"
}

问题在左应用 m应该应用到l然后n 到那个结果。从 AST 的打印输出可以看出,n 应用于 m,结果应用于 l 这是不正确的。

如果我更改现有规则以防止左递归问题,其中应用程序假设左侧只是一个变量或抽象以包含应用程序的可能性 - 那么就会出现递归问题。

我介绍了括号的概念 - 但我停止将它们整合进去。我真的不希望它们出现在语法中。

  1. 我们可以在 PEG.JS 中解决这个问题吗?
  2. 或者我应该重写应用程序对象的构造(hack)吗?
  3. 或者有没有更好的方法来解析这个 - 例如滚动自定义解析器?

有缺陷的方法:我玩过的一种方法是强制匹配两个以上的变量 |连续抽象并向左应用它们

APP_2
    = lhs:ABSTRACTION_OR_VARIABLE rhs:ABSTRACTION_OR_VARIABLE
    {
        return Application(location(), lhs, rhs, false, "APP2")
    }

APP_3
    = lhs:APP_2 rhs:TERM
    {
        return Application(location(), lhs, rhs, false, "APP3")
    }

APPLICATION_WO_PARENS
    = APP_3
    / APP_2

这看起来适用于三​​个学期的申请。当有四个时,我们得到一棵 2 层的平衡树,而我们想要一棵三层的不平衡树……所以这是前一个 PEG 的错误输出(输入为 lmno):

{
    "expr": "lmno",
    "ast": "Application::APP3( 
              Application::APP2(  Variable(l),  Variable(m) ),
              Application::APP2(  Variable(n),  Variable(o) ) 
            )"
}

所以我可以构建任意数量的 APP_2 ... APP_99 规则并强制执行左侧应用程序。哪个可行 - 直到我超过 99(或其他)申请数量。解决方案将是一个真正的黑客和脆弱的。


工作 Hack 方法:想要一起破解一些东西,我改变了匹配术语数组作为应用程序的方法:

APP_ARR
    = terms:ABSTRACTION_OR_VARIABLE*
   {
        return reduceTerms(location(), terms)
   }

APPLICATION_WO_PARENS
    = APP_ARR

这种方法需要我编写一些代码来构建我试图避免的结构 (reduceTerms)。这是代码:

const reduceTerms = function(info, terms){
    const initialLhs = terms.shift()
    const initialRhs = terms.shift()
    const initialApp = Application(info, initialLhs, initialRhs, false, 'reduceTerms')

    const appAll = terms.reduce(
        (lhs, rhs) => Application(info, lhs, rhs, false, 'reduceTerms'),
        initialApp
    )

    return appAll ;
}

请忽略布尔值和'reduceTerms'字符串。布尔值用于指示此应用程序是否包含在括号内(将删除括号概念,直到我在本书后面遇到它)。字符串是 how/where 上的标签 我构造了这个应用程序节点的实例(以调试解析器如何应用规则)。

reduceTerms 函数是将数组简单地缩减到应用程序树中,应用左侧应用程序中的术语。初始对象是 terms 数组中左侧两项的应用程序。 reducing 函数会将初始对象作为 lhs,将下一项作为 rhs,这正是您想要的。这是 工作 输出:

{
    "expr": "lmno",
    "ast": "Application::reduceTerms( 
              Application::reduceTerms( 
                Application::reduceTerms(  Variable(l),  Variable(m) ),  
              Variable(n) ),  
            Variable(o) )"
}

这里的一个问题是我需要做一些额外的工作才能使 info 对象准确反映匹配。在此版本中,info 对象包含匹配所有术语的范围。虽然不是很重要,但最好能把所有的东西都绑起来。


所以我仍在寻找一种解决方案,它可以在 PEG 内部执行此操作,而无需在数组上进行匹配并缩减为树。


去除左递归的进展:使用已发布的方法翻译

A -> A α | β

A -> β A'
A' -> α A' | ε

在PEG中我有

TERM_OR_VARIABLE
    = L_PARENS TERM R_PARENS
    / VARIABLE

APP
   = lhs:TERM_OR_VARIABLE rhs:APP_
   {
       return Application(location(), lhs, rhs, false, "APP")
   }

APP_
    = lhs:TERM_OR_VARIABLE rhs:APP_
    {
        return Application(location(), lhs, rhs, false, "APP_")
    }
    / lhs:TERM_OR_VARIABLE END
    {
        return lhs
    }
    / END

END
    = !.

APPLICATION_WO_PARENS
    = APP

我现在能够解析应用程序 lmno 但 AST 是从右到左的应用程序

Application::APP(  
    Variable(l), 
    Application::APP_(  
       Variable(m), 
       Application::APP_(  
          Variable(n),  
          Variable(o) 
       ) 
    ) 
)