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

Javascript 在保持左关联性的同时避免左递归解析Lambda演算,javascript,node.js,lambda-calculus,peg,left-recursion,Javascript,Node.js,Lambda Calculus,Peg,Left Recursion,我正试图利用JavaScript和PEG.JS将lambda演算术语解析为AST。语法相当简单: /***************************************************************** t ::= x variable λx.t

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

/*****************************************************************
        t ::=
                x                                       variable
                λx.t                                    abstraction
                t t                                     application
*****************************************************************/
我从中编码出了销钉:

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,然后应用于该结果。从AST的打印输出可以看出,n应用于m,结果应用于l,这是不正确的

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

我引入了parens的概念,但我停止了将它们集成到。我真的不想让它们出现在语法中

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

  • 有缺陷的方法:我玩过的一种方法是强制匹配两个以上的变量|一行中的抽象,然后左应用它们

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

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

    reduceTerms函数是应用左应用程序中的术语将数组简化为应用程序树。初始对象是术语数组中左两项的应用程序。还原函数将把初始对象作为lhs,下一个项作为rhs,这正是您想要的。以下是工作输出:

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


    因此,我仍然在寻找一种解决方案,在PEG内部实现这一点,而无需在阵列上进行匹配,并简化为树


    删除左递归的进展:使用已发布的方法

    A -> A α | β
    

    我有

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

    我正在做正确的标记,因为我把自己交给了array reduce,这是我目前的最佳选择
    A -> β A'
    A' -> α A' | ε
    
    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
    
    Application::APP(  
        Variable(l), 
        Application::APP_(  
           Variable(m), 
           Application::APP_(  
              Variable(n),  
              Variable(o) 
           ) 
        ) 
    )