C++用スクリプト言語

Giraffe+のスクリプトをもうちょっとなんとかしようと、LuaとIoとBoost.PythonSwigとClipと他にもいろいろ、そういったもののマニュアルをたくさん読んだんですが、結局どれもいまいち使い勝手がよくなかったり、使える段階に入ってなかったり、そもそも言語が気に入らなかったりして、自分で作ることになりました。

  • プロトタイプベース
  • C++で作る
  • C++で使う

とりあえずこれらを基本方針として作り始めました。
Boost.Spiritでパーサを作り、日本語をそのまま処理できるようワイド文字列(Unicode)ネイティブとし、RTTI、仮想関数、テンプレート、BOOST_PP、Boost.MPLといったものを多用して、C++のクラスをそのままスクリプトのオブジェクトとして扱えるようになりました。
Giraffe+の旧言語をそのまま使えるようにしたため、文法にかなり癖のあるものになってしまいましたが、プロトタイプベースのオブジェクトシステムはそれなりに扱いやすく仕上がりました。文法とシステムは分離してるので、パーサのみを置き換えるということもそれほどの労力を要さずに可能なはずです。
バイナリ ソース
バイナリはreadme.txtすらつけてませんし、ソースはビルドできる状態ですらありませんが、とりあえずこんな感じというリリースです。ソースのテスト部分をサンプルコードとして見てみるのが一番解かりやすいと思います。"gs.exe --eval ..."で"..."を評価します。"gs.exe --help"とすればオプションが表示されます。

//関数型言語によくある階乗のやつ
m: `0[1].|
   `n[n.* recur(n.- 1)]
m(10) // -> 3628800

このコードを適当に解説すると、まず、':'で終わる文字列はdefの省略記法で、def(:m ...)と同じになり、カレントコンテキストオブジェクトのdefメソッドを呼ぶことになります。':'で始まる文字列はシンボルIDのリテラルで、一意の数値になり、オブジェクトのスロットを指定するのに使います。そして、'`'で始まるのはメソッドリテラルで、"`引数式[式]"という形になります。引数式にリテラルがある場合は渡された値と比較され、一致しなかった場合は例外が投げられます。その例外はOR演算子".|"で作られたメソッドに受け取られ、次の候補が試されます。演算子の概念は特殊なため、詳しい解説は別でします。recurはContextのメソッドで、再帰に使われます。末尾再帰の類はサポートされておりません。最後の"m(10)"でカレントコンテキストのmというスロットを呼び出すことになります。コンテキストオブジェクトの解説はオブジェクトシステムと一緒に詳しく別項でします。

//C++のalgorithm
0..10.accumulate // -> 55
"abcdefg".reverse // -> gfedcba
{2 3 1 4}.sort // -> {1 2 3 4}
'aBcDeFG'.=~ r/[A-Z]/.each& `m[m.out] // -> "BDFG" is output to STDOUT.

C++のalgorithmヘッダにある関数をContainerオブジェクトのメソッドとし、Boost.Rangeで扱えそうなものをContainerオブジェクトの子供とすることで、文字列や配列やRuby風のRangeに同一の機能を持たせています。

//Giraffe+での実用
Migemo.open('migemo.dll' 'dict\migemo-dict')
global.AutoCompleteSetList@ `s[
  s.path_is_path.&&[s.path_get_name.size.>1].? [Giraffe:List:AutoComplete:MigemoPartialMatch(s).return]
].& (global.AutoCompleteSetList$)

Migemoオブジェクトはc/migemoC++で使用するために作ったCMigemoをオブジェクト化したものです。':'のdefと同じように、文字列の最後に'@'でset、'$'でgetとなります。メソッドのオブジェクトそのものにアクセスする場合に$が無いとそれを評価してしまうことになります。メソッドのAND演算子は、2つのメソッドを組み合わせ、はじめのメソッドがreturnしなかったら次の候補を評価するというメソッドを作り出すものです。