6
社区成员
发帖
与我相关
我的任务
分享回到第 8 集中,我们迈出了创建新编程语言的第一步,并实现了分词器。如果你没有读过那一集,我建议你现在就去看看,否则这一集可能会有点混乱。
我们将创建一种非常简单的数学语言。
每行一个statement,并且只有这些语句:
read nameprint nameset name = expression任何空行都将被忽略。#将开始一条评论,直到当前行结束。
所以一个程序可能看起来像这样:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code># Program for converting miles to km
read miles
set kms = miles * 1.60934
print kms
</code></span></span>
expression?很清楚 astatement是什么,但是 an 是什么expression?
让我们写下可能性。这些对于我们的简单语言来说就足够了:
1.60934( expression )expression + expressionexpression - expressionexpression * expressionexpression / expression我们拥有完整的语言!
嗯,差不多。只有很小的歧义问题。现在,该语言不知道是3 + 4 * 5指3 + ( 4 * 5 )还是(3 + 4) * 5。它也不知道是否2 - 3 - 4是2 - (3 - 4)或(2 - 3) - 4。
在使用中有两种方法可以解决这个问题。一个是告诉我们的解析器“运算符优先级”(if +or *goes first),以及每个运算符的关联性(左或右或不允许 - 它决定了2 - 3 - 4解析的方式)。
另一种是重写语法以消除这种歧义,这通常是更简单的方法。
而不是有expression6 种可能性,它现在只有这些:
expression + productexpression - productproduct然后product是:
product * factorproduct / factorfactor并且factor是:
( expression )number这样表达,不可能有歧义。你可以自己验证。除非我们使用括号,否则产品不可能+在两边都有任何内容,因为它所引用的规则都没有+。并且2 - 3 - 4不可能意味着2 - (3 - 4)因为右边-是一个product,所以它不能有任何东西-,没有明确的括号。
顺便说一句,不要过多考虑诸如product或 之类的名称factor。我自己并不觉得它们非常直观。你可以称它为expression2,expression3等等。
许多“解析器生成器”工具接受我们刚刚提出的定义。不幸的是,对于这一集,我们不会使用解析器生成器,我们将自己编写递归下降解析器,它需要对语法进行一些调整。
我还没有讨论递归下降解析器是什么。我们很快就会开始。
但是 Recursive Descent Parser 的一个很大的局限是没有任何解析规则可以在左边引用它自己,因为那样它会进入无限递归循环。
因此,让我们以不会出现此问题的方式重写我们的定义:
expression是:
product ( ("+"|"-") product )*product是:
factor ( ("*"|"/") factor ]*)并且factor是:
"(" expression ")"number我不得不稍微扩展一下符号。a|b意味着a或b。( ... )*表示括号中的零个或多个。由于现在有特殊字符,我引用所有文字符号,如"+", 或"("。
好的,让我们从分词器开始。它将使用StringScannerRuby 标准库和一些正则表达式。这是我们数学语言的分词器程序:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)">#!/usr/bin/env ruby</span>
<span style="color:var(--syntax-text-color)">require</span> <span style="color:var(--syntax-string-color)">"strscan"</span>
<span style="color:var(--syntax-text-color)">require</span> <span style="color:var(--syntax-string-color)">"pathname"</span>
<span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">MathLanguage</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">initialize</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">path</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">@lines</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">Pathname</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">path</span><span style="color:var(--syntax-text-color)">).</span><span style="color:var(--syntax-name-color)">readlines</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">tokenize</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">string</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">scanner</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">StringScanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">new</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">string</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">tokens</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">[]</span>
<span style="color:var(--syntax-declaration-color)">until</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">eos?</span>
<span style="color:var(--syntax-declaration-color)">if</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">scan</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">/\s+/</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-comment-color)"># do nothing</span>
<span style="color:var(--syntax-declaration-color)">elsif</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">scan</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">/#.*/</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-comment-color)"># comments - ignore rest of line</span>
<span style="color:var(--syntax-declaration-color)">elsif</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">scan</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">/-?\d+(?:\.\d*)?/</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">tokens</span> <span style="color:var(--syntax-error-color)"><<</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-string-color)">"number"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">value: </span><span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">matched</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">to_f</span> <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">elsif</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">scan</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">/[\+\-\*\/=()]|set|read|print/</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">tokens</span> <span style="color:var(--syntax-error-color)"><<</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">matched</span> <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">elsif</span> <span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">scan</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">/[a-zA-Z][a-zA-Z0-9]*/</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">tokens</span> <span style="color:var(--syntax-error-color)"><<</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-string-color)">"id"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">value: </span><span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">matched</span> <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">else</span>
<span style="color:var(--syntax-declaration-color)">raise</span> <span style="color:var(--syntax-string-color)">"Invalid character </span><span style="color:var(--syntax-string-color)">#{</span><span style="color:var(--syntax-text-color)">scanner</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">rest</span><span style="color:var(--syntax-string-color)">}</span><span style="color:var(--syntax-string-color)">"</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-text-color)">tokens</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">call</span>
<span style="color:var(--syntax-text-color)">@lines</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-declaration-color)">do</span> <span style="color:var(--syntax-error-color)">|</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-error-color)">|</span>
<span style="color:var(--syntax-text-color)">tokens</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">tokenize</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">puts</span> <span style="color:var(--syntax-string-color)">"Line: </span><span style="color:var(--syntax-literal-color)">\"</span><span style="color:var(--syntax-string-color)">#{</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">chomp</span><span style="color:var(--syntax-string-color)">}</span><span style="color:var(--syntax-literal-color)">\"</span><span style="color:var(--syntax-string-color)"> has tokens:"</span>
<span style="color:var(--syntax-text-color)">tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-declaration-color)">do</span> <span style="color:var(--syntax-error-color)">|</span><span style="color:var(--syntax-text-color)">token</span><span style="color:var(--syntax-error-color)">|</span>
<span style="color:var(--syntax-text-color)">p</span> <span style="color:var(--syntax-text-color)">token</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">unless</span> <span style="color:var(--syntax-declaration-color)">ARGV</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">size</span> <span style="color:var(--syntax-error-color)">==</span> <span style="color:var(--syntax-literal-color)">1</span>
<span style="color:var(--syntax-declaration-color)">STDERR</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">puts</span> <span style="color:var(--syntax-string-color)">"Usage: </span><span style="color:var(--syntax-string-color)">#{</span><span style="color:var(--syntax-text-color)">$0</span><span style="color:var(--syntax-string-color)">}</span><span style="color:var(--syntax-string-color)"> file.math"</span>
<span style="color:var(--syntax-text-color)">exit</span> <span style="color:var(--syntax-literal-color)">1</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">MathLanguage</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">new</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-declaration-color)">ARGV</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">]).</span><span style="color:var(--syntax-name-color)">call</span>
</code></span></span>
如果我们运行它,我们可以看到它把所有行都变成了标记,并跳过注释:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ ./math_tokenizer.rb miles_to_km.math
Line: "# Program for converting miles to km" has tokens:
Line: "read miles" has tokens:
{:type=>"read"}
{:type=>"id", :value=>"miles"}
Line: "set kms = miles * 1.60934" has tokens:
{:type=>"set"}
{:type=>"id", :value=>"kms"}
{:type=>"="}
{:type=>"id", :value=>"miles"}
{:type=>"*"}
{:type=>"number", :value=>1.60934}
Line: "print kms" has tokens:
{:type=>"print"}
{:type=>"id", :value=>"kms"}
</code></span></span>
我们有一个定义列表。现在我一直在暗示的这个递归下降解析器是什么?
它只是一堆方法(或者函数,如果你不喜欢 OOP 术语)——每个语法规则一个。每个方法都使用左边的一些标记,并返回解析后的内容。
对于 Recursive Descent Parser,规则的方法X应该期望它X位于列表的左侧,如果不是则引发异常 - 但它们应该可以接受一些剩余的标记。
因为我们只是解析而不执行任何东西,所以现在我们的循环看起来像这样:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code> <span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">call</span>
<span style="color:var(--syntax-text-color)">@lines</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-declaration-color)">do</span> <span style="color:var(--syntax-error-color)">|</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-error-color)">|</span>
<span style="color:var(--syntax-text-color)">@tokens</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">tokenize</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">next</span> <span style="color:var(--syntax-declaration-color)">if</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">empty?</span>
<span style="color:var(--syntax-text-color)">statement</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_statement</span>
<span style="color:var(--syntax-declaration-color)">raise</span> <span style="color:var(--syntax-string-color)">"Extra tokens left over"</span> <span style="color:var(--syntax-declaration-color)">unless</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">empty?</span>
<span style="color:var(--syntax-text-color)">pp</span> <span style="color:var(--syntax-text-color)">statement</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
</code></span></span>
我保存@tokens到实例变量,所以我们不需要将它传递给每个方法。
同样为了简化代码,这里有一些辅助方法。next_token_is?检查下一个标记是否属于任何预期类型并返回trueor false。expect_token如果它是预期类型之一,则转移令牌,如果它是错误类型,则引发异常:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code> <span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">next_token_is?</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-error-color)">*</span><span style="color:var(--syntax-text-color)">types</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">]</span> <span style="color:var(--syntax-error-color)">&&</span> <span style="color:var(--syntax-text-color)">types</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">include?</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">][</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">])</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-error-color)">*</span><span style="color:var(--syntax-text-color)">types</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">raise</span> <span style="color:var(--syntax-string-color)">"Parse error"</span> <span style="color:var(--syntax-declaration-color)">unless</span> <span style="color:var(--syntax-text-color)">next_token_is?</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-error-color)">*</span><span style="color:var(--syntax-text-color)">types</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">shift</span>
<span style="color:var(--syntax-declaration-color)">end</span>
</code></span></span>
然后,这是我们的解析器:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code> <span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">parse_factor</span>
<span style="color:var(--syntax-text-color)">token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"number"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"id"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"("</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">case</span> <span style="color:var(--syntax-text-color)">token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"number"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"id"</span>
<span style="color:var(--syntax-text-color)">token</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"("</span>
<span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_expression</span>
<span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">")"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">result</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">parse_product</span>
<span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_factor</span>
<span style="color:var(--syntax-declaration-color)">while</span> <span style="color:var(--syntax-text-color)">next_token_is?</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"*"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"/"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">op_token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">shift</span>
<span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-text-color)">op_token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">],</span> <span style="color:var(--syntax-string-color)">left: </span><span style="color:var(--syntax-text-color)">result</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">right: </span><span style="color:var(--syntax-text-color)">parse_factor</span><span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-text-color)">result</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">parse_expression</span>
<span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_product</span>
<span style="color:var(--syntax-declaration-color)">while</span> <span style="color:var(--syntax-text-color)">next_token_is?</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"+"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"-"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">op_token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">shift</span>
<span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-text-color)">op_token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">],</span> <span style="color:var(--syntax-string-color)">left: </span><span style="color:var(--syntax-text-color)">result</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">right: </span><span style="color:var(--syntax-text-color)">parse_product</span><span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-text-color)">result</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">parse_statement</span>
<span style="color:var(--syntax-text-color)">token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"read"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"set"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"print"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">case</span> <span style="color:var(--syntax-text-color)">token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"read"</span>
<span style="color:var(--syntax-text-color)">token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"id"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-string-color)">"read"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">id: </span><span style="color:var(--syntax-text-color)">token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:value</span><span style="color:var(--syntax-text-color)">]}</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"set"</span>
<span style="color:var(--syntax-text-color)">var_token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"id"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"="</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">expr</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_expression</span>
<span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-string-color)">"set"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">id: </span><span style="color:var(--syntax-text-color)">var_token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:value</span><span style="color:var(--syntax-text-color)">],</span> <span style="color:var(--syntax-string-color)">expr: </span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"print"</span>
<span style="color:var(--syntax-text-color)">token</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">expect_token</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"id"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-string-color)">type: </span><span style="color:var(--syntax-string-color)">"print"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">id: </span><span style="color:var(--syntax-text-color)">token</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:value</span><span style="color:var(--syntax-text-color)">]}</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
</code></span></span>
我建议好好看看这段代码。Recursive Descent Parsers 的可读性非常好,但由于它可能不是您以前见过的代码类型,因此一开始可能有点令人困惑。
我们可以试一试:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ ./math_parser.rb miles_to_km.math
{:type=>"read",
:id=>"miles"}
{:type=>"set",
:id=>"kms",
:expr=>
{:type=>"*",
:left=>{:type=>"id", :value=>"miles"},
:right=>{:type=>"number", :value=>1.60934}}}
{:type=>"print",
:id=>"kms"}
</code></span></span>
递归下降解析器相对于解析器生成器的一个巨大优势是潜在的错误报告。递归下降更接近人类的思维方式,所以如果有错误,我们可以这样说At line 20 char 1: expected one of "read", "set", or "print", but got token: "number"。
由于复杂的技术原因,解析器生成器创建的解析器通常会报告非常糟糕的错误。
我提到这一点,为了简单起见,对于这一集,我们不会做任何事情raise "Parse error",但我们可以,而且我们将来会。
为了运行代码,我们稍微改变了循环:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code> <span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">call</span>
<span style="color:var(--syntax-text-color)">@variables</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">{}</span>
<span style="color:var(--syntax-text-color)">@lines</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-declaration-color)">do</span> <span style="color:var(--syntax-error-color)">|</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-error-color)">|</span>
<span style="color:var(--syntax-text-color)">@tokens</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">tokenize</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">line</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">next</span> <span style="color:var(--syntax-declaration-color)">if</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">empty?</span>
<span style="color:var(--syntax-text-color)">statement</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">parse_statement</span>
<span style="color:var(--syntax-declaration-color)">raise</span> <span style="color:var(--syntax-string-color)">"Extra tokens left over"</span> <span style="color:var(--syntax-declaration-color)">unless</span> <span style="color:var(--syntax-text-color)">@tokens</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">empty?</span>
<span style="color:var(--syntax-text-color)">eval_statement</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
</code></span></span>
以及评估已解析语句和表达式的代码:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code> <span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">eval_expression</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">case</span> <span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"number"</span>
<span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:value</span><span style="color:var(--syntax-text-color)">]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"id"</span>
<span style="color:var(--syntax-text-color)">@variables</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:value</span><span style="color:var(--syntax-text-color)">]]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"+"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"-"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"*"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-string-color)">"/"</span>
<span style="color:var(--syntax-text-color)">left</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">eval_expression</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:left</span><span style="color:var(--syntax-text-color)">])</span>
<span style="color:var(--syntax-text-color)">right</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">eval_expression</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:right</span><span style="color:var(--syntax-text-color)">])</span>
<span style="color:var(--syntax-text-color)">left</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">send</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">expr</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">],</span> <span style="color:var(--syntax-text-color)">right</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">else</span>
<span style="color:var(--syntax-declaration-color)">raise</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">def</span> <span style="color:var(--syntax-name-color)">eval_statement</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-declaration-color)">case</span> <span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:type</span><span style="color:var(--syntax-text-color)">]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"read"</span>
<span style="color:var(--syntax-text-color)">@variables</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:id</span><span style="color:var(--syntax-text-color)">]]</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">STDIN</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">readline</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">to_f</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"print"</span>
<span style="color:var(--syntax-text-color)">puts</span> <span style="color:var(--syntax-text-color)">@variables</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:id</span><span style="color:var(--syntax-text-color)">]]</span>
<span style="color:var(--syntax-declaration-color)">when</span> <span style="color:var(--syntax-string-color)">"set"</span>
<span style="color:var(--syntax-text-color)">@variables</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:id</span><span style="color:var(--syntax-text-color)">]]</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">eval_expression</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">statement</span><span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-string-color)">:expr</span><span style="color:var(--syntax-text-color)">])</span>
<span style="color:var(--syntax-declaration-color)">else</span>
<span style="color:var(--syntax-declaration-color)">raise</span>
<span style="color:var(--syntax-declaration-color)">end</span>
<span style="color:var(--syntax-declaration-color)">end</span>
</code></span></span>
我离开else raise那里去捕捉编程错误。如果我们正确编码,它永远不会到达。
如果您不熟悉 Ruby,left.send(expr[:type], right)则等同于left + right、left - right、left * right或left / right取决于expr[:type]。
这就是我们创建合适的编程语言所需的全部:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ ./math.rb miles_to_km.math
500
804.67
</code></span></span>
我们不需要任何特殊工具,只需要一些用于分词器的正则表达式,以及一些用于解析器和执行的普通递归方法。
这种方法非常适合简单的编程语言。您只需添加更多的标记类型、更多的语法规则、一些错误处理和一些有趣的解释器,您就可以拥有满足您需要的体面的语言。没有什么是特定于 Ruby 的——您可以使用任何语言。正则表达式支持绝对是一大优势,但人们也一直在编写不带正则表达式的分词器,一次只看一个字符。
关注我的博客,您将在其中获得提示、技巧和挑战,以保持您的技能敏锐。记得关注我哦!