スタックマシンで学ぶ template expression
例は考えてあったんだった。template expressionを使えるようになりましょうという話。
template expressionが使えるようになれば、今日からあなたも大量のエラーメッセージとお友達になれること間違いなし!
(ネタ以外に、boost::lambdaのエラーメッセージが読めるようになるとかの効果があるかもしれん)
もうちょっとまともなのを読みたい人は http://homepage1.nifty.com/herumi/prog/prog81.html こちらのほうがよいかと思いますよ。
emit_bytecode( ( x + y + 3 ) )
とかやると、
LOAD_VAR 0xnnnnn LOAD_VAR 0xnnnnn ADD LOAD_CONST 3 ADD
とかって出力されるプログラムを作ってみ…と見せかけて、実際には無理なのでちょっと妥協して、
emit_bytecode( var(x) + var(y) + 3 ) ;
こんな感じで。
まず、構文木を表現するデータ型を定義。
template < typename Lhs , typename Rhs > struct binop { Lhs const lhs; Rhs const rhs; binop ( Lhs const & lhs, Rhs const & rhs ) :lhs(lhs), rhs(rhs) {} }; template < typename Lhs, typename Rhs > struct add :public binop<Lhs,Rhs> { add ( Lhs const & lhs, Rhs const & rhs ) : binop<Lhs,Rhs>(lhs,rhs) {} }; template < typename Lhs, typename Rhs > struct sub :public binop<Lhs,Rhs> { ....(続く…mul、div、or…)
基本基本。ここらへんは多分どう書いても一緒になるのでいいや。上のように、LhsとRhsを持つテンプレートを必要なだけ定義。あんまり考えることは無い。
んで、演算子を定義。
template < typename Lhs, typename Rhs > detail::add< Lhs, Rhs > operator +( Lhs const &lhs, Rhs const &rhs ) { return detail::add<Lhs,Rhs> ( lhs, rhs ); } template < typename Lhs, typename Rhs > detail::sub< Lhs, Rhs > operator -( Lhs const &lhs, Rhs const &rhs ) { ....(続く)
これもあんまり考えることは無い。
んで、構文木の葉を定義。今は変数があればよいか。
namespace detail { template < typename VarType > struct var_ { VarType &var_ref; var_ ( VarType &ref ) :var_ref(ref) {} }; } template < typename T > detail::var_<T> var( T &ref ) { return detail::var_<T>(ref); }
(var_はboost::lambdaではidentityとかいう名前だった)
別に var_
ここまでまとめ。構文木作るところ。
- データ型作る
- operator 書く
- var 書く
あんまり考えることは無い。
次に、構文木の評価。
名前は…evalとかでいいんだけど。今回のテーマっぽく、名前はemit_bytecodeにしよう。(文字列出力するんだけど)
これもあんまり考えることは無い(そればっかだな…)
まずは、intの出力。
template < typename OutputStream > void emit_bytecode( OutputStream &out, int val ) { out << "LOAD_CONST " << val << "\n"; }
で、次は加算でもするか…
template < typename OutputStream, typename Lhs, typename Rhs > void emit_bytecode( OutputStream &out, detail::add<Lhs,Rhs> &expr ) /* にばんめの引数がaddね */ { emit_bytecode( out, expr.lhs ); emit_bytecode( out, expr.rhs ); out << "ADD\n"; }
ほいできた。あんまり考えることはない…
int main() { emit_bytecode( std::cout, 3 + 4 ); }
ように見せかけて、これが動かないのだった。(LOAD_CONST 7が出力される)
これは、非常に汚い解決法があって、
struct intval { const int val; intval( int val ) :val(val){} };
こんなのをつくって、
template < typename OutputStream > void emit_bytecode( OutputStream &out, intval val ) { out << "LOAD_CONST\t" << val.val << "\n"; }
こうして。
int main() { emit_bytecode( std::cout, intval(3) + 4 ); }
こう!
$ ./stackmachine LOAD_CONST 3 LOAD_CONST 4 ADD
ほいできた!
おっと!このぐらいで「C++ってたいへんアレですね」とか思った人はもうだめです。
さて!じゃきじゃきいきましょう。変数出力
template < typename OutputStream, typename T > void emit_bytecode( OutputStream &out, detail::var_<T> v ) { out << "LOAD_VAR\t" << &v.var_ref << "\n"; }
ほいほい!あっというま!あんまり考えることなんかない!
int main() { int x, y; emit_bytecode( std::cout, var(x) + 4 + var(y)); }
$ ./stackmachine LOAD_VAR 0xbf8700cc LOAD_CONST 4 ADD LOAD_VAR 0xbf8700c8 ADD
あとは、mulとかsubとかつくって終了!
以下、大体のソース。途中でやめてるけど。
なお、バイトコードを最適化してx86マシン語にしてPEバイナリ生成するのは読者の宿題とする。
続きを読む