我试图利用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>
如果我更改了防止左递归问题的规则,则应用程序假定左侧仅是变量或包含应用可能性的抽象-那么就存在递归问题。
我介绍了原语的概念-但我停止将它们整合。我真的不希望它们出现在语法中。
答案 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)
)
)
)