SmaCC 指南

这是一个用于演示一些SmaCC(Smalltalk编译器的编译器)的简要指南。在这个例子中,我们会逐步开发一个简易的计算器。

如果你已经做过这种东西,你可以先 载入代码 。你载入了代码之后,你需要打开SmaCC解释器生成器。在VisualWorks 和 VisualAge中,它在Tools菜单下。Dolphin的在一个额外的工具目录中。它会打开一个类似下面的窗口:

SmaCC Window

我们第一个计算器相对比较简单。它只要能读取两个数字并把它们相加。开始之前,我们首先要告诉扫描程序如何辨认一个数字。数字由一个或多个数字打头,后面可能还有一个小数点加上0或者更多的数字。扫描程序对这个标记的定义是:

把这行代码输入界面上的scanner标签页中。让我们逐个看每一个部分:

<number>

指出记号的名字。在<>中的名称必须是合法的Smalltalk变量名。

:

分隔记号名称和记号定义。

[0-9]

匹配任何一个在’0’到’9’(一个数字)范围中的字符。

+

匹配前面的表达式一次或多次。在这种情况下,我们要匹配一个或多个数字。

( … )

标示子表达式组。

\.

匹配 ‘.’ 字符(. 在正则表达式中有特殊的含义,使用 \ 来转义)。

*

匹配前一个表达式零次或多次。

?

匹配前面的表达式零次或一次。(也就是,前面表达式是可选的)。

;

终止一个记号说明。

我们不想去关心我们语言中的空白符,所以我们需要定义什么是空白符并且忽略它,输入下面的记号说明:

\s 会匹配任何空白字符(空格、制表符、换行、回车等等)。然后我们怎么告诉扫描程序去忽略它呢?
如果你看一下SmaCCScanner类,你会发现一个叫做’whitespace’的方法。如果一个扫描程序有一个方法的名称和某个标记一样,那么一旦扫描程序匹配了这类标记就会调用这个方法。正如你所见,whitespace方法会吃掉空白符。同样还有一个’comment’方法会作类似的处理。

说到我们的语法,现在让我们来定义它吧。在Parser表格中输入以下语法说明:

这基本上指出了一个表达式可以是一个数字或者是另一个表达式加上一个数字。

我们现在应该可以编译一个分析器了。切换到Compile标签页。你要输入扫描器和分析器的类的名称。这里我们相应使用CalculatorScanner 和CalculatorParser。当类名输完之后,我们就准备编译分析器了。点击 ‘Compile LARLR(1)’
按钮(你应该总是点这个按钮除非你知道你要做什么。一般来说,他会生成比另一个选项更小的分析器)。这时就会生成新的CalculatorScanner和CalculatorParser的Smalltalk类同时会编译这两个类中的一些方法。所有的SmaCC编译出来的方法会按照”generated-*”的格式。你不可以更改这些方法因为每次你重新编译他们都会被覆盖。

不管SmaCC何时创建新类,这些类都会被放到默认的应用程序/包中。如果你使用的是VisualAge,你要确保默认应用程序是开放的版本而且SmaCCRuntion应用程序已经安装(prereq)。

如果你已经生成了扫描器和分析器类,你可以通过类名旁边的”…”按钮来载入他们的定义。如果在出现的对话框中你回答”Yes”,那么在Scanner/Parser标签页中的文本就会被替换为上一次编译过的定义(假设”Generate definition comments”在上一次编译中被选中了)。

现在我们要测试我们的分析器了。进入“test”面板,输入“ 3 + 4”(不要加双引号),并且点击“parse”按钮;你会看到分析器正确分析了它。如果你点击“Parse and Inspect”你会看到一个检视器(inspector),里面有一个包含了被解析的记号的顺序集合(OrderedCollection)。这是因为我们没有指明当分析器在解析的时候要怎么处理记号。你也可以是如一个不正确的内容。例如尝试解析“3 + + 4”或者“3 + a”。应该会出现一个错误信息。

现在我们要定义当我们分析我们的表达式的时候要产生的动作。当前的情况是,我们的分析器仅仅验证表达式是一些相加的数字。一般来说你要创建一些结果来表示你已经解析了什么内容(比如,一个棵分析树)。然而,在这个情况下,我们不关心结构,我们只关心结果(表达式的值)。在我们的例子中,你需要把语法定义改成如下:

括号中的文本是Smalltalk代码,当应用规则的时候,就会执行这个代码。有一个数字的字符串会被替换成相应的解析节点。在第一个Expression的规则中,’1’会被替换成匹配Expression的ParseNode同时’3’会被替换成匹配Number的ParseNode。在规则中的第二个东西是’+’记号。因为我们已经知道它是什么了,所以我们对他不感兴趣。编译新的分析器。现在当你从Test面板中执行’Parse and Inspect’时,你应该看到这个结果:7。

前面的代码有一个问题是如果你需要更改一个规则,你可能也要跟着修改规则内的代码。例如,假设你你在规则的开头添加了一个新的记号,那么你就要更改所有在Smalltalk代码中的引用了。我们可以通过使用命名表达式来减少这种问题。在规则的每个部分后面,我们可以指明它的名称。名称是通过单引号来标明的,它同样必须是一个合法的Smalltalk变量名。象下面这个:

它和前面解析的语言的结果是一样的,但它同时让你更容易维护的你分析器。让我们现在扩展我们的语言并加入减法功能。这里是新的语法:

你编译了这个代码之后,’3 + 4 – 2’就会返回’5’了。下面,再让我们加入乘法和减法:

这时我们遇到一个问题.如果你计算” 2 + 3 * 4“,最后的结果将是 20。这个问题是因为在标准的数学中,乘法比加法有更高的优先级。我们的语法是严格按照从左到右的方式运算的。这个问题一般的解决方法是定义加法的非终结符来强制计算的顺序。这个解决方法的语法类似:

这时候如果你编译这个语法,你会看到” 2 + 3 * 4 “的计算结果是14,正如我们所期望的那样。现在,正如你可以想象的,当优先级规则的数量增加时,语法也越来越复杂(例如,C语言)。我们可以使用歧义语法和优先级规则来简化这种情况。这里有一段使用优先级来限制计算顺序的一段语法:

注意我们更改了语法所以操作符两边都是Expression。我们在语法顶部添加的两行表示“+”和“-”是从左至右运算的而且优先级相同,同时他们的优先级比“*”和“/”低。类似的,第二行表示“*”和“/”有同样的优先级。这个形式的语法通常更加直观,特别是当有很多优先级要处理的时候。我们再来一个例子,现在加入指数运算和括号:

当你编译了这个语法之后,你就可以正确计算” 3 + 4 * 5 ^ 2 ^ 2“得到2503了。由于这个指数操作是右结合的,所以这个表达式是象这样计算的3 + (4 * (5 ^ (2 ^ 2)))。我们也可以计算带括号的表达式。例如,计算 ” (3 + 4) * (5 – 2) ^ 3 “将得到189。

发表评论

电子邮件地址不会被公开。 必填项已用*标注