DSL / Context Variable


DSL

タイトル

一言要約

解析中に必要なコンテキストを保持する為に使用。

要約

仕組み

  • コンテキスト変数は情報を収集し、意味論モデルオブジェクトを作成するビルダーのようなものとして扱う大抵上手くいく。

いつ使用すべきか

  • 解析中にコンテキストを維持しなければならない際に使用する。

 例:iniファイルを読む(#C)

/* iniファイルに相当? */
class Project...

  //iniファイル内の各セクションに相当
  class Project {
    public string Code { get; set; }  //セクション名
    public string Name { get; set; }  //nameプロパティ
    public string Lead { get; set; }  //leadプロパティ
/* iniファイルの解析(ビルダー)*/
class ProjectParser...

    private TextReader input;

    //プロジェクト(=iniファイル内のセクション)のリスト
    private List<Project> result = new List<Project>();

  //現在のプロジェクト
    private Project currentProject;

    public ProjectParser(TextReader input) {
      this.input = input;
    }

   /* プロジェクトの解析 */
    public List<Project> Run() {
      string line;
    
   //1行ずつ解析
      while ((line = input.ReadLine()) != null) {
        parseLine(line);
      }
      return result;
    }

  /* 行単位で解析 */
    private void parseLine(string s) {
      var line = removeComments(s);
      if (isBlank(line)) return ;
      else if (isSection(line)) parseSection(line);    //セクションの解析
      else if (isProperty(line)) parseProperty(line); //プロパティーの解析
      else throw new ArgumentException("Unable to parse: " + line);
    }

   /* '#' 区切りの最初の文字列(コメント前の記述)を返す */
   private string removeComments(string s) {
      return s.Split('#')[0];
    }

  /* 空白のみかを判定 */
    private bool isBlank(string line) {
      return Regex.IsMatch(line, @"^\s*$");
    }

    /* セクション判定 */
    private bool isSection(string line) {

      //0回以上の空白+ '[' で始まっているか?
      return Regex.IsMatch(line, @"^\s*\[");
    }

    /* セクションの解析 */ 
    private void parseSection(string line) {

      // lineが"[任意の文字列]"にマッチした場合の"[任意の文字列]"の値    
      var code = new Regex(@"\[(.*)\]").Match(line).Groups[1].Value;
      
      //現在のセクション名でプロジェクトを作成
      currentProject = new Project {Code = code};

      //iniファイル内のセクションリストに追加
      result.Add(currentProject);
    }

    /* プロパティー判定 */
    private bool isProperty(string line) {

      // '='を含んでいるか?
      return Regex.IsMatch(line, @"=");
    }

    /* プロパティーの解析 */
    private void parseProperty(string line) {

   //トークンの取得
      var tokens = extractPropertyTokens(line);

   //トークン名でプロパティーを取得し、カレントプロジェクトにトークンの値を設定
      setProjectProperty(tokens[0], tokens[1]);
    }

    /* プロパティーからトークンの抽出 */
    private string[] extractPropertyTokens(string line) {
      char[] sep = {'='};
      
      //行内の文字列を "="で分割(格納する配列のサイズは2)
    var tokens = line.Split(sep, 2);

      if (tokens.Length < 2) throw new ArgumentException("unable to split");

      //名前と値をtokensに格納
      for (var i = 0; i < tokens.Length; i++) tokens[i] = tokens[i].Trim();      
      return tokens;  
    }

    /* セクションのプロパティを設定 */
    private void setProjectProperty(string name, string value) {

      var proj = typeof(Project);
      var prop = proj.GetProperty(capitalize(name));

      if (prop == null) throw new ArgumentException("Unable to find property: " + name);      
      prop.SetValue(currentProject, value, null);
    }

    /* 先頭のみ大文字、以降は小文字に変換 */
    private string capitalize(string s) {
      return s.Substring(0, 1).ToUpper() + s.Substring(1).ToLower();
    }

ファウラーへのフィードバック

担当者のつぶやき

コンテキストっていうから、実行環境の事かと思ったら、本当に「文脈」なのね・・・
様は、解析対象(意味論モデル)の内容を保持するクラスの作り方を書きたかった?

  • 仕組み
    • 解析最中の更新ってどんな場面?@you update periodically during the parse as you move from one item to another in the input script.

みんなの突っ込み

  • 本文の例で、currentProjectが更新されているのがまさにこれに当たるのではないでしょうか?[xxxx]のセクションが登場するたびに、プロパティの格納対象がかわっていきます。 -- kentaro714? 2009-10-31 (土) 03:14:01