オペレータを自己定義できる言語の処理方法
OCamlやHaskellのように自分自身でオペレータを定義できる言語を考えてみる。
普通のオペレータ、とくに二項演算子は結合順位というものがある。
- foo.bar.buzz = bar.buzz = 10
では.はオペレータであり、同じ演算子で並ぶと左優先で結びつく
- ((foo.bar).buzz) = (bar.buzz) = 10
であり、さらに=もオペレータで、こちらは右優先で結びつく
- (((foo.bar).buzz) = *1
優先順位はすなわち計算順序となる。foo.barの結果oに対して、o.buzzが処理される。
この優先順位はオペレータが固定的な言語ではパーザー上で定義されている。
二項演算子のパーザーを定義する場合、演算子記号と優先方向と優先順位(.のほうが=より優先して結びつく)が必要である。
つまりオペレータを自己定義できる言語、とはパーザーを定義できる言語に他ならない。
一番簡単な方法はいわゆるプリプロセッサ処理してしまうことだ。たとえば、
#operator(+, left, 4, plus)
とか書いておき、まず#operatorだけを読み込む。その情報からパーザーをつくり、処理する。
この方法での問題はモジュール機構である。モジュール側で演算子を定義したい場合、モジュール読み込みもプリプロセッサ処理しなくてはいけなくなる。パーザー定義のための機構は別扱いするという手もあるが、つかう場合に使うモジュールを二回(パーザー作成のための読み込み宣言とモジュールロード文を)書く必要が出るので書く側はめんどくさいかもしれない。
あとはモジュールを読み込み処理ではなく宣言にしてしまうとか。明確的なプリプロセスではなく、状態つきパーザーを使えるので、言語にあった文法にしやすい。言語的にはこれでいい場合が多い。
ただ、自分の関心は動的言語だ。この場合、モジュールはオブジェクトであり、処理が走るとき、そのrequire時点で読み込んでほしい。requireが走る時点ではすでにコードはパーズされている必要があるため、遅いのである。
さて、なにかうまく実装できる方法はないだろうか。あとオペレータを自己定義できるようにすると、リテラルも自己定義したくなる。
*1:bar.buzz) = 10