构建解析器(第一部分)
Posted
技术标签:
【中文标题】构建解析器(第一部分)【英文标题】:Building a parser (Part I) 【发布时间】:2012-03-16 04:36:26 【问题描述】:我正在制作自己的基于 javascript 的编程语言(是的,这很疯狂,但它仅供学习......也许?)。好吧,我正在阅读有关解析器的信息,第一遍是将代码源转换为标记,例如:
if(x > 5)
return true;
标记器到:
T_IF "if"
T_LPAREN "("
T_IDENTIFIER "x"
T_GT ">"
T_NUMBER "5"
T_RPAREN ")"
T_IDENTIFIER "return"
T_TRUE "true"
T_TERMINATOR ";"
我暂时不知道我的逻辑是否正确。在我的解析器上它甚至更好(或不是?)并转换为它(是的,多维数组):
T_IF "if"
T_EXPRESSION ...
T_IDENTIFIER "x"
T_GT ">"
T_NUMBER "5"
T_CLOSURE ...
T_IDENTIFIER "return"
T_TRUE "true"
我有一些疑问:
-
我的方式比原始方式更好还是更差?请注意,我的代码将被读取和编译(翻译成另一种语言,如 php),而不是一直被解释。
在我分词器之后,我到底需要做什么?我真的迷路了!
有一些很好的教程可以学习如何做到这一点?
嗯,就是这样。再见!
【问题讨论】:
嘿,制作编程语言并不疯狂。这里的很多人都在做同样的事情。 你试过龙书吗?基本上你所说的第一个阶段是词法分析阶段,然后是实际的句法解析阶段 -> 理想情况下输出某种 AST(抽象语法树),然后您可以对其进行语义分析(解析)或转换为目标语言 @IntermediateHacker 哈哈...是的,crazy 部分是对一个人来说非常复杂。但是学习是一件非常好的事情,真的。对于专业用途,我想需要一个团队,所以一个人做是疯狂的。 :p 【参考方案1】:通常,您希望将标记器(也称为 lexer)的功能与编译器或解释器的其他阶段分开。这样做的原因是基本的模块化:每次传递都会消耗一种东西(例如字符)并产生另一种东西(例如令牌)。
所以你已经将你的字符转换为标记。现在你想将你的标记列表转换为有意义的嵌套表达式,这就是通常所说的解析。对于类似 JavaScript 的语言,您应该查看 recursive descent parsing。对于解析具有不同优先级的中缀运算符的表达式,Pratt parsing 非常有用,对于特殊情况,您可以使用普通的递归下降解析。
根据您的情况给您一个更具体的示例,我假设您可以编写两个函数:accept(token)
和 expect(token)
,它们会测试您创建的流中的下一个令牌。您将为您的语言语法中的每种类型的语句或表达式创建一个函数。下面是 statement()
函数的 Pythonish 伪代码,例如:
def statement():
if accept("if"):
x = expression()
y = statement()
return IfStatement(x, y)
elif accept("return"):
x = expression()
return ReturnStatement(x)
elif accept("")
xs = []
while True:
xs.append(statement())
if not accept(";"):
break
expect("")
return Block(xs)
else:
error("Invalid statement!")
这为您提供了程序的所谓抽象语法树 (AST),然后您可以对其进行操作(优化和分析)、输出(编译)或运行(解释)。
【讨论】:
【参考方案2】:大多数工具包将整个过程分成两个独立部分
词法分析器(又名标记器) 解析器(又名语法)tokenizer 将输入数据拆分为token。解析器将仅对令牌“流”进行操作并构建结构。
您的问题似乎集中在标记器上。但是您的第二个解决方案将语法解析器和标记器混合为一个步骤。理论上这也是可能的,但对于初学者来说, 使用与大多数其他工具/框架相同的方式更容易:保持步骤分开。
对于您的第一个解决方案:我会像这样标记您的示例:
T_KEYWORD_IF "if"
T_LPAREN "("
T_IDENTIFIER "x"
T_GT ">"
T_LITARAL "5"
T_RPAREN ")"
T_KEYWORD_RET "return"
T_KEYWORD_TRUE "true"
T_TERMINATOR ";"
在大多数语言中,关键字不能用作方法名、变量名等。这已经反映在标记器级别(T_KEYWORD_IF
、T_KEYWORD_RET
、T_KEYWORD_TRUE
)。
下一个级别将采用此流,并通过应用形式语法构建一些数据结构(通常称为 AST - 抽象语法树),可能如下所示:
IfStatement:
Expression:
BinaryOperator:
Operator: T_GT
LeftOperand:
IdentifierExpression:
"x"
RightOperand:
LiteralExpression
5
IfBlock
ReturnStatement
ReturnExpression
LiteralExpression
"true"
ElseBlock (empty)
手动实现解析器通常由某些框架完成。手动和有效地实施类似的事情通常是在大学的一个学期的大部分时间里完成的。所以你真的应该使用某种框架。
语法解析器框架的输入通常是某种形式的语法BNF。您的“如果”部分可能如下所示:
IfStatement: T_KEYWORD_IF T_LPAREN Expression T_RPAREN Statement ;
Expression: LiteralExpression | BinaryExpression | IdentifierExpression | ... ;
BinaryExpression: LeftOperand BinaryOperator RightOperand;
....
这只是为了得到这个想法。 正确解析像 Javascript 这样的真实语言并不是一件容易的事。但是很有趣。
【讨论】:
【参考方案3】:我的方式比原来的方式更好还是更差?请注意,我的代码将被读取和编译(翻译成另一种语言,如 PHP),而不是一直解释。
原来的方式是什么?有许多不同的方式来实现语言。我认为你的实际上很好,我曾经尝试自己构建一种翻译成 C# 的语言,hack programming language。许多语言编译器会翻译成中间语言,这很常见。
在我分词器之后,我到底需要做什么?我真的迷路了!
标记化后,您需要解析它。使用一些好的词法分析器/解析器框架,例如Boost.Spirit,或 Coco,或其他。有数百个。或者您可以实现自己的词法分析器,但这需要时间和资源。解析代码的方式有很多,我一般都是靠recursive descent parsing。
接下来您需要进行代码生成。这是我认为最困难的部分。也有工具,但如果你愿意,你可以手动完成,我尝试在我的项目中这样做,但它非常基本且有问题,有一些有用的代码 here 和 here。
有一些很好的教程可以学习如何做到这一点?
正如我之前建议的,使用 tools 来完成。有很多非常好的有据可查的解析器框架。有关更多信息,您可以尝试询问一些了解此内容的人。 @DeadMG,在Lounge C++ 正在构建一种名为“Wide”的编程语言。你可以试着咨询他。
【讨论】:
【参考方案4】:假设我在编程语言中有这样的语句:
if (0 < 1) then
print("Hello")
词法分析器会将其翻译成:
keyword: if
num: 0
op: <
num: 1
keyword: then
keyword: print
string: "Hello"
解析器然后将获取信息(又名“令牌流”)并进行以下操作:
if:
expression:
<:
0, 1
then:
print:
"Hello"
我不知道这是否会有所帮助,但我希望它会有所帮助。
【讨论】:
这是一个新问题还是试图改写其他答案之一?以上是关于构建解析器(第一部分)的主要内容,如果未能解决你的问题,请参考以下文章