中間表現を分離すると煩雑になる問題

困った。とても困った。中間表現を分離しようとすると、processメソッドの記述が煩雑で冗長になる。特にインライン要素。


インライン要素はブロック要素と違って、再帰的に展開される。つまり、

\bold{\italic{str}}

としたとき、\boldが受け取る文字列は「\italic{str}」で、これをインライン展開し、「< em >str< /em >」という文字列を受け取った後、これを「< strong >< em >str< /em >< /strong >」にして返す。

一部のブロック要素も再帰的に展開する。(たとえば今のquoteの実装。multilineで生の引用文を受け取り、ブロック要素を展開する)


で、問題は、要素によってインライン要素を再帰的に展開したりしなかったりする場合がある、ということ。

たとえばlinkで、

\link{\bold{text}>url}

としたとき、link要素は「\bold{text}」を再帰的にインライン要素を展開する、かもしれない。XHTMLでは< a >タグの中に他のインライン要素が含まれることが許されているが、そうではないフォーマットもあるかもしれない。(WikiFormeの目指すところはWiki記法の1ソースマルチユース)


というわけで、インライン要素を展開するかしないかは、processメソッド(というか各要素の事情)が決める。

で、中間表現というのは、processメソッドに渡されるものなので、processメソッドで中間表現を作るわけにはいかない。…←コレなんか書いていてアホらしいな。processメソッドを過ぎると、逆変換できなくなる。


ここでループ発生。インライン要素を展開する必要がある要素のprocessメソッドが手元に持っている文字列は生の文字列で、中間表現ではない。でもprocessメソッドが中間表現を作るわけにはいかない。中間表現に変換しないとインライン要素を展開できない。どーするよ。



そんなわけで、processメソッドに至る前に、前処理を入れる必要がある。仮にparseと命名。それからparseの逆変換を行うunparse。インライン要素を展開する場合、parseが中間表現に変換する。この時点では、unparseを使って中間表現からWiki記法に戻すことが出来る。
すべての要素にprocessとparseを実装する。これで今度こそ中間表現を分離できる…たぶんおそらく。



中間表現を分離するために、実装の手間がprocessとparseの2つに増えるのは、コスト的にどうなのか、という問題。(processやparseを実装するのはユーザーという考え。拡張性の観点から)


中間表現を分離すると嬉しいことは、以下の3点。

  1. 「*」や「>」などのマーク(syntax)をいつでも変えられる(中間表現を介して変換ができる)
  2. 複数の人が1つのソースを編集するとき、各々が好きなsyntaxで編集できる(中間表現で保存しておき、編集時に変換する)
  3. ソースを配布できる(中間表現を配布する)

1はいいとしても、それ以外は自分1人で使う分にはまったく必要ない。人間のコスト計算アルゴリズムでは、他人のことは二の次という感があるので、1のために手間を増やすコストを払うのか否か、ということになる。




うーむ。こまった。


parseはほとんどの場合デフォルトで十分、というのはどうか。ちょっと凝った要素を作りたいときだけ、parseを独自に実装する必要がある。
中間表現に落とす理由は、processメソッドをsyntaxに依存しないようにするためなので、syntaxを参照したい場合だけparseを独自に実装する、というところまで持って行けるはず。


たとえば↓

# ↓boldはsyntaxを参照しないので、今まで通りprocessを定義するだけでOK
inline["bold"].process {|text|
  "<strong>#{text}</strong>"
}
# ↓inline["bold"].parseのデフォルト定義
inline["bold"].parse {|syntax,text|
  if method_defined?(:raw_process)
    # インライン要素を再帰的に展開しない要素は、
    # processの代わりにraw_processを定義することにする
    text
  else
    text.parse
  end
}

# ↓linkはsyntaxによって挙動を変えるので、parseを定義する
inline["link"].parse {|syntax,text|
  name, url = text.split(syntax["splitter"], 2)
  return name.parse, url
}
inline["link"].process {|name,url|
  # parseの返り値がprocessの引数に多重代入される
  "<a href=\"#{url}\">#{name}</a>"
}
# parseを独自に定義した場合はunparseも実装する必要がある
inline["link"].unparse {|syntax,name,url|
  "#{name.unparse}#{syntax["splitter"]}#{url}"
}

# unparseのデフォルト定義↓
inline["bold"].unparse {|syntax,text|
  if method_defined?(:raw_process)
    text
  else
    text.unparse
  end
}


うーむ…どうなんだ…。これはアリなのかナシなのか。



あまり意識してないけど、実行速度はさらに落ちるだろうなぁ…。1回すべての要素にparseを実行して中間表現に変換した後、もう一回すべての要素を回って親要素補完をやって、その後でprocessメソッドを実行することになる。1回丸ごとループが増える。中間表現分メモリ消費量も増える。


…あ、親要素補完で補完された要素のparseはいつやるんだ…。中間表現に変換するときに、親要素補完もやってしまえばいいのだろうか。でもそうするためには、中間表現に変換する際に、構造化も一緒にやらないといけない。中間表現は構造化後のデータにしてしまう?