贴上我今天编的实验性代码,这段代码验证了我的思路的可行性
coffeescript语言:
exports.parse = (element, text) ->
global.text = text
global.cursor = 0
global.tokens = []
global.memo = {}
rules[element](0)
match = (c) ->
if text[cursor] is c
cursor++;
true
rules = {
Binary: (start) ->
"Binary: 1 | Binary+1"
hash = 'Binary'+start
m = memo[hash]
if m is undefined
memo[hash] = rules.Unary(start)
rules.Binary(start)
else
if not match('+') then delete memo[hash]; return m
a = rules.Unary(cursor)
if not a then delete memo[hash]; m
else memo[hash] = m+a; rules.Binary(start)
Unary: (start) ->
c = text[cursor]
switch c
when '+'
cursor++;
if text[cursor]=='+' then cursor++; rules.Unary(cursor)+1;
else rules.Unary(cursor)
when '-'
cursor++;
if text[cursor]=='-' then cursor++; rules.Unary(cursor)-1
else -rules.Unary(cursor)
else
x = rules.Atom(start)
if text[cursor]=='+' and text[cursor+1]=='+' then cursor+=2; x+1
else if text[cursor]=='-' and text[cursor+1]=='-' then cursor+=2; x-1
else x
Atom: (start) ->
switch text[cursor]
when '1' then cursor++; 1
when '(' then cursor++; exp = rules.Binary(cursor); match(')'); exp
}
以下是所通过的测试:
"test parse left recursive": (test) ->
test.equal parse('Binary', '1'), 1
test.equal parse('Binary', '++1'), 2
test.equal parse('Binary', '1++'), 2
test.equal parse('Binary', '1--'), 0
test.equal parse('Binary', '(1--)--'), -1
test.equal parse('Binary', '+1'), 1
test.equal parse('Binary', '-1'), -1
test.equal parse('Binary', '--1'), 0
test.equal parse('Binary', '1+1+1'), 3
test.equal parse('Binary', '1+(1+1)'), 3
test.equal parse('Binary', '(1+1)+(1+1)'), 4
test.equal parse('Binary', '1+(1+1+1)+1'), 5
test.done()
这段代码体现了递归下降分析方法的特点。在上面的代码中,一个函数对应一个文法的非终结符,程序简单直观。以前的问题是,不知道如何在函数调用中避免左递归的无限扩展,本程序的机制很好的实现了扩展和memo的配合。
非常纳闷,为什么如此简单的方法,这么多年来竟然没有被发现?而我自己也是在五年时间内一直保持摸索,尝试过许多别的方法,失败过三四十次,有的方法非常复杂,包括基于python的yield语句(关心的朋友可以搜索一下yieldProlog)的方法,基于后续的方法(continuation-based, cps),但是都不如这种简单方案彻底,有一次看起来已经解决了,但是代码十分复杂和丑陋,另外还有时间和空间的效率问题,我最终还是放弃了。
请问谁能有个合理的解释?
或者是我的想法中有什么漏洞或者错误?
另外,这种新方法,配合python的yield语句,将是一个非常简单、直观、灵活和强大的手写解析器的方案。具体技术可以参看YieldProlog。