yutopp's blog

サンドバッグになりたい

!2!文鳥言語にプルリクください!2! - Aizu Advent Calendar 2014(3日目)

おはようございます.Aizu Advent Calendar 2014,3日目の@yutoppです.
25日分埋まりましたありがとうございます!!!!

さて今日も,Rill(a.k.a. 文鳥言語)についてポエムを書きます.
この記事の要約をすると,"pullreqください" になります.

Rill

文鳥言語にプルリクください - Aizu Advent Calendar 2014(1日目)

今日は?

Rillの構文が変わり,一昨日書いたサンプルコードが早速動かなくなりました.ユーザが自分しか居ないので,言語機能の破壊的変更もなんのそのです.
テンプレートの構文が変わり,より短く記述できるようになりました.

ちなみに,「D言語パクった?」「劣化C++」「劣化D言語」「劣化Embedded C++」などというコメントは大歓迎です.

サンプルの一部

一昨日のやつ

template(T: type)
def advent(_: u, c: T) {
    p("Advent");
    return \() => p(c);
}

今日のやつ

def advent!(T)(_: u, c: T) {
    p("Advent");
    return \() => p(c);
}

その他のサンプルはここにあります!

おわり

このように,Rillの構文や意味付けは数日単位で思いきり変わります.
Rillに関する何かがあれば,喜んで取り入れるのでissueやpullreqメッチャください.一緒に文鳥言語を作りませんか?とてもアットホームな鳥小屋です.

次は〜

(◔⊖◔)つ @i__yahoo

前は〜

@youxkei ⊂(◔⊖◔)

文鳥言語にプルリクください - Aizu Advent Calendar 2014(1日目)

おはようございます.Aizu Advent Calendar 2014一日目の@yutoppです.
現段階で9人枠が余ってるので各位頼む頼む頼む!!!!

さて今年は,Rill(a.k.a. 文鳥言語)についてポエムを書きます.
この記事の要約をすると,"pullreqください" になります.

Rill

Rillは趣味で作っているプログラミング言語です.

yutopp/rill

自分はC++をよく使うのですけど,構文に対して「こう書けたら良いのになあ」と思うことが多く,その感情を解決してみたかったということと,型理論LLVMの勉強したさで実装を始めたのがこの処理系です.ア,C++は大好きですよ.
意味が直感的で落とし穴が少なく,ライブラリが作りやすい言語が目標です.[要出典]
C++Ruby,Go,D,Scalaを主に参考にしています.
静的型付けです.また,この処理系が仕様です.

あれこれ

  • 仮引数は,デフォルトでconst参照.
  • 値と参照の扱いはC++に似ている.コピーとムーブがある.参照自体の値が欲しければポインタを使う.
  • 型に生存期間の情報を付けて,リソースを適切に扱えるようにする.
  • GCやスレッドサポートも入れたいところ.もちろんオプションで切り離し出来るようにする.
  • コンパイル時処理はD以上に充実させたい.ライブラリを作りやすくするにはメタプログラミングの手厚いサポートが必要だという考えから. 現状の処理系でも,意味解析中にLLVM IRを生成し,IRをJITコンパイルして呼び出すような実装になっているので,Rillの言語機能の全体に近いサブセットをコンパイル時にも実行できるようになっているはず.既にintなどの型情報はコンパイル時はtype型の値として扱われていて,例えば,const(int)はただの関数呼び出しであり,コンパイル時にコンパイラ内部のconstという関数intの型を表す整数値を渡して呼び出すLLVM IRを生成してJITコンパイル,実行され,const属性が付加されたint型を表す整数値を生成する.今のところ上手くいっているけれど,コードが大きくなると重たそうでア!
  • それでいてC++並の自由さが欲しい.
  • "C++の設計と進化"は最高.

つまり?

つまりRillは,文鳥が,小さいライブラリとランタイムを組み合わせて自由に楽しくコードを書けるプログラミング言語(になる|という)わけです.

ちなみに,「Rustと被ってない?」「劣化C++」「劣化D言語」「劣化Embedded C++」などというコメントは大歓迎です.

部分的に実装されている機能

サンプルコード

def main(): int {
    val i = "i";
    ref calendar = "Calendar";
    (Aizu() + i).f("u").advent(calendar)();
    return 0;
}

class Aizu {
    def ctor() {
        p( "A" );
    }

    def op +(v: ptr!int8) {
        p(v);
        return u("z");
    }
}

class u {
    def ctor(v: ptr!int8) {
        this.f(v);
    }

    def f(v: ptr!int8): ref(u) {
        p(v);
        return this;
    }
}

template(T: type)
def advent(_: u, c: T) {
    p("Advent");
    return \() => p(c);
}

これをコンパイルして実行すると

bytes => A
bytes => i
bytes => z
bytes => u
bytes => Advent
bytes => Calendar

と出力されます.(bytes =>はランタイムのデバッグログ)

pは標準ライブラリに含まれる関数です.ptrint8typeも標準ライブラリで定義されるクラスです.(標準ライブラリ)
このサンプルではテンプレートや演算子オーバーロード,UFCS,ラムダ式を使ってみました.

その他のサンプルはここにあります!

おわり

プログラミング言語を作るのは楽しすぎてつらいですよね.
Rillは全体的に構文や意味付けも未完成ですので,issueやpullreqメッチャください.一緒に文鳥言語を作りませんか?とてもアットホームな鳥小屋です.

次は〜

(◔⊖◔)つ @youxkei

鳥小屋のシンチョク#1

9月に入ってしまうので,鳥小屋のここまでのシンチョクのまとめ.

去年のBoost.勉強会 仙台で"鳥小屋を支える技術"というLTをしたものの,今や過去の技術は全て鳥小屋と共に倒壊しました.

Wandboxがつよくてつらい.

鳥小屋 is 何

いわゆるオンラインコンパイラです.

ProcGarden

ProcGarden(最新)

シンチョク

おわり

つらい

LLVMと俺俺ランタイムで例外を実装する(その1)

環境: Arch Linux(x86_64), LLVM 3.4, GCC 4.9.0

文鳥言語の言語機能に例外を追加したかったので,先にLLVM側の調査とランタイムを実装しました(進捗).自分で思い出すために後々読み返しそうなので,例外ハンドリングについてメモっておきます.

何かの参考になれば幸い.間違いなどは指摘していただけると嬉しいです.

今回実装したのはLinuxx86_64用の実装でtable-basedのものです.

コードはこちら yutopp/llvm-exception-handling-test

例外ハンドリング

LLVMの例外ハンドリングのドキュメントはこちら Exception Handling in LLVM

今回は,このドキュメントで示されている"Itanium ABI Zero-cost Exception Handling"を元に自前の例外ランタイムを実装したことになりますが,このドキュメントは,CやC++で使われている例外ハンドリングについて深く説明しているため,実際に言語のランタイムに実装するとなると少し物足りない感じがありました.でも読みやすくてスバラシイ〜

そして,次に参考にしたのが,LLVM PROJECT BLOG.New libunwind implementation in libc++abi

大体

例外のunwindには,libcxxabiに含まれるcxa*例外関数群が要求するUnwind*関数群(高レベルAPI)と,old HP libunwind projectで定義されたインターフェースであるunw*関数群(低レベル操作)の二通りがある.

多くのアーキテクチャでは”ゼロコスト”例外をC++に使っている.ゼロコストとは,例外が発生しない通常の実行では余計な命令が生じないものである.しかし,例外が投げられた場合には,ランタイムはどのようにレジスタをリストアし,現在のスタックフレームからcatch項まで"unwind"するかを,side tableを参照し把握しなければならない. スタックフレームを修正,全てのレジスタをリストアし,適切にスレッドをcatch節にresumeさせることが,libunwindの主な用途である.

みたいな事が書いてありました.

Itanium C++ ABI: Exception Handling でいうと,”libcxxabiに含まれるcxa*例外関数群” が”Level II: C++ ABI”以降,”Unwind*関数群”が,”Level I. Base ABI”になるようです.

従って,今回はLLVMと共に自前の例外ランタイムを実装したいので,このC++ ABIを使わずに,この部分を自前で実装すればいいことになります.また,Level 1 ABIの実装は共通なので,既にある実装を使うことができます. というわけで,Level 1 ABIについては,今回は既存の実装を利用しています(申し訳 of the world).

フォーマット

LLVMが生成してくれるASM テーブルフォーマット(ドキュメントてきとう訳)

例外が投げられた際に,どんな処理を取るべきか決定するために例外ハンドリングランタイムに使われるテーブルが2つあります.

Exception Handling Frame

例外ハンドリングフレームであるeh_frameは,DWARF debug infoに使われているunwind frameにとても良く似ています. このフレームは,現在のフレームをtear downし,以前のフレームの情報をリストアするために必要な全ての情報を含んでいます. コンパイル単位における,それぞれの関数の例外ハンドリングフレームに加え,全ての関数の共通する情報を定義した一般的な例外ハンドリングフレームも存在します.

Exception Tables

例外テーブルは関数のコードのある部分で例外が投げられた際に,どのアクションを起こすべきか,という情報を含んでいます. 例外テーブルを必要としないリーフ関数(注:関数テーブルエントリ(スタックフレームなど)を使用しない関数のこと)やnon-throwな関数のみを呼び出す関数を除いて,各関数には一つの例外テーブルが存在します.

これらのフォーマットの読み込みをコードに落とすのに,以下のスライドがとても参考になりました.

Compiler Internals: Exceptions and RTTI

また,例外処理で出てくる用語は以下の感じ

単語 意味
LSDA Language Specific Data Areaのこと.
landingpad 例外が投げられたあと,catch,またはclean upするための,ユーザーコードのセクションのこと.personality routineによってexception runtimeからコントロールを得た後,通常のユーザーコードに合流させるか,resumeによってランタイムに戻る,新しい例外をraiseするかといった目的に用いる.
personality routine C++(や他の言語)のランタイムライブラリにある,system unwind libraryとlanguage-specific exception handling semanticsとの間のインターフェースを提供する関数のこと.

LLVMでの流れ

  • landingpad 命令で例外の着地点をつくる
  • @llvm.eh.typeid.for を用いて例外を選択する

おわり

今回実装したもの

  • 言語固有の例外ハンドリングの関数(allocate, throw, resume, free)
  • personality routine

言語固有の例外ハンドリングの関数

C++ ABIを真似て適当に作った.

personality routine

今回の山場でした.unwindを行うライブラリと言語固有の機能のインターフェースを取り持つ関数です.

2段階のフェーズに分かれて呼ばれるようで,最初の探索フェーズで

  • LSDAの読み込み
  • landingpad の選択
  • call siteやaction_tableの読み込み
  • 適切なcatch節やclean upの選択
  • その情報の保存

を行い.

次の段階で,

  • 第一段階の情報の引き出し
  • 目的の節へのジャンプの登録

を行います.

C++D言語のランタイムの実装を参考にしました!

eh_personality.cc Source File

druntime/src/ldc/eh.d at ldc · ldc-developers/druntime

おわりに

くぅ〜疲れました!w

Binary Hacks ください.

その2に続く→

参考資料

dzeta.jp技術資料 - NPTLとC++例外処理 force_unwindの存在をこれで知りました.

g++の例外を捕まえよう!!(素手で)

exception handling | 0xdeadp0rk

Mono:Runtime:Documentation:LLVM

unwind自体の実装

gcc/libgcc/unwind-dw2.c at master · mirrors/gcc https://llvm.org/svn/llvm-project/libcxxabi/trunk/src/Unwind/UnwindLevel1.c

LLVM IRの構造の可視化をするメモ

LLVM IRの構造をグラフで見たかったので試してみました.メモ.

参考資料:

LLVM’s Analysis and Transform Passes

Visualizing code structure in LLVM

環境: Arch LinuxLLVM 3.4,graphviz 2.36.0

試す

試しに使ったC++コード(test.cpp)

ProcGarden - test.cpp (libc++がおかしいのでclangでなくGCCでビルドしています…ウッ)

そして,この test.cppclang++ test.cpp -emit-llvm -c -SLLVM IRにした結果は以下(test.ll)

ProcGarden - test.ll

このLLVM IRとoptコマンドを用いてdotファイルを生成し,それをgraphvizで可視化する.

CallGraph

opt test.ll -dot-callgraph > /dev/null

e.g. CallGraphをgraphvizを用いてPNGにする

dot -Tpng callgraph.dot > callgraph.f.png

f:id:yutopp:20140503003625p:plain

CFG

opt test.ll -dot-cfg > /dev/null

e.g. 関数fのCFGをgraphvizを用いてPNGにする

dot -Tpng cfg._Z1fv.dot > cfg.f.png

f:id:yutopp:20140503003546p:plain

Dominance Tree

opt test.ll -dot-dom > /dev/null

e.g. 関数fのDominance Treeをgraphvizを用いてPNGにする

dot -Tpng dom._Z1fv.dot > dom.f.png

f:id:yutopp:20140503003640p:plain

Post-dominance Tree

opt test.ll -dot-postdom > /dev/null

e.g. 関数fのPost-dominance Treeをgraphvizを用いてPNGにする

dot -Tpng postdom._Z1fv.dot > postdom.f.png

f:id:yutopp:20140503003658p:plain

おわり

次はLLVMで例外を使ってみる.

鳥小屋でOpenJDK9を動かしたときのメモ

オンラインコンパイラにOpenJDK9を入れた後にハマったのでメモ

オンラインコンパイラの実行環境ではメモリリソースに制限をかけている(rlimit_asで1.5GB程度)ので、そのままのJVMの起動ではリソース不足で動かなかった。だいたいJVMが2GB使うようだったので微妙に足りない…。

結局解決できたので、そのオプションを書いておく。
Java8からPermGenというものがMetaspaceというものに変わったようなのでそこもチェックする。

まずヒープ
-Xms 64m
-Xmx 128m

スタック
-Xss 512k

Metaspace
-XX:MaxMetaspaceSize=128M
-XX:MetaspaceSize=64M

ここまで指定すると
"Could not allocate metaspace: 1073741824 bytes"
と遺してJVMが死んでしまう。

そこで、以下のようにオプションを指定したところエラーは発生しなくなった。
-XX:CompressedClassSpaceSize=32M

-XX:CompressedClassSpaceSizeのデフォルト値は1024Mであり、エラーの内容とも一致しているのでこれが原因のはず。
エラーの発生箇所がjdk8/awt/hotspot: 209aa13ab8c0 src/share/vm/memory/metaspace.cppだったので、この値を変えてみたのだけど、結局これはなんなのだろう…

とりあえず、これでJVMのメモリ使用量が1GB程度になって無事動かせるようになった。


参考

文鳥言語とBoost.Spirit.Qi Tips - C++ Advent Calendar 2013(11日目)

これは C++ Advent Calendar 2013 - PARTAKE 11日目の記事です。

この記事では俺々プログラミング言語の紹介と、C++処理系を作るにあたって便利そうなBoost.Spirit.QiのTipsを紹介します。
Boostは1.55.0が対象です。

文鳥言語とは

文鳥言語とは、私が趣味で作っているプログラミング言語です。
yutopp/rill · GitHub

C++は(人間の)プログラマが書いていて楽しく感じるように作られている言語ですが、文鳥言語は文鳥が書いていて楽しく感じられるように作られています。
文鳥 - Google 検索
カワイイヤッター!(?)

文鳥言語を支える技術

文鳥言語の実装には、全体的にC++11、構文解析器にBoost.Spirit.Qi、コード生成にはLLVMを用いています。
構文解析器とコード生成にライブラリを使っている時点で、言語を自分で作っている感は薄いのですが、常にそれなりに動く状態で遊べるのでとても楽しいのです。

今の段階では、このようなコードをコンパイルすることができます。単純なFizzbuzzです。

def main(): int
{
    print( "hello, bunchou lang on Linux!!!bunbun!\n" );
 
    val i = 1: int mutable;
    while( i < 100 ) {
        if ( i % 15 == 0 ) {
            print( "Fizzbuzz " );
        } else if ( i % 5 == 0 ) {
            print( "Buzz " );
        } else if ( i % 3 == 0 ) {
            print( "Fizz " );
        } else {
            extern_print_int( i ); print( " " );
        }
 
        i = i + 1;
    }
    print( "\n" );
    // Test
    overload( 2, 5 );
    overload( "bun!" );
 
    return 0;
}
 
def overload( val a: int, val b: int ): void
{
    print( "====================\n" );
    print( "test_scope\n" );
    print( "====================\n" );
 
    val i = 42: int mutable;
    {
        val i = 72: int;
        print( "inner: " ); print_int( i );
    }
    print( "outer: " ); print_int( i );
}
 
 
def overload( ref s: string ): void
{
    print( "====================\n" );
    print( s );
    print( "\n" );
}
 
//
extern def extern_print_int( val :int ): void "put_string2"; 
 
def print_int( val i: int ): int { extern_print_int( i ); print( "\n" ); }

出力バイナリの実行結果

hello, bunchou lang on Linux!!!bunbun!
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizzbuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 Fizzbuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 Fizzbuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 Fizzbuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 Fizzbuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 Fizzbuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz 
====================
test_scope
====================
inner: 72
outer: 42
====================
bun!

かわいいですね。今は基礎部分に力を入れて開発をしています。いつか、これでリンカなど作りたいですね。

さて、この言語処理系を作るにあたって、Boost.Spirit.Qiが大活躍しています。

Boost.Spirit.Qi Tips

Boost.Spirit.Qi については、インターネッツを検索するとためになる資料が出てきますのでそちらを参考にして下さい。

さて、Qiについて色々調べていると、様々なつぶやきが散見されます。

  • とっつきにくい
  • コンパイル時間がBoostする
  • コンパイルエラーが意味不明
  • 非常に明快で分かりやすい

このコンパイルエラーが意味不明、というのがとっつきにくい最大の問題だと思います。

私が初めてBoost.Spiritに触れたのは中学3年生の頃(某とは関係ありません)でした。Boostは1.43.0でVC++2005な環境だった気がします。
その時はSpiritのコンパイルエラーに精神を破壊されたのですが、今ならSpirit.Qiをとても楽に使いこなせるはずです。

そう、Clangとならね。

Clangを使う

パーサを書くときは、Clangを使ってコンパイルするのがオススメです。
Clangはコンパイルエラーが優しいという事で有名ですが、この優しさがQiを使うときにも活きてくるためです。

例えば、以下のようなコードがあるとします。

#include <iostream>
#include <vector>
 
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
# define BOOST_SPIRIT_USE_PHOENIX_V3
#endif
#include <boost/spirit/include/qi.hpp>
 
#include <boost/fusion/include/adapt_struct.hpp>
 
struct wrapper
{
    std::vector<int> v;
};
 
BOOST_FUSION_ADAPT_STRUCT(
    wrapper,
    (std::vector<int>, v)
)
 
int main()
{
    namespace qi = boost::spirit::qi;
 
    std::string const test_input = "1, 2, 3, 4, 5";
 
    auto it = test_input.cbegin();
    auto const last = test_input.cend();
 
    wrapper result;
 
    auto const r = qi::int_ % qi::lit( ',' );
 
    auto const s = qi::phrase_parse( it, last, r, qi::ascii::space, result );
    assert( s );
    assert( it == last );
 
    for( auto const& i : result.v )
        std::cout << i << " ";
    std::cout << std::endl;
}

これはコンパイルエラーになるコードなのですが、ここで、Clang と GCC からのコンパイルエラーをご覧ください。(Wandboxありがとうございます)

GCCのコンパイルエラーはパッと見どの箇所がエラーの原因なのか分かりにくく、つらい感じがします(19行目)。
今回はシンプルなコードなので比較的特定しやすいのですが、構文のruleが増えてくると、この分かりにくさは地獄を生み出します。

比べてClangのコンパイルエラーの優しさといったらなんでしょう!11行目でバッチリ`value_type`が無いと教えてくれていますね。
これで、as でくくって要件を満たす一時オブジェクトを渡してやればいいんだな、とすぐに理解することが出来ます。

auto const r = qi::int_ % qi::lit( ',' );

auto const r = qi::as<std::vector<int>>()[ qi::int_ % qi::lit( ',' ) ];

に修正。やりました!

TemplateAliasesを使う

ruleをgrammerにまとめる場合はメンバにrule型の宣言を置くことになり、型を推論させることが出来ません。

なので、例えば

qi::rule<Iterator, ast::statement_ptr(), skip_grammer_type> statement_;

のような宣言を大量に書くことになるはずですが、Iterator や skip_grammer_type などを毎回書くのが面倒なので

template<typename T>
using rule = qi::rule<Iterator, T, skip_grammer_type>;

などとしておくと

rule<ast::statement_ptr()> statement_;

と書けるようになるので幸せだと思います。

これはVC++2013で使えるので使わない手はありませんね!
C++11/14 Core Language Features in VS 2013 and the Nov 2013 CTP - Visual C++ Team Blog - Site Home - MSDN Blogs

ruleを細かく分ける

ruleを細かく分割することによって、コンパイルエラーとなる箇所の特定がとても楽になります。
上記の2つと合わせて、rule を細かく分けるのがオススメです。

rule の 型をしっかり見る

rule<ast::statement_ptr()> statement_;

ruleに渡すast::statement_ptr()などの()は忘れがちでハマるので気をつけましょう。
引数を渡すruleを作ることが少ないので忘れがちになりますね…

Phoenix V3 を使う

Spirit関連のファイルをインクルードする前に

#define BOOST_SPIRIT_USE_PHOENIX_V3 1

とすることで、Boost.Phoenix 3が使われるようになります。
関数オブジェクトを作るのが楽になっているはずなので、Phoenix は 3 を使いたいところ。

make_shared を作る

文鳥言語では、ASTの組み変えが発生しそうなのでコピーコストが低いであろうshared_ptrを使ってASTを構築しています。
なので、SemanticActionにてオブジェクトを生成する必要があるのですが、Phoenixには相当の関数がデフォルトで無いようなので作ります。

template<typename T>
struct make_node_pointer_lazy
{
    typedef std::shared_ptr<T> result_type;

    template<typename... Args>
    auto operator()( Args&&... args ) const
        -> result_type
    {
        return std::make_shared<T>( std::forward<Args>( args )... );
    }
};

template<typename T, typename... Args>
auto make_node_ptr( Args&&... args )
    -> decltype( boost::phoenix::function<make_node_pointer_lazy<T>>()( std::forward<Args>( args )... ) )
{
    return boost::phoenix::function<make_node_pointer_lazy<T>>()( std::forward<Args>( args )... );
}

(Allocatorを考慮するの忘れていた…)
ひとまずコレで

some_rule_[qi::_val = make_node_ptr<HogeClass>( qi::_1, ... )]

のようにnodeが簡単に作れるようになるので、便利かもしれません。

参考: Boost.Phoenix V3 - 関数のbind化 - Faith and Brave - C++で遊ぼう

パース時に位置情報を取る

c++ - boost::spirit access position iterator from semantic actions - Stack Overflow
参考になるのでどうぞ…
(line_pos_iteratorの実装ってどうなんでしょう…ウーン)

Assertion `rhs.f && "Did you mean rhs.alias() instead of rhs?"' failed. is 何

未初期化のruleを直に代入すると、このassertionにひっかかります。

#include <iostream>
#include <vector>

#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
# define BOOST_SPIRIT_USE_PHOENIX_V3
#endif
#include <boost/spirit/include/qi.hpp>

#include <boost/fusion/include/adapt_struct.hpp>

template<typename Iterator>
class code_grammer
    : public boost::spirit::qi::grammar<Iterator, int>
{
public:
    code_grammer()
        : code_grammer::base_type( a_ )
    {
        a_ = b_;

        b_ = boost::spirit::qi::int_;
    }

private:
    boost::spirit::qi::rule<Iterator, int> a_, b_;
};

int main()
{
    namespace qi = boost::spirit::qi;

    std::string const test_input = "42";

    auto it = test_input.cbegin();
    auto const last = test_input.cend();

    int result;
    code_grammer<decltype(it)> r;

    auto const s = qi::phrase_parse( it, last, r, qi::ascii::space, result );
    assert( s );
    assert( it == last );

    std::cout << result << std::endl;
}

code_grammerの

a_ = b_;

が、未初期化の b_ を代入しているのでダメですね。

解決策

  • 素直に a_ と b_ の代入位置を入れ替える
  • .alias() を使う
a_ = b_.alias();
  • %= を使う
a_ %= b_;

おわりに

ここまでBoost.Spirit.Qiを使って構文解析器を作った段階で踏み抜いた罠を紹介しました。
Spiritは実際便利なのであらゆる場所で使われているはずなのですが、少し初見殺しなところがある上に、罠から抜け出す術が中々載っていないので、Tipsが少しでも役に立てば幸いです。

では