最初に始める時に考えている事
以前ポッドキャストで、小さく始めるのは結構難しい、みたいな話をした。 今日やっていた事はその具体例的な話になってるなぁ、と思いブログを書いてみる。
結構前から数式Viewがandroidにほしい、と思っている。 DynamicDrawableSpanで描けるような奴。 それをなかなか始めずにグズグズしている時の、このグズグズしていることは何なのかなぁ、と思って、それを書きたい。
katexを眺める
まず、katexが移植しやすそうだなぁ、と思ってデモサイトでelementを見てみると普通のCSSっぽい。 次に軽くコードを眺めていると、そんなにブラウザにも依存していないような? html依存な所はかなり最後の段階で、そこだけ書き換えれば良さそうに見える。
試しにビルドしてrenderToStringしてみると、結構レンダリングするのに十分な情報は入ってそうに見える。 テキストを食わせてテキストを出力しているので、割とポータビリティが高いんじゃないか? フォントメトリクスとかどう取ってるのかは良く理解してないが。 buildHTMLを眺めていると、buildCommon的なのまではhtmlには依存してなさそうな雰囲気で、ここまでで大半の作業はやっているようにも見える。
こんな事を、たまにやる気が出た時にちょろっと進める、という感じでやってきた。
小さいニ歩めのある一歩目を探す難しさ
完成形は先は長いので、まずはなんか小さく動いている所までたどり着いて、そこから機能を追加したりしてやっていきたい、と思う訳だ。
小さく始める、という観点からすれば、xを食わせてxをレンダリングする、という話になる。 だが、これはあまり意味が無い気がする。 この意味が無い、というあたりで考えているのは何なのか?
まず、最終的に元のコードをほぼそのまま移植したいと思っている。 この手の物はロジックを変えて、でも結果は同じ、みたいなのは意外と面倒くさいので、完全に同じように動く方が面倒が少ない。 コードがほぼ一対一に対応してて、おかしい所があったらコードを見比べて同じになるように直す、という事が出来るようにしたい。
だから追加していくスタートとなる構造が、あまりにも目標と違うと無駄が大きい。 理想的には元のコードの、どこかのクラスの特定のメソッド以外をなくして、その下のレイヤーをダミーに差し替えてとりあえず動くようにした、くらいから始められると理想的だ。 それがどこか?というのを結構考えている気がする。
また、とりあえずxを食わせてxを表示する、だと、そこまで終わったあとに、次にやる事がいまいちイメージが湧かない。 最初の目標は、そこまで行ったらそこから先は何も考えなくても自然に進められる方がいいよなぁ。
理想的には「あとはこれに足していくだけ」みたいなのの集まりでゴールが見えるのが望ましい。 しかも足す一つ一つは小さい方がいい。 だから一つ目がいくら小さくても、その次にやる事が大きいとあまり意味が無い。
切れ目を考える
以上を踏まえて、とりあえずの目標はxの2乗が表示出来る、を最初のマイルストーンと考えた。 これをどこから手をつけたものか?と考える。 これも結構頭を使ってる気がする。
katex.jsを眺めているとgenerateParseTreeというのがある。 nodeから読んで見ると、意味的なツリーが返ってくるっぽい。 座標とかサイズは含んで無さそう。 とりあえずこのレイヤーでxの2乗が返ってくる、くらいの物を作り、そのあとこのレイヤーより先でxの2乗を表示する、という何かを作れば良い気がする。
パースツリーを作る所より下はどんな適当でも、返ってくるデータさえ正しければ、そこから先の表示のコードは影響を受け無さそう。 という事でこの下を力の限り手抜きして、上を実装する、という事をやっても問題無さそうだし、良い切れ目に思う。
表示周りで何が必要かは現時点では良くわからないが、どちらにせよツリーを作る所までは必要そうだ。 ツリーを作ってる過程でコードの理解も深まるだろう。
ユニットテストやrenderToStringを眺めても、ここは良い切れ目っぽいし、ここから先のレンダリングはまぁまぁ描く先にも依存しそうで、その割にはそこから先はそんなに大きくも無い。良い切れ目っぽい。
という事でgenerateParseTreeでxの2乗をパースする、を最初の目標にする。
何をどこまでやるか考える
とりあえずxを入れてnodeでパースしてみて、このパース結果がkotlinでの実装と意味的に一致する、というのを目指そう。
xという文字列をgenerateParseTreeしてみて、結果を眺める。 そしてそれと同じデータを表すクラスを作ろうと考える。 だが結果の型はAnyParseNodeっぽいが、これのサブクラスは膨大でしかもこのクラスをどう使うかは良く分からない。 とりあえずxだけならmathordが返ってくるっぽいので、なんとなくそこだけ真似する。
次はパーサーだよな、と見てみると、パーサーは結構複雑。 まず、macro expanderというのがあって、この単純なケースの場合はほとんどの仕事がmacro expanderがやる事になりそう。 だが、これが何をやってるかは現時点では良く分からない。
さらにmacro expanderは字句への切り出しはやってなさそうで、別にlexerがある。 そしてmacro expanderのトップレベルのコードは結構複雑で、 この辺をちゃんと実装すればパーサーの半分くらいは完成と言えるくらいな気がしてくる。
何か最初の一歩を作るのに、3つもクラスを作らなくてはいけないのはやや辛い。 だが、Parserクラスだけで字句を切ったりを全部やってしまうと、そこから元のコードの構造にしていくのが大変な気もする。
暫定的な結論
という事で作った第一歩がこの辺。
結局
- generateParseTreeでxを入れた物と同じ結果になるのを比較
- lexerは持ってきて動かしてみた
- lexerとparserはまだつないでない
あたりで第一歩とした。
なお、lexerで[^]という正規表現があり、これがコンパイルエラーになる。 どうも改行まで含めて一文字なんでもヒットする、という意味らしい。
改行周りはテストケースが出てくるまでどうせ試せないので、([^a]丨a)という正規表現を書いておくが、テストはしない。
このあとMacroExpanderを書いてParserとつなげるのは、やれそうな範囲の第二歩になってると思うので、結構いい始め方が出来たんじゃないか。
なんか手をつける気が起こらないなぁ、という時は、半分くらい無意識だが結構いろいろ考えていて、良い開始地点を一生懸命考えているような気もする。 最初の一歩がいい感じの所に踏み出せたら、とりあえず使える所までたどり着く最大の難関は突破したと言っても良いんじゃないか。