在保持左关联性的同时避免左递归解析Lambda演算

时间:2018-08-18 16:00:26

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

我试图利用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 ,这是不正确的。 / p>

如果我更改了防止左递归问题的规则,则应用程序假定左侧仅是变量或包含应用可能性的抽象-那么就存在递归问题。

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

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

1 个答案:

答案 0 :(得分:0)

缺陷方法:我戏弄过的一种方法是强制匹配两个以上的变量|连续提取并应用抽象

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

当应用程序为三个术语时,哪个看起来可以工作。当有四个时,我们得到一个两级的平衡树,而我们想要一个三级的不平衡树...因此,这是前一个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 ;
}

请忽略boolean和'reduceTerms'字符串。布尔值用于指示此应用程序是否包含在parens中(打算删除parens概念,直到我在本书后面遇到它时为止)。字符串是我如何/在何处构造“应用程序”节点实例的标签(以调试解析器如何应用规则)。

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) 
       ) 
    ) 
)