A very brief introduction to Aurum
Aurum是一個用Ruby實現的LALR(n) parser generator(是的,又是一個parser generator),不過它和其他一些廣泛應用的parser generator相比略有不同的:
1.Aurum的主要目標之一,是簡化external DSL的開發(尤其是ruby external DSL)。
2.Aurum采用增量LALR(n)算法,而不是通常的LALR(1)。這意味著:
a.不必由于LALR(1)能力的限制,而改寫語法,很多在LALR(1)中沖突的語法在LALR(n)中可以比較自然地表達。
b.由于識別能力的增強,可以處理一些比較復雜的語法,比如COBOL(LALR(2)或LALR(3)),比如一些簡化的自然語言(LALR(3+))。
c.處理能力接近Generalized LR,卻快很多
d.比起Full LALR/LR(n),增量算法生成的語法表更小。
3.出于簡化external DSL實現的考慮,Aurum支持語法重用。
4.Aurum采用Ruby internal DSL作為語法聲明的元語言,可以利用Ruby豐富的測試框架,有效地對編譯/解釋/分析器進行測試。
5.正如名字所暗示的,Aurum(Gold的化學名稱)的一部分靈感來自
GOLD parsing system,它將支持獨立于平臺和語言的編譯器開發。
好,閑話少說,看一個例子,編譯原理中的Hello World —— 表達式求值:
1 require 'aurum'
2
3 class ExpressionGrammar < Aurum::Grammar
4 tokens do
5 ignore string(' ').one_or_more # <= a
6 _number range(?0, ?9).one_or_more # <= b
7 end
8
9 precedences do # <= c
10 left '*', '/'
11 left '+', '-'
12 end
13
14 productions do # <= d
15 expression expression, '+', expression {expression.value = expression1.value + expression2.value} # <= e
16 expression expression, '-', expression {expression.value = expression1.value - expression2.value}
17 expression expression, '*', expression {expression.value = expression1.value * expression2.value}
18 expression expression, '/', expression {expression.value = expression1.value / expression2.value}
19 expression '(', expression, ')' do expression.value = expression1.value end # <= f
20 expression _number {expression.value = _number.value.to_i}
21 expression '+', _number {expression.value = _number.value.to_i}
22 expression '-', _number {expression.value = -_number.value.to_i}
23 end
24 end
如果諸位對之前有用過compiler compiler或者parser generator的話,應該能看個七七八八吧。我大概解釋一下:
a.這里定義了文法空白,也就是被lexer忽略的部分,在通常的語言中,是空格回車換行之類的字符;string是用于定義lexical pattern的helper方法(出了string之外,還有range, enum和concat);ignore是一個預定義的說明指令,表示若文本匹配給定模式則該文本會被lexer自動忽略,其格式為:
ignore pattern {//lexical action}
b.此處為lexical token聲明,所有lexical token必須以_開頭,其格式為:
_token_name pattern {//lexical action}
這里其實是一個簡略寫法,等價于
match pattern, :recognize => :_token_name
c.此處為運算符優先級聲明,支持左/右結合運算符(無結合屬性運算符開發中);每一行中所有運算符具有相同優先級;比它下一行的運算符高一個優先級。比如在這個例子中,'*'和'/'具有相同優先級,但是比'+'和'-'的優先級別高。
d.此處為語法規則聲明,所使用的symbol主要有三種,nonterminal(小寫字母開頭),terminal(其實就是lexical token,以_開頭)和literal(字符串常量),其中所有literal都會被自動聲明為保留字。
e.此處定義了一條文法規則(加法),以及對應的semantic action。在semantic action中可以直接通過symbol的名字來獲取值棧中的對象。如遇到同名symbol,則按照出現順序進行編號即可。
f.其實這個沒啥,只不過由于我們使用的是Ruby DSL,所以有時候不能都用{},需要do end,這就是一個例子。
最后測試一下實際中如何使用定義好的語法(使用helper method,注意由于分析表沒有緩存,每次都會重算語法表,僅僅適用于debug mode。)
puts ExpressionGrammar.parse_expression('1+1').value
或者通過分析表自己構造lexer和parser
lexer = Aurum::Engine::Lexer.new(ExpressionGrammar.lexical_table, '1+1')
parser = Aurum::Engine::Parser.new(ExpressionGrammar.parsing_table(:expression))
puts parser.parse(lexer).value
最后最后,給另外一個例子,就是
Martin Fowler Blog上的
HelloParserGenerator系列中所用的語法:
1 require 'aurum'
2
3 Item = Struct.new(:name)
4
5 class Catalog < Aurum::Grammar
6 tokens do
7 ignore enum(" \r\n").one_or_more
8 _item range(?a,?z).one_or_more
9 end
10
11 productions do
12 configuration configuration, item {configuration.value = configuration1.value.merge({item.value.name => item.value})}
13 configuration _ {configuration.value = {}}
14 item 'item', _item {item.value = Item.new(_item.value)}
15 end
16 end
17
18 config = Catalog.parse_configuration(<<EndOfDSL).value
19 item camera
20 item laser
21 EndOfDSL
22
23 puts config['camera'].name
P.S.:本文是根據Aurum0.2.0寫成的,你可以從rubyforge的svn上得到它。
P.S.P.S.: 在exmaples目錄里有一個更復雜一些的例子,是一個簡單的Smalltalk解釋器。