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解釋器。