読者です 読者をやめる 読者になる 読者になる

M GrammerでDSL

.net design

OsloとM言語で、DSLを作る例を紹介します(MSDN Hands-On-Lab18を参考)。
映画と監督のデータを登録する「MovieSTART 映画名 [BY 監督名]」のようなDSLを作りたいとします(sample.movie)。

MovieSTART Hello2009 BY Mike
MovieSTART "Star Sky"
MovieSTART DogAndCat BY "H Miya"

DSLを定義するためのエディタ「Intellipad」を以下のパラメータで起動します(ipad-vs-samples.xamlは今のところ、動的コンパイル機能のパラメータ)。

".\Intellipad\ipad.exe" /c:ipad-vs-samples.xaml

左のペインがDSL、真ん中がDSL定義、右がアウトプットツリーです。これで、DSLを定義しながら、DSLを入力して、ツリーが正しいかを確認できます。

DSLを定義するプログラムはこんな感じになります。Osloでは、独自のDSLを定義するためにM Grammar(mg.exe)という言語が用意されています。ファイルの拡張子はMGです(sample.mg)。

module MoviePlayer
{
    language MovieLanguage 
    {
        interleave Skippable 
            = Whitespace;
         
        // ---------------
        //  ↓シンタックス
        // ---------------
        
        //ルート(映画の繰り返し)
        //moviesとは「Movieの1個以上の繰り返し」と定義
        //それを、Movies{...}で出力
        syntax Main 
            = movies:Movie+ 
            => Movies{ valuesof(movies) };

        //映画
        //Movieは「MovieStart MovieName Director」
        //それを { Name {...}, Director{...}}で出力
        syntax Movie 
            = MovieStart name:MovieName director:Director 
            => { Name {name}, Director{director}} ;
        
        //映画の構文(ダブルクォーテーション囲みもOK)
        syntax MovieName 
            = name:Name => name
            | name:NameVerbatim => name;
        
        //監督の構文(ダブルクォーテーション囲みもOK)
        syntax Director 
            = By name:Name => name
            | By name:NameVerbatim => name
            | empty => "Unknown";

        //ダブルクォーテーションで囲まれた名前
        nest syntax NameVerbatim 
            = '"' name:NameWithWhitespace '"' => name;

        // ---------------
        //  ↓トークン
        // ---------------
        
        //アルファベットと数字
        token AlphaNumerical 
            = 'a'..'z' | 'A'..'Z' | '0'..'9';
        
        //映画監督を示す定数
        final token By 
            = "BY";

        //映画開始を示す定数
        final token MovieStart 
            = "MovieSTART";
         
        //名前(アルファ数値の固まり:映画名 または 監督名)
        token Name 
            = AlphaNumerical+;
        
        //名前と区切り(ダブルクォーテーション用)
        token NameWithWhitespace 
            = (AlphaNumerical | Whitespace)+;

        //区切り(改行 または タブ または スペース)
        token Whitespace 
            = '\r' 
            | '\n' 
            | '\t' 
            | ' ';
    }
}        

lex(字句解析)、yacc構文解析)と似てますが、MGrammarは、ツリーを組み立てることに特化してるようです(printfとかできないので、デバッグは難しいかも)。
最初の入力に対してアウトプットされるツリーは次の通りです。

Movies{
  {
    Name{
      "Hello2009"
    },
    Director{
      "Mike"
    }
  },
  {
    Name{
      "Star Sky"
    },
    Director{
      "Unknown"
    }
  },
  {
    Name{
      "DogAndCat"
    },
    Director{
      "H Miya"
    }
  }
}