オペレータを自己定義できる言語の処理方法

OCamlHaskellのように自分自身でオペレータを定義できる言語を考えてみる。


普通のオペレータ、とくに二項演算子は結合順位というものがある。

  • 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