感谢各位帮忙,也为自己之前描述问题不清楚感到抱歉。
是这样的,我正在学习 PEG (Parsing Expression Grammar) 来打算自己写 parser,一个简单的模型:
LineBreak <- "\r\n" / "\n" / "r"
Char <- !LineBreak .
Comment <- "//" Char* LineBreak / "/*" (!"*/" Char)* "*/"
左边的是 expression
. 代表任意字符,* 代表可以有零个或者多个前面的 expr,! 是一个 not predicate,如果后面的 expr 匹配成功则失败,失败则成功,但是它不会消耗字符。/ 代表如果前面一个失败了,则选择后面的,即所谓的 prioritized choose。
语文能力拙计,所以描述的大家看不懂请见谅啊……有兴趣可以去看这篇论文:
http://pdos.csail.mit.edu/~baford/packrat/popl04/peg-popl04.pdf然后回到我这个问题。因为我论文没看太懂(后面的数学证明太可怕,我毕竟还年轻),于是打算使用一种比较直观的方式,用 Javascript 动手实现一遍,我是这样定义的。
function (input, index) -> { output, index } / failed
满足这样的函数都被称为 expression,input 是给的字符串,index 代表从哪儿开始 consume,然后返回的 output 是结果,index 是指新的 consume starting position。如果失败了,就返回一个对象 failed,其实就是 var failed = {},Javascript 对象都是指针,我没有理解错吧。
然后我要实现一下上面的这些 not predicate 啊、prioritized choose 啊什么的,就是把 expr(现有函数)给这些高阶函数(是这么称呼吧),返回新的 expr,这样写起来比较直观好玩,当然 stack size 太恐怖,生产环境肯定不会这么用。可能是我最近看 SICP 中毒了吧。
比如说上面的 parse C++ 注释的,就可以这么写:
var LineBreak = PEG['/'](PEG['t']('\r\n'), PEG['t']('\n'), PEG['t']('\r'));
PEG['t'] 接受一个字符串,返回能 consume 这个字符串的 expr。
var Char = PEG['s'](PEG['!'](LineBreak), PEG['.']);
PEG['s'] 就是把几个 expr 连在一起,返回值用数组装着,一个失败了,就全部失败。
var Commnet = PEG['/'](PEG['s'](PEG['t']('//'), PEG['*'](Char), LineBreak), PEG['s'](PEG['t']('/*'), balabala, PEG['t']('*/')));
好吧我知道不能直接写 // 但是为了省事大家就不要在意了。
于是乎,回到我的问题,大家知道很多 expr 需要递归:
CppComment <- // Char* LineBreak
CCommentHelper "*/" / Char CComentHelper
CComment <= "/*" CCommentHelper
var CCommentHelper = ......
在这种情况下,我的那些高阶函数需要操作一个暂时还不存在的函数,如何是好?虽然可以用七楼的办法,但是那样一点都不优雅……实际上我已经放弃自己写 PEG parser 的打算了,只是保留了一份好奇,有没有办法绕过这个问题?^_^