手编码解析器
对于你所有的编译器大师,我想写一个递归下降解析器,我想用代码来完成。 没有从其他语法生成词法分析器和解析器,也不会告诉我阅读龙书,我会最终回答这个问题。
我想深入了解一个合理的简单语言实现一个词法分析器和解析器的细节,比如说CSS。 我想这样做是正确的。
这可能最终会成为一系列问题,但现在我以一个词法分析器开始。 在这里可以找到CSS的令牌化规则。
我发现我的自我编写代码是这样的(希望你可以从这段代码中推断出其他代码):
public CssToken ReadNext()
{
int val;
while ((val = _reader.Read()) != -1)
{
var c = (char)val;
switch (_stack.Top)
{
case ParserState.Init:
if (c == ' ')
{
continue; // ignore
}
else if (c == '.')
{
_stack.Transition(ParserState.SubIdent, ParserState.Init);
}
break;
case ParserState.SubIdent:
if (c == '-')
{
_token.Append(c);
}
_stack.Transition(ParserState.SubNMBegin);
break;
这个叫什么? 我离合理的理解还有多远? 我试图在效率和易用性之间取得平衡,使用堆栈来实现某种状态机工作得很好,但我不确定如何继续这样做。
我拥有的是一个输入流,从中我可以一次读取1个字符。 我现在没有做任何表情,我只是读取角色,然后根据当前状态尝试做些什么。
我真的很想进入编写可重用代码片段的思路。 这种Transition
方法当前意味着要做到这一点,它将弹出堆栈的当前状态,然后以相反的顺序推入参数。 这样,当我写入Transition(ParserState.SubIdent, ParserState.Init)
,它将“调用”子例程SubIdent
,它将在完成时返回Init
状态。
解析器将以几乎相同的方式实现,目前,使用这样的一个大方法实现了所有功能,这使我可以在找到一个令牌时轻松返回令牌,但这也迫使我将所有内容都保存在一个单一的大方法中。 有没有一种很好的方法将这些标记化规则分解为单独的方法?
你正在写的东西叫做下推式自动机。 这通常比你需要编写一个词法分析器的能力更强大,如果你正在为像CSS这样的现代语言编写词法分析器,那肯定是过分的。 递归下降解析器与下推自动机相近,但递归下降解析器更容易编写和理解。 大多数解析器生成器生成下推式自动机。
Lexers几乎总是被编写成有限状态机,也就是说,除了摆脱“堆栈”对象之外,就像你的代码一样。 有限状态机与正则表达式密切相关(实际上,它们可以相互等价)。 设计这样一个解析器时,通常从正则表达式开始,并使用它们来创建确定性有限自动机,在转换中使用一些额外的代码来记录每个标记的开始和结束。
有工具可以做到这一点。 lex
工具及其后代众所周知,并已翻译成多种语言。 ANTLR
工具链也有一个词法分析器组件。 我最喜欢的工具是ragel
在支持它的平台上。 大部分时间手动编写词法分析器几乎没有什么好处,而且这些工具生成的代码可能会更快,更可靠。
如果你确实想用手工编写自己的词法分析器,那么好的经常看起来像这样:
function readToken() // note: returns only one token each time
while !eof
c = peekChar()
if c in A-Za-z
return readIdentifier()
else if c in 0-9
return readInteger()
else if c in ' nrtvf'
nextChar()
...
return EOF
function readIdentifier()
ident = ""
while !eof
c = nextChar()
if c in A-Za-z0-9
ident.append(c)
else
return Token(Identifier, ident)
// or maybe...
return Identifier(ident)
然后,您可以将解析器编写为递归下降解析器。 不要试图将词法分析器/解析器阶段合并为一个,这会导致代码的混乱。 (根据Parsec作者,它也比较慢)。
如果您打算从头开始编写所有代码,我肯定会考虑使用递归体裁解析器。 在你的文章中,你并没有真正说出你在解析源代码后会用令牌流做什么。
有些事情,我会建议得到处理
1.为您的扫描仪/词法分析器设计出色,这将为您的解析器标记源代码。
2.接下来的事情是解析器,如果你的源语言有很好的ebnf,那么解析器通常可以很好地转换成一个递归正确的解析器。
3.您需要真正需要的另一个数据结构是符号表。 这可以像散列表一样简单,也可以像表示复杂记录结构等的树结构一样复杂。我认为对于CSS,您可能介于两者之间。
4.最后你想要处理代码生成。 你在这里有很多选择。 对于翻译,您可以在解析代码时简单地解释。 一个更好的方法可能是生成一个i代码,然后你可以编写一个iterpreter,之后甚至编译器。 当然,对于.NET平台,您可以直接生成IL(可能不适用于CSS :))
对于参考资料,我收集你并没有深入深入的理论,我不怪你。 如果你不介意Pascal,那么获得基础知识的一个非常好的起点就是Jack Crenshaw的“Let's build a compiler”
http://compilers.iecc.com/crenshaw/
祝你好运我相信你会喜欢这个项目。
你需要从你的BNF / EBNF编写你自己的递归下降解析器。 我最近不得不写我自己的,这个页面有很多帮助。 我不确定你的意思是“只用代码”。 你的意思是你想知道如何编写自己的递归解析器?
如果你想这样做,你需要首先使用语法。 一旦你有了EBNF / BNF,解析器就可以很容易地写出来。
我编写解析器时做的第一件事是读取所有内容,然后标记文本。 所以我基本上结束了一系列我认为是堆栈的令牌。 为了减少将值从堆栈中拉出来的冗长/开销,并在不需要时将其重新推回,可以使用peek
方法简单地返回堆栈中的顶层值,而不弹出它。
UPDATE
根据你的评论,我必须从头开始编写一个JavaScript递归下降解析器。 你可以在这里看看解析器。 只需搜索constraints
函数。 我写了自己的tokenize
函数来标记输入。 我还写了另一个便利功能( peek
,我之前提到过)。 解析器在这里根据EBNF进行解析。
这让我花了一点时间才弄清楚,因为我写了一个解析器已经有好几年了(上次我写它在学校!),但相信我,一旦你得到它,你就会得到它。 我希望我的例子能在你的路上继续前进。
另一个更新
我也意识到我的例子可能不是你想要的,因为你可能会使用shift-reduce解析器。 你提到现在你正在尝试写一个标记器。 在我的情况下,我用Javascript写了自己的标记器。 这可能不健壮,但足以满足我的需求。
function tokenize(options) {
var str = options.str;
var delimiters = options.delimiters.split("");
var returnDelimiters = options.returnDelimiters || false;
var returnEmptyTokens = options.returnEmptyTokens || false;
var tokens = new Array();
var lastTokenIndex = 0;
for(var i = 0; i < str.length; i++) {
if(exists(delimiters, str[i])) {
var token = str.substring(lastTokenIndex, i);
if(token.length == 0) {
if(returnEmptyTokens) {
tokens.push(token);
}
}
else {
tokens.push(token);
}
if(returnDelimiters) {
tokens.push(str[i]);
}
lastTokenIndex = i + 1;
}
}
if(lastTokenIndex < str.length) {
var token = str.substring(lastTokenIndex, str.length);
token = token.replace(/^s+/, "").replace(/s+$/, "");
if(token.length == 0) {
if(returnEmptyTokens) {
tokens.push(token);
}
}
else {
tokens.push(token);
}
}
return tokens;
}
根据你的代码,看起来你正在阅读,标记和解析 - 我假设这是一个shift-reduce解析器的作用? 我所拥有的流程首先标记为构建令牌堆栈,然后通过递归下降解析器发送令牌。
链接地址: http://www.djcxy.com/p/66545.html上一篇: hand coding a parser