我试图利用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,这是不正确的。
如果我更改了用于防止左递归问题的规则,其中应用程序假定左侧只是一个变量或一个抽象,以包含应用程序的可能性,那么就会出现递归问题。
我引入了父母的概念--但我停止了将他们整合进来。我真的不想让他们出现在语法里。
发布于 2018-08-18 20:07:07
有缺陷的方法:我玩过的一种方法是强制在一行中匹配两个以上的变量,然后继续应用它们
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 (或其他)的申请数量。解决方案将是一个真正的黑客和脆弱的。
Working Hack Approach:想要一起破解一些东西,我改变了将一系列术语作为应用程序进行匹配的方法:
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概念,直到我在本书后面遇到它)。字符串是关于我如何/在何处构造这个应用程序节点实例的标签(用于调试解析器如何应用规则)。
reduceTerms函数是将数组简化为应用程序树,应用左侧应用程序中的术语。初始对象是术语数组中左边两个项的应用程序。减少功能将把最初的对象作为lhs,下一个术语作为rhs,这正是你想要的。以下是工作输出:
{
"expr": "lmno",
"ast": "Application::reduceTerms(
Application::reduceTerms(
Application::reduceTerms( Variable(l), Variable(m) ),
Variable(n) ),
Variable(o) )"
}这里的一个问题是,我需要做一些额外的工作,以获得信息对象,以准确地反映匹配。在这个版本中,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)
)
)
)https://stackoverflow.com/questions/51910361
复制相似问题