オブジェクト指向パーサジェネレータ 「Metal」based on Parsing Expression Grammar


Parsing Expression Grammar (PEG)MetalRubyRuby

MetalOMeta: an Object-Oriented Language for Pattern MatchingRuby


Metal


Ruby

Mix-insuper

PEG










CodeRepos:/lang/ruby/metal


使い方

Ruby gemsでインストールできます。

$ gem install metal


文法定義ファイルを書いて、metalコマンドを使って構文解析器を生成します。

$ metal syntax.metal > syntax.rb


生成された構文解析器は他のRubyスクリプトからrequireして使うことができますが、とりあえず試したいときはmetal-runコマンドを使って試せます。

$ metal-run syntax.rb '(add 1 2)'

文法定義の書き方


S
SExpressionParser {
  # ここから構文解析開始
  -main
    expression:e s end  {* e *}

  # expressionはliteralまたはカッコで囲まれたexpressionの繰り返し
  -expression
      literal
    / s '(' (s expression:e s {*e*})+:es ')'s{* es *}

  # literalはstringまたはnumber
  -literal
      string
    / number

  # stringは[a-zA-Z_0-9]の1文字以上の繰り返し
  -string
    [a-zA-Z_0-9]+:xs  {* xs.join *}

  -number
    [0-9]+:s  {* s.join *}

  -s
      [ \r\n]*
}

SExpressionParser { .... }

-expression-main*1
-literalstring / number




 e1 e2 e3

ParseErrore3

 e1 / e2 / e3

/e1e1e2e2e3

 e*

*e1

1 e+

+e11ParseError

 e?

?enile

 [a-zA-Z]

ParseError

 ""  ''

ParseError

1 .

.1

 end

endParseErrornil

 {* ... *}

{* ... *}Ruby

 ?{* ... *}

{* ... *}RubynilfalseParseError

 &e

&eeeParseError

 !e

!eenileParseError


 :-p


変数


:xx使
[0-9]+:s {* s.join *}[0-9]+s+1s"0""9"{* s.join *}
SExpressionParser-number09


継承とsuper


SExpressionParser+-SExpressionParser
SExpressionParserExtend < SExpressionParser {
  -literal
      super
    / operator

  -operator
      '+' / '-' / '*' / '/'
}

-literalsuper / operatorSExpressionParser-literal-operator



Mix-in


SExpressionParser-number-s使Mix-in使
# module Utilityを定義
module Utility {
  # よく使うメソッドを切り出しておく
  -string
      [a-zA-Z_0-9]+:xs  {* xs.join *}

  -number
      [0-9]+:s  {* s.join *}

  -s
      [ \r\n]*
}

# 先頭のclassは省略可能
class SExpressionParser {
  # Utilityをincludeする
  @include Utility

  -main
      expression:e s end  {* e *}

  -expression
      literal
    / s '(' (s expression:e s {*e*})+:es ')'s{* es *}

  -literal
      string
    / number
}

module Utility使@include UtilityMix-in


外部構文解析器の呼び出し


SSSClassName.methodName
SS
Calc {
  @include Utility

  -main
    expression:e s end  {* e *}

  -expression
      number:l s '+' s number:r  {* [:add, l, r] *}
    / number:l s '-' s number:r  {* [:sub, l, r] *}

  # 数字の連続か、先頭に'S'が付いたS式にマッチ
  -number
      [0-9]+:xs  {* xs.join *}
    / 'S' SExpressionParserExtend.expression
}

S(- (+ 1 2) 3) + 1


埋め込みスクリプト

@{ ... @}の中にRubyスクリプトを書くと、それが生成される構文解析器の中にそのまま埋め込まれます。

@{
# 最初にmetalをrequireしておく
require 'metal'
@}

Calc {
  @{
    # メソッドやクラスを定義しておく
    class Expression
      # ...
    end
  @}

  -main
      expression:e  {* Expression.new(e) *}

  # ...
}

パラメータ付きメソッド呼び出し


(arg, arg, ...):x :y:x :y2
  -expression
    prediction:p iterator(p)?:p variables?:v {* Expression.new(p, v) *}

  # 引数1つ取るメソッド
  -iterator :x
      '*' {* IterMany.new(x) *}
    / '+' {* IterMany1.new(x) *}
    / '?' {* IterMay.new(x) *}
    /     {* x *}

MetalMetalMetal-expressionpredictioinpiteratorp


無名メソッド


( ... )
  # (:で区切られた文字列)のゼロ回以上の繰り返し
  -rule_args
      (s':' [a-zA-Z_]+)*

  # "で囲まれた文字列
  -quoted_string
    '"' (!'"' . / '\"')+:s '"'   {* s.join *}

-rule_args(s ':' [a-zA-Z_]+) 


-quoted_string " ... " 


 !'"' .  '"' ParseError .1 "1 
 '\"'  \" 
(!'"' . / '\"') "1  \" 


(!'"' . / '\"')+:s "1  \" 1s
 '"' (!'"' . / '\"')+:s '"' {* s.join *}  "  "1  \" 1 " " ... " join





参考文献など

本家OMeta:http://www.cs.ucla.edu/~awarth/ometa/
OMetaの論文:OMeta: an Object-Oriented Language for Pattern Matching
Packrat Parserで左再帰を処理する話:Packrat Parsers Can Support Left Recursion
Python版OMeta:PyMeta
C#版OMeta:OMeta#

*1:変更可能。SExpressionParser.new(:string)のようにinitializeにメソッド名をシンボルで渡すと、そのメソッドから構文解析が始まります。