readerの実装 の1 : pre-parse

readerは後からどう実装しても替えが効くので適当に作っておく。重要なのは、

  • annotatedなreaderを作る
  • annotateされたcellを扱うためのmapとか

あたり。もっとも、annotateはオブジェクトシステムと連携しないとどうしようもないので、入れられるようにだけしておいて、後から入れることにする。

ステージ

S式のパースは次の3フェーズに分割することにした。

  1. pre-parse
  2. datum-parse
  3. reparse

pre-parseはコメントを読み飛ばし、datum(ただしリストやベクタのような再帰的データを除く)を表す文字列の境界を検出する。この部分は並列化できない。終端していないリストやコメントのようなsyntax errorはここで検出できる。
datum-parseは、文字列から内部データを生成する。例えばUTF-8→UCS4のような変換や文字列のバリデート、浮動小数点のreadの類は比較的時間が掛かる。ここを再帰的にしないためにpre-parseとdatum-parseを分割している。
これらの分割は実用的な高速化ではなく、別の目的(moshのbytestreamライブラリの設計)のために行っている。多分、L1/L2キャッシュを無駄に消費することでパフォーマンスは相当にロスするだろう。また、datum-parseではオブジェクトの生成やシンボルのinternを行うので、その並列性は制限される。
reparseは、文字列補間のような再帰的パースが必要なオブジェクトを(マクロによって要求駆動的に)パースする。

出力データ

((-1.0 2 3/4) (a b c d) #(A B C D))

のようなデータを読むとして、pre-parseの出力データを簡単に言えば、

(("-1.0" "2" "3/4") ("a" "b" "c" "d") #("A" "B" "C" "D"))

のようになることが期待される。もちろん、このままでは本当の文字列なのか、文字列で表現されたdatumなのかが区別がつかないので、適当なタグを付与することが期待される。reader macrosを何処まで処理するかは悩ましい。
ここで実際にオブジェクトやリスト、ベクタを生成してしまうとメモリがもったいないので、実際にはXMLにおけるSAXのようなイベントを生成し、ポインタと長さだけを含むようにする。