我正在制作自己的基于javascript的编程语言(是的,它很疯狂,但这只是为了学习...... 也许?)。好吧,我正在阅读解析器,第一步是将代码源转换为令牌,如:
if(x > 5)
return true;
Tokenizer:
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"
我有些疑惑:
嗯,就是这样。再见!
答案 0 :(得分:20)
通常,您希望将tokenizer(也称为 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),然后您可以操作(优化和分析),输出(编译)或运行(解释)。
答案 1 :(得分:18)
大多数工具包将整个流程拆分为两个单独的部分
标记生成器会将输入数据拆分为标记。解析器只对令牌" stream"进行操作。并建立结构。
你的问题似乎集中在标记器上。但是你的第二个解决方案将语法分析器和标记器混合成一步。从理论上讲,这也是可能的,但对于初学者而言,与大多数其他工具/框架一样, 更容易做到这一点:保持步骤分离。
对你的第一个解决方案:我会像你这样标记你的例子:
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 ";"
在大多数语言中, keywords 不能用作方法名,变量名等。这已在令牌化程序级别(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的形式语法。你的"如果"部分migh看起来像这样:
IfStatement: T_KEYWORD_IF T_LPAREN Expression T_RPAREN Statement ;
Expression: LiteralExpression | BinaryExpression | IdentifierExpression | ... ;
BinaryExpression: LeftOperand BinaryOperator RightOperand;
....
这只是为了得到这个想法。正确解析像Javascript 这样的真实世界语言并非易事。但很有趣。
答案 2 :(得分:1)
原始方式的方式是好还是坏?请注意,我的代码将被读取和编译(翻译成另一种语言,如PHP),而不是一直解释。
什么是原创方式?有许多不同的方法来实现语言。我认为你的确很好,我曾经试图自己构建一种语言,转换为C#,hack programming language。许多语言编译器都转换为中间语言,这很常见。
在我的标记器之后,我需要做什么呢?我真的迷失了这个传球!
标记后,您需要解析它。使用一些好的词法分析器/解析器框架,例如Boost.Spirit或Coco,或其他任何东西。有数百个。或者你可以实现自己的词法分析器,但这需要时间和资源。有很多方法可以解析代码,我通常依赖recursive descent parsing。
接下来,您需要执行代码生成。这是我认为最困难的部分。也有这样的工具,但你可以手动做,如果你想,我试着在我的项目中做,但它是非常基本和错误,有一些有用的代码here和here
有一些很好的教程可以学习如何做到这一点吗?
正如我之前建议的那样,使用工具来完成它。有很多很好的文档解析器框架。有关详细信息,您可以尝试询问一些了解这些内容的人。 @DeadMG,Lounge C++正在构建一种名为“Wide”的编程语言。你可以尝试咨询他。
答案 3 :(得分:0)
假设我使用一种编程语言来表达这一观点:
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"
我不知道这是否会有所帮助,但我希望这样做。