【问题标题】:Avoiding Left Recursion parsing Lambda Calculus while maintaining Left Associativity在保持左关联性的同时避免左递归解析 Lambda 演算
【发布时间】:2018-08-18 16:00:26
【问题描述】:

我正在尝试利用 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 ) ) ) ) ) )"
}

问题出在 Left Application m 应该应用于 l,然后 n 应用于该结果。正如您从 AST 的打印输出中看到的那样,n 应用于 m,而该结果应用于不正确的 l

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

我引入了括号的概念 - 但我停止将它们集成。我真的不希望它们出现在语法中。

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

【问题讨论】:

    标签: javascript node.js lambda-calculus peg left-recursion


    【解决方案1】:

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

    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 级的平衡树,而我们想要一个 3 级的不平衡树......所以这是前一个 PEG 的错误输出(输入为 lmno):

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

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


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

    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”字符串。布尔值用于指示此应用程序是否包含在括号中(将删除括号概念,直到我在本书后面遇到它)。字符串是关于我如何/在何处构建此应用程序节点实例的标签(以调试解析器如何应用规则)。

    reduceTerms 函数是在应用程序树中应用左侧应用程序中的项的简单数组缩减。初始对象是 terms 数组中左侧两项的 Application。归约函数会将初始对象作为 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) 
           ) 
        ) 
    )
    

    【讨论】:

    • 我标记为正确,因为我将自己辞职到数组 reduce 作为此时我的最佳选择。
    • 链接到我写的一篇关于我的工作的文章,来源medium.com/@akaphenom/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-16
    • 2014-01-14
    • 1970-01-01
    相关资源
    最近更新 更多