2011-05-15 4 views
4

私のプログラムの1つは、実行時に(kill fooのような)コマンドを受け入れます。これをドメイン固有の小さな言語と考えてください。ここではいくつかの例です:CでDSLを解析するためのlex/yaccよりも優れたソリューションですか?

kill 
kill client 
exit 

しかし、また、チェーンのコマンドが許可されていると空白は、コマンドの前と後に有意ではないので、以下の例も有効です。

kill ; say "that was fun" 
    kill ; kill  ; kill; 

私は現在、これを実装していますlex/yacc(flex/bisonを指定する)と頭痛の原因となっていました。レクサーは文脈に非常に依存します(例:killキーワードの後でなければ、空白トークンは返されません)、多くの異なる状態があります。文法はコンフリクトがあったので、指定しなければならない形式(特に$ 1、$ 2、$ 3、...非終端記号用の引数を使用する)が気に入らない。また、bisonが提供するエラーメッセージ(解析時)は正確な場合もありますが、多くの場合、エラーメッセージは表示されません。killコマンドでオプションの引数を指定すると、の代わりにUnexpected $undefined, expected $end or ;というエラーメッセージが表示されます。kill client)最後に、yaccのC APIは残酷です(外部はどこにでも定義されています)。

上記の質問をすべて解決するようには依頼していません(私は、lex/yaccの周りに道がない場合は、より具体的な説明とコードで個別のスレッドを開きます)。代わりに、私はlex/yaccの代替案に興味があります。

私の基準は次のとおりです。

  • 入力が文字列(のconstのchar *)で、そこには出力されませんが、代わりにいくつかのコードは、それぞれ異なるキーワードに呼び出さなければなりません。
  • 私はこれをC(C99)と使いたいです。
  • ソフトウェアは、主要なLinuxディストリビューションに含まれているか、少なくともバンドル/パッケージが簡単である必要があります。
  • 十分に文書化されていなければなりません。
  • 私の言語を記述するための構文は簡単でなければなりません。
  • エラーの解析時に意味のあるエラーメッセージを出力するはずです。
  • パフォーマンスはそれほど重要ではありません(もちろん、高速であるべきですが、一般的な使用例はインタラクティブな使用であり、MBのコマンドを処理しません)。
+1

キル、キル、キル! – Philip

+0

@Philip:ええ、あなたは、すべてのレクサー/パーサーのものを乱してしまった気分を味わえます:) – Michael

答えて

1

私はANTLRが好きで、生産システムで数回使用しました。

バージョン2では、C++コードを生成することはできましたが、Cは生成できませんでしたが、バージョン3ではCコードは生成できますが、C++は生成できません。私はC++が好きで、まだANTLR v2を使用していますが、おそらくv3をお楽しみいただけます。そんなにあなたのために良い。

多くのディストリビューションにはANTLR v2パッケージがあり、一部にはv3もあります。それはかなりよく文書化されています(私はv2を使用していますが、v3はこの点で悪くはないと思います)。

ANTLRは「すぐに使える」超高速パーズエラーメッセージを生成しません。これは、最も一般的なパーサシステムに共通するものであり、根本的に解決するのは簡単な問題ではありません。しかし、いくつかの作業で、ANTLRベースのシステムからの診断結果が得られました(ANTLRには何も言えません)。

+0

私はこれがコンパイル時に私のプロジェクトに追加するjava依存関係について少し懐疑的です。私はこの権利を得ますか? – Michael

+0

コードジェネレータを実行するにはJavaが必要です。しかし、Javaは多かれ少なかれ利用可能であり、利用できない場所があれば、別のプラットフォームでコードを生成し、それを奇妙なものにコンパイルするだけの理由はありません。 –

3

非常にシンプルで小さな文法については、手書きでレクサー/パーサーを書くことを検討しています。多くの場合、あまり効果がありません。

実質的にすべてのLinuxディストリビューションには、lex/yaccの亜種が出荷されています。それ以外の広く使われている2つのパーサジェネレータはlemonantlrです。

1

Lexの興味深い代替手段& YaccはLemonパーサーです。それはかなり進んでいますが、私はそれを本格的に使っていないので、それが本当にうまくいくかどうかは完全にはわかりません。これはSQLiteによって使用されます。

0

lexerlessパーサーが必要です(例:PEG)。 Cを使っていてyaccに慣れているので、thisのようなものを試してみる価値があります。

文法が単純な場合は、代わりに随時再帰下降パーサーを実装できます。

+0

私は実際にこの質問をする前にペグ/脚を見つけましたが、以下の点で私は興味を失いました。比較的まばらな文書(マンページだけです)があるようです。 Debianにはパッケージ化されておらず、活発に開発されていないようです。 – Michael

+0

@Michael、助けてくれない - 私は自分でPackratを実装した。おそらくあなたは同じことをするつもりです。結局のところそれほど複雑ではありません。 –

3

あなたの言語はかなりシンプルなので、入力をトークン化して解析する有限状態マシンを実装することをお勧めします。

空白でトークン化する(引用符で囲まれていない文字列ではない)場合は、一度に1文字ずつ読むだけです。各 "コマンド"は、コマンド引数を解析する別の状態でマシンを取得します。 ";"または "\ n"はマシンを開始状態にリセットします。

+0

これはどのように見えるか簡単な説明をありがとう。実際、私の言葉はちょっと複雑です(問題の簡単な部分だけを明らかにしました)。しかし、私はあなたのアプローチについて考えます。 – Michael

1

Ragelをお勧めします。私は最近、それを使い始めると、一度スピードアップしてから作業するのが楽しいと感じました。あなたの例では、(:テストされていません!注):ような何かを行う可能性があります

#include <stdio.h> 
#include <string.h> 

%%{ 
    machine my_cmd_lang; 

    action pk { printf("Killing %.*s\n", fpc-mark, mark); } 
    action mk { mark = fpc; } 

    k = 'kill'; # creates a machine that doesn't do anything 
    x = 'exit' @{ printf("Exiting\n"); }; 
    arg = alpha+ >mk; # arg to kill is built in machine 'alpha' 1 or more times 
    cmd = ((k space arg) @pk space* ';'?) | x; 
    main := cmd* ; 
}%% 

%% write data; 

int main(int argc, char* argv[]) { 
    int cs; 
    char* p = "kill client"; 
    char* pe = p + strlen(p); 
    char* mark; 

    %% write init; 
    %% write exec; 

    return 0; 
} 

ragel <filename.rl>でRagelを通してそれを実行し、それが<filename.c>を吐き出すます。

関連する問題