A very brief introduction to Aurum
Aurum是一個(gè)用Ruby實(shí)現(xiàn)的LALR(n) parser generator(是的,又是一個(gè)parser generator),不過(guò)它和其他一些廣泛應(yīng)用的parser generator相比略有不同的:
1.Aurum的主要目標(biāo)之一,是簡(jiǎn)化external DSL的開發(fā)(尤其是ruby external DSL)。
2.Aurum采用增量LALR(n)算法,而不是通常的LALR(1)。這意味著:
a.不必由于LALR(1)能力的限制,而改寫語(yǔ)法,很多在LALR(1)中沖突的語(yǔ)法在LALR(n)中可以比較自然地表達(dá)。
b.由于識(shí)別能力的增強(qiáng),可以處理一些比較復(fù)雜的語(yǔ)法,比如COBOL(LALR(2)或LALR(3)),比如一些簡(jiǎn)化的自然語(yǔ)言(LALR(3+))。
c.處理能力接近Generalized LR,卻快很多
d.比起Full LALR/LR(n),增量算法生成的語(yǔ)法表更小。
3.出于簡(jiǎn)化external DSL實(shí)現(xiàn)的考慮,Aurum支持語(yǔ)法重用。
4.Aurum采用Ruby internal DSL作為語(yǔ)法聲明的元語(yǔ)言,可以利用Ruby豐富的測(cè)試框架,有效地對(duì)編譯/解釋/分析器進(jìn)行測(cè)試。
5.正如名字所暗示的,Aurum(Gold的化學(xué)名稱)的一部分靈感來(lái)自
GOLD parsing system,它將支持獨(dú)立于平臺(tái)和語(yǔ)言的編譯器開發(fā)。
好,閑話少說(shuō),看一個(gè)例子,編譯原理中的Hello World —— 表達(dá)式求值:
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
如果諸位對(duì)之前有用過(guò)compiler compiler或者parser generator的話,應(yīng)該能看個(gè)七七八八吧。我大概解釋一下:
a.這里定義了文法空白,也就是被lexer忽略的部分,在通常的語(yǔ)言中,是空格回車換行之類的字符;string是用于定義lexical pattern的helper方法(出了string之外,還有range, enum和concat);ignore是一個(gè)預(yù)定義的說(shuō)明指令,表示若文本匹配給定模式則該文本會(huì)被lexer自動(dòng)忽略,其格式為:
ignore pattern {//lexical action}
b.此處為lexical token聲明,所有l(wèi)exical token必須以_開頭,其格式為:
_token_name pattern {//lexical action}
這里其實(shí)是一個(gè)簡(jiǎn)略寫法,等價(jià)于
match pattern, :recognize => :_token_name
c.此處為運(yùn)算符優(yōu)先級(jí)聲明,支持左/右結(jié)合運(yùn)算符(無(wú)結(jié)合屬性運(yùn)算符開發(fā)中);每一行中所有運(yùn)算符具有相同優(yōu)先級(jí);比它下一行的運(yùn)算符高一個(gè)優(yōu)先級(jí)。比如在這個(gè)例子中,'*'和'/'具有相同優(yōu)先級(jí),但是比'+'和'-'的優(yōu)先級(jí)別高。
d.此處為語(yǔ)法規(guī)則聲明,所使用的symbol主要有三種,nonterminal(小寫字母開頭),terminal(其實(shí)就是lexical token,以_開頭)和literal(字符串常量),其中所有l(wèi)iteral都會(huì)被自動(dòng)聲明為保留字。
e.此處定義了一條文法規(guī)則(加法),以及對(duì)應(yīng)的semantic action。在semantic action中可以直接通過(guò)symbol的名字來(lái)獲取值棧中的對(duì)象。如遇到同名symbol,則按照出現(xiàn)順序進(jìn)行編號(hào)即可。
f.其實(shí)這個(gè)沒啥,只不過(guò)由于我們使用的是Ruby DSL,所以有時(shí)候不能都用{},需要do end,這就是一個(gè)例子。
最后測(cè)試一下實(shí)際中如何使用定義好的語(yǔ)法(使用helper method,注意由于分析表沒有緩存,每次都會(huì)重算語(yǔ)法表,僅僅適用于debug mode。)
puts ExpressionGrammar.parse_expression('1+1').value
或者通過(guò)分析表自己構(gòu)造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
最后最后,給另外一個(gè)例子,就是
Martin Fowler Blog上的
HelloParserGenerator系列中所用的語(yǔ)法:
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.:本文是根據(jù)Aurum0.2.0寫成的,你可以從rubyforge的svn上得到它。
P.S.P.S.: 在exmaples目錄里有一個(gè)更復(fù)雜一些的例子,是一個(gè)簡(jiǎn)單的Smalltalk解釋器。