2012-04-20

C++における(抽象)構文木の表現

C++でプログラミング言語の構文木を表現する方法はそれこそ星の数ほどあると思われる。その中で自分の用途ではどのようなものが適しているのかしばしば考えることがある。

例えば単純な式を表す構文を考えよう。

<expr> ::= <add_expr>

<add_expr> ::= <mul_expr>
	     | <add_expr> "+" <mul_expr>
	     | <add_expr> "-" <mul_expr>

<mul_expr> ::= <primary_expr>
	     | <mul_expr> "*" <primary_expr>
	     | <mul_expr> "/" <primary_expr>

<primary_expr> ::= "(" <expr> ")"
		 | <integer_literal>
		 | <float_literal>

登場するのは+-*/の二項演算子、整数リテラル、浮動小数点数リテラルといった要素だ。これらの要素が抽象構文木のノードとなる。それぞれプログラム上ではBinOp、IntLit、FloatLitと書くことにする。

継承で表現する

C++で一番素直な方法と言ったら、やはり継承を使うことだと思う。

struct Location
{
  int line, column;
  Location(int line, int column) : line(line), column(column){}
};
struct Expr
{
  Location loc;
  Expr(const Location &loc) : loc(loc){}
  virtual ~Expr(){}
};
typedef boost::shared_ptr<Expr> ExprPtr;
struct BinOp : Expr
{
  enum OpType { OP_ADD, OP_SUB, OP_MUL, OP_DIV};
  OpType op;
  ExprPtr lhs;
  ExprPtr rhs;
  BinOp(OpType op, ExprPtr lhs, ExprPtr rhs, const Location &loc)
    : Expr(loc), op(op), lhs(lhs), rhs(rhs) {}
};
struct IntLit : Expr
{
  int value;
  IntLit(int value, const Location &loc)
    : Expr(loc), value(value) {}
};
struct FloatLit : Expr
{
  double value;
  FloatLit(double value, const Location &loc)
    : Expr(loc), value(value) {}
};

コードに対する補足説明:

  • public:とかprivate:とか書くのが面倒なので、すべてstructにしてある。
  • Locationはいきなり出てきたが、ソースコード上の位置を記憶しておくためのもの。こういったものがあるかどうかも設計に微妙に影響してくる。
  • 各ノードはshared_ptrで取り回すことにした。解放が楽なので。循環参照の危険も今回はないので。

上のコードは式の基底クラスであるExprと、その派生クラスであるBinOp、IntLit、FloatLitを定義している。

素直なC++のコードだが、次の点が気になる。

  • const Location &locやExpr(loc)があちこちに出現する。Exprにメンバを追加するたびにこれらを修正しなければならない。
  • ノードを巡回する方法が未定。

一つ目の問題はコードの重複を気にするものだが、あまり良い方法は見当たらないのでひとまず置いておく。C++で継承をするとよくこの問題にぶつかる。マクロを使ったり、Exprを直接引数にとったり、包み込む別のクラスを作ってlocをそちらへ移動したり、初期化リストで初期化するのを諦めたり、独自のプリプロセスを行ったり、いくつか手は考えられるのだが、それぞれ良い面と悪い面がある。

二つ目の問題は実際に構文木を構築した後、どのように使うのかということだ。プログラムで構文木の各ノードを一つ一つ見て処理をしていく場合に、どうやって各ノードの種類を判別し、適切な処理を起動するのか。

dynamic_castによる訪問

例えば各ノードを訪問してテキスト出力するようなプログラムは次のようになる。

void print(const ExprPtr &expr)
{
  if(const BinOp *binop = dynamic_cast<const BinOp *>(expr.get())){
    std::cout << "BinOp(" << binop->op << 'n';
    print(binop->lhs);
    print(binop->rhs);
    std::cout << ")n";
  }
  else if(const IntLit *intlit = dynamic_cast<const IntLit *>(expr.get())){
    std::cout << "IntLit(" << intlit->value << ")n";
  }
  else if(const FloatLit *floatlit = dynamic_cast<const FloatLit *>(expr.get())){
    std::cout << "FloatLit(" << floatlit->value << ")n";
  }
  else{
    throw std::runtime_error("Unknown Expr");
  }
}

template<typename T>
ExprPtr new_sp(const T &v) { return ExprPtr(new T(v));}

int main()
{
  // (123 + 234) * 1.25
  ExprPtr expr =
    new_sp(BinOp(
      BinOp::OP_MUL,
      new_sp(BinOp(
    BinOp::OP_ADD,
    new_sp(IntLit(123, Location(1,2))),
    new_sp(IntLit(234, Location(1,8))),
    Location(1,6))),
      new_sp(FloatLit(1.25, Location(1,15))),
      Location(1,13)));
  print(expr);
}

この方法ではdynamic_castを使用して基底クラスから派生クラスの種類を求めている。様々あるやり方の中では素直な方だし、見た目的にもシンプルで、決して悪い方法とは言えないだろう。ただ、問題があることも確かである。上から順にif文でテストしていくので線形探索相当になる。新しい派生クラスを追加したときにif文を追加し忘れるという問題もある。ディスパッチ処理(dynamic_castによる判別)と呼ばれる処理(テキスト出力)が一緒くたになっているのも気になるが、その気になればテンプレートを使って分離できるのでそれはさほど問題ではない。

visitorパターンによる訪問

この手の問題への解決方法にはVisitorパターンというものがよく知られている。どんな感じになるのかはWikipediaにC++の例が載っているので、見れば分かると思う。

C++で実装する典型的なVisitorパターンにはいくつか欠点があるように思う。

  • 侵入的
  • 各派生クラスにacceptを仕込む工夫が必要
  • 一つの派生クラスに関する定義・宣言が複数の場所に散乱しがち(各accept関数の中身を書くにはVisitorインタフェースの完全な定義が必要だが、Visitorインタフェースを定義するには各派生クラスの宣言が必要。なので、各派生クラス毎に前方宣言を書くか、accept関数の定義をクラスの定義から分離するかしないといけない、と思う)
  • accept関数の戻り値を自由にできない
  • accept関数の引数型を自由にできない(constにするかしないか、参照で受けるかポインターで受けるか、shared_ptrで受けるか等)

種類を取得するメンバ関数を追加する方法

派生クラスの判別をどのように行うか。派生クラス自身に尋ねれば良い。この方法によって、典型的なVisitorパターンのいくつかの欠点は解決するが、また別の欠点が現れてくる。

struct Expr
{
  //
  virtual int get_tag() const = 0;
};
struct BinOp : Expr
{
  //
  virtual int get_tag() const { return 0;}
};
struct IntLit : Expr
{
  //
  virtual int get_tag() const { return 1;}
};
struct FloatLit : Expr
{
  //
  virtual int get_tag() const { return 2;}
};

訪問は次のように行う。

template<typename Result, typename Func>
Result visit(Func f, const ExprPtr &expr)
{
  switch(expr->get_tag()){
  case 0: return f(boost::static_pointer_cast<BinOp>(expr));
  case 1: return f(boost::static_pointer_cast<IntLit>(expr));
  case 2: return f(boost::static_pointer_cast<FloatLit>(expr));
  default: throw std::runtime_error("Unknown Expr");
  }
}

struct Printer
{
  void operator()(const ExprPtr &expr) const
  {
    visit<void>(*this, expr);
  }
  void operator()(const boost::shared_ptr<BinOp> &binop) const
  {
    std::cout << "BinOp" << std::endl;
    (*this)(binop->lhs);
    //
  }
  void operator()(const boost::shared_ptr<IntLit> &intlit) const
  {
    std::cout << "IntLit " << intlit->value << std::endl;
  }
  void operator()(const boost::shared_ptr<FloatLit> &floatlit) const
  {
    std::cout << "FloatLit " << floatlit->value << std::endl;
  }
};

Printer()(expr);

すべての派生クラスにget_tag関数を仕込む手間が生じるという点で少しだけ侵入的であるが、ディスパッチ処理自体は継承ツリーの中に侵入していないので、いくらか柔軟性があると思う。

最大の欠点は0、1、2というget_tagが返す値(派生クラスの識別子)を正しく維持しなければならない点だろう。新しい派生クラスが追加されることを考えると、ある程度自動的に識別子を設定して欲しい。

少し考えただけでも次のような方法が思い浮かぶ。

  • typeidを使う方法
  • プリプロセッサでナンバリングする方法
  • 型リストを使う方法

型リストを使って派生クラスの識別子を設定する

次のような型リストを作っておけば、

typedef boost::mpl::list<
  BinOp,
  IntLit,
  FloatLit
  > Types;

次のようなコードで型がリストの中で何番目かをコンパイル時定数で得ることが出来る。

boost::mpl::index_of<Types, IntLit>::type::value //== 1になる。

つまり、例えばIntLit::get_tagは次のように実装できることになる。

struct IntLit : Expr
{
  //
  int get_tag() const { return boost::mpl::index_of<Types, IntLit>::type::value;} //Typesの中で何番目かを返す。
};

もちろんこんなのを派生クラス毎にいちいち書いていられない。

マクロを使って

struct IntLit : Expr
{
  //
  GET_TAG_IMPL(IntLit);
};

のように書くようにする手もあるが、少し見苦しい。

struct IntLit : DerivedImpl<IntLit, Expr>
{
  //
};

くらいにしたいところなので、

template<typename Derived, typename Base>
struct DerivedImpl : Base
{
  // コンストラクタ
  DerivedImpl() : Base(){}
  template<typename A0> DerivedImpl(const A0 &a0) : Base(a0){}
  template<typename A0, typename A1> DerivedImpl(const A0 &a0, const A1 &a1) : Base(a0, a1){}

  // get_tagの実装
  int get_tag() const { return boost::mpl::index_of<typename Base::DerivedTypes::Types, Derived>::type::value;}
};

のようなget_tagを実装してくれるクラスを用意することにした。

これが動くには基底クラスにDerivedTypes::Typesという名前で派生クラスの型リストが無ければならない。

struct Expr
{
  struct DerivedTypes;
  //
};

//各派生クラスの定義。

struct Expr::DerivedTypes
{
  typedef boost::mpl::list<
    BinOp,
    IntLit,
    FloatLit
    > Types;
};

Exprに直接型リストを書かないのは、前方宣言をできるだけしないでいいようにするためである。

型リストを使った場合のディスパッチ

型リストからswitch~caseを生成する。

template<typename Result, typename Func, typename Base>
Result visit(Func f, Base &a)
{
  typedef typename Base::DerivedTypes::Types Types;
  switch(a.get_tag())
  {
  case 0: return f((typename boost::mpl::at_c<Types, 0>::type &)a);
  case 1: return f((typename boost::mpl::at_c<Types, 1>::type &)a);
  case 2: return f((typename boost::mpl::at_c<Types, 2>::type &)a);
  //case 3: return f((typename boost::mpl::at_c<Types, 3>::type &)a);
  //case 4: return f((typename boost::mpl::at_c<Types, 4>::type &)a);
  }
}

型リストの長さを越えてat_cするとコンパイルが通らない。なので、インデックスが型リストのサイズを超えているかどうかでコードを切り替える必要がある。面倒なので説明は省略。

boost::variantのapply_visitorも内部でこのようなコードを使用している。

ライブラリ化

そんなこんなでライブラリとしてまとめたのが以下。

これの良いところは新しく作った派生クラスを型リストに追加し忘れても、ちゃんとエラーになってくれるところ。get_tagがコンパイルできないのでエラーになる。

boost::variantを使った構文木の表現

Recursive variant types - Advanced Topics - Boost.Variantチュートリアルに興味深い例が載っている。boost::variantを使って簡単な計算機を作る例だ。

これをまねて、今回の構文木に適用してみると、

struct Location
{
  int line, column;
  Location(int line, int column) : line(line), column(column){}
};

struct BinOp;
struct IntLit;
struct FloatLit;

typedef boost::variant<
  boost::recursive_wrapper<BinOp>,
  IntLit,
  FloatLit
  > ExprBody;

struct IntLit
{
  int value;
  IntLit(int value) : value(value) {}
};
struct FloatLit
{
  double value;
  FloatLit(double value) : value(value) {}
};

struct Expr
{
  Location loc;
  ExprBody body;
  template<typename Body>
  Expr(const Body &body, const Location &loc) : loc(loc), body(body){}
};

struct BinOp
{
  enum OpType { OP_ADD, OP_SUB, OP_MUL, OP_DIV};
  OpType op;
  Expr lhs;
  Expr rhs;
  BinOp(OpType op, const Expr &lhs, const Expr &rhs)
    : op(op), lhs(lhs), rhs(rhs) {}
};

継承を使っていないのでコンストラクタがすっきりする。

しかし、派生クラスの前方参照をずらずら書かなければならなかったり、クラスの定義順に気を使わなければならなかったり、それを回避しようといろいろやっていると、結局ポインターで持った方が良いじゃん!ということになりかねなかったりする。

それと、ノードを作るときにExpr(IntLit(123), Location(1,1))と書かなければならないし、apply_visitorで個別の型を受け取れたとしてもLocationを別途受け取らなければならなかったりして意外と面倒。

2012-04-15

新番組ファーストインプレッション

新番組多すぎ。このクソ忙しいのに。頑張って可能な限り見た。

  • ○宇宙兄弟
  • ×緋色の欠片
  • △→×しばいぬこさん
  • ×GON-ゴン-
  • △→×あらしのよるに
  • △これはゾンビですか?オブザデッド
  • △めだかボックス
  • △LUPIN THE Third 峰不二子という女
  • △→×しろくまカフェ
  • △→×あっちこっち
  • △→×さんかれあ
  • ○アクセルワールド
  • ○ZETMAN
  • ×夏色キセキ
  • △銀河へキックオフ!!
  • △→×ジュエルペット きら☆デコッ!
  • △プリティーリズム・ディアマイフューチャー
  • △謎の彼女X
  • △へうげもの
  • △黄昏乙女×アムネジア
  • ×這いよれ!ニャル子さん
  • ○黒子のバスケ
  • ○ヨルムンガンド
  • ×クイーンズブレイド リベリオン
  • ○坂道のアポロン
  • ○つり球
  • △咲-Saki-阿知賀編 episode of side-A
  • △エウレカセブンAO
  • △→×戦国コレクション
  • △→×シャイニングハーツ 幸せのパン

新番組30○7△10×13。数が多い割にはこれはというものは無い。しいて言えば坂道のアポロンあたりか。やっぱりノイタミナは固い。

2012-04-06

2012桜の開花

4/4~4/6撮影gifアニメーション。4/4、4/5、4/6の三日間、毎日午前9事前後に撮影。

20120406_sakura_blooming.gif

結構角度が変わってしまったのだけどPhotoshopで無理矢理補正。

2012-04-02

ギャラクシーエンジェル ア・ラ・カルト

MXでやっていたので見た。傑作選なのね。なんか投票もやってるんだとか。

今回放送されていたのは一期の第一話と第二話だったけど、画面比が4:3だから画面左右が青帯で埋められていてなんだか変な感じ。

GAはほぼすべて見ているはずだけど、一番印象に残っているのは顔出し看板ですかね。あとは成りアガリクスダケとか。

2012-03-21

出家とその弟子

確かtwitterで言及されていて興味を持ったんだと思ったけど、出家とその弟子を読んだ。たまにはこういうのを読むのも良いね。

どんなに考えてみても人間ではどうにもできないと思えるから祈るとか願うとかするしか無いでしょ、みたいな感じかなぁ。私は何か超越的な存在(例えば仏様みたいなの)を具体的に信じているわけでは無いので、祈って神仏におすがりする、みたいなのはゾッとする(まあ、すがれとは言っていないと思うけど)。しかし、繰り返し祈り、願うことによって深層意識というか、脳に実現しやすい性質をすり込んでいくというのならば、まだ分からなくも無い。どのみち証拠も無い分からない話ではある。

2012-03-18

書けない

う゛ー、構文解析が難しい。書こうと思えば多分書ける。だが、いくつか問題が生じる。

というか、構文自体に多少の問題がある。このままの構文だとネストしたときに書けなくなる構文がまれではあるが出来てしまう。それを防ぐには普段よく使う構文を多少見た目が悪いものにする必要があるように思う。でもそれは嫌だ。まれにしか使わない書き方のためによく使う書き方を犠牲にしたくない。それに、そのまれにしか使わない書き方は別に書けなくても回避方法はいくらでもある。すべてが丸く収まる構文が思いつけば良いのだが、どうも思いつきそうも無い。

実装自体にも多少問題はある。文脈によってキーワードにするものを細かく変えなければならない。それも、ネストする度に特殊な解析モードを入れたり切ったりしなければならない。あんまりそういうことをするとエラー回復が難しくなるからやりたくないんだけどなぁ。

2012-03-11 ,

org-modeで画像へのURLリンクをインライン表示

org-modeには画像のリンクをインラインで表示する機能がある。

[[file:hoge.png]]

のようなリンクがあった場合に、org-toggle-inline-images ( C-C C-x C-v ) でインライン表示するかどうかをトグルで切り替えられる。

しかしこの機能はURLには対応していない。

[[http://www.google.co.jp/images/srpr/logo3w.png]]

のようなリンクがインラインで表示されないのである。

なので、それが出来るようにしてみた。

結果のソースコードはorg-http-inline-image.el

http経由で画像を取得

まずはウェブ上からhttp経由で画像を取得する方法について調べた。

(insert-image
  (create-image
    (with-current-buffer
      (url-retrieve-synchronously
        "http://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/260px-The_Earth_seen_from_Apollo_17.jpg")
      (buffer-substring (re-search-forward "\n\n") (point-max))) nil t))

これで任意のURLから画像を取得できる。

url-retrieve-synchronouslyで画像データを含んだバッファを作成し、
HTTPヘッダを除いた部分を取り出してcreate-imageしているだけ。

試しにinsert-imageするとバッファに画像が表示されるはず。

本当は非同期にしたり画像をキャッシュしたりエラー処理をしないとダメなんだろうけど、まずは最低限のコードが知りたかったので。

あと、kill-bufferした方がメモリ的には良いのかもしれない。

org-display-inline-imagesの改変

画像のインライン表示はorg.el内のorg-display-inline-imagesで行っている。

この関数は7.8.03の時点では次のようになっている。

(defun org-display-inline-images (&optional include-linked refresh beg end)
  "Display inline images.
Normally only links without a description part are inlined, because this
is how it will work for export.  When INCLUDE-LINKED is set, also links
with a description part will be inlined.  This can be nice for a quick
look at those images, but it does not reflect what exported files will look
like.
When REFRESH is set, refresh existing images between BEG and END.
This will create new image displays only if necessary.
BEG and END default to the buffer boundaries."
  (interactive "P")
  (unless refresh
    (org-remove-inline-images)
    (if (fboundp 'clear-image-cache) (clear-image-cache)))
  (save-excursion
    (save-restriction
      (widen)
      (setq beg (or beg (point-min)) end (or end (point-max)))
      (goto-char (point-min))
      (let ((re (concat "\[\[\(\(file:\)\|\([./~]\)\)\([^]n]+?"
                        (substring (org-image-file-name-regexp) 0 -2)
                        "\)\]" (if include-linked "" "\]")))
            old file ov img)
        (while (re-search-forward re end t)
          (setq old (get-char-property-and-overlay (match-beginning 1)
                                                   'org-image-overlay))
          (setq file (expand-file-name
                      (concat (or (match-string 3) "") (match-string 4))))
          (when (file-exists-p file)
            (if (and (car-safe old) refresh)
                (image-refresh (overlay-get (cdr old) 'display))
              (setq img (save-match-data (create-image file)))
              (when img
                (setq ov (make-overlay (match-beginning 0) (match-end 0)))
                (overlay-put ov 'display img)
                (overlay-put ov 'face 'default)
                (overlay-put ov 'org-image-overlay t)
                (overlay-put ov 'modification-hooks
                             (list 'org-display-inline-modification-hook))
                (push ov org-inline-image-overlays)))))))))

この関数は、

  1. refreshでないときは既存のインライン画像をすべて取り除く。
  2. インライン表示対象となるリンク文字列を検索する。
  3. 見つかったリンクに対して、ファイル名が有効なら、そのファイルの画像を作成して、リンクの範囲にオーバーレイとして設定する。ただし、refreshでかつ特定の条件を満たせば、新しい画像は作らない。

といった動作をしている。

どうしたものか。

まず、httpリンクを見つけ出さなければならない。

正規表現部分の三つ目の開き括弧部分にhttpを入れることにした。

(let ((re (concat "\[\[\(\(file:\)\|\([./~]\|http://\)\)\([^]n]+?"
                  (substring (org-image-file-name-regexp) 0 -2)
                  "\)\]" (if include-linked "" "\]")))
      old file path ov img)
  (while (re-search-forward re end t)
    (setq old (get-char-property-and-overlay (match-beginning 1)
                                             'org-image-overlay))
    (setq file (concat (or (match-string 3) "") (match-string 4)))
    (setq path (expand-file-name file))

httpの部分はファイル名に含めたかったので、二つ目では無く三つ目にした。
それに伴い変数fileはexpand-file-nameする前のファイル名にして、変数
pathはexpand後にしてある。

次に実際に画像を用意するところだが、このままだと少し見通しが悪い。
新しいオーバーレイを作る部分を別の関数に分離してみた。

       (when (file-exists-p file)
         (if (and (car-safe old) refresh)
             (image-refresh (overlay-get (cdr old) 'display))
           (setq img (save-match-data (create-image file)))
           (my-org-add-inline-image-overlay img))))))))

(defun my-org-add-inline-image-overlay (img)
  (when img
    (let ((ov (make-overlay (match-beginning 0) (match-end 0))))
      (overlay-put ov 'display img)
      (overlay-put ov 'face 'default)
      (overlay-put ov 'org-image-overlay t)
      (overlay-put ov 'modification-hooks
                   (list 'org-display-inline-modification-hook))
      (push ov org-inline-image-overlays))))

少し見通しが良くなった。下半分はファイルでもURLでも共通の処理であり、問題は上半分だけとなった。

さて、ファイルへのリンクかURLへのリンクかによって処理を変えたいのだが、どうしたら良いだろうか。

問題の部分。

(when (file-exists-p file)
  (if (and (car-safe old) refresh)
      (image-refresh (overlay-get (cdr old) 'display))
    (setq img (save-match-data (create-image file)))
    (my-org-add-inline-image-overlay img))))))))
  • 1行目はファイルの時の処理。
  • 2行目~3行目はURLのときでも同じ処理。
  • 4行目はファイルの時の処理。
  • 5行目はURLの時でも同じ処理。

このようにファイルの時の処理とURLと共通の処理が交互に現れていて分離するのが先ほどと比べて難しい。

ここはlambdaを使って共通の処理だけをくくりだしてみた。

    (cond
     ((string= (match-string 3) "http://")
      (my-org-update-inline-image
       refresh (lambda () (save-match-data (my-org-create-image-from-url file)))))
     ((file-exists-p path)
      (my-org-update-inline-image
       refresh (lambda () (save-match-data (create-image path))))))))))

(defun my-org-update-inline-image (refresh loader)
  (let ((old (get-char-property-and-overlay (match-beginning 1)
                                            'org-image-overlay)))
    (if (and (car-safe old) refresh)
        (image-refresh (overlay-get (cdr old) 'display))
      (my-org-add-inline-image-overlay (funcall loader)))))

あとはURLから画像を読み込む関数が有れば良い。

(defun my-org-create-image-from-url (url)
  (let* ((buf (url-retrieve-synchronously url))
         (res (if buf (with-current-buffer buf (buffer-string))))
         (sep (if res (string-match "nn" res)))
         (data (if sep (substring res (+ 2 sep))))
         (img (if data (create-image data nil t))))
    (if buf (kill-buffer buf))
    img))

nilチェックやkill-bufferで不要になったバッファを削除したりしている。