私は、avr-gccツールチェーンを使用したCのAVRマイクロコントローラーの演習として、言語のような単純なBASIC用の小さなインタープリターを書いています。ただし、レクサーとパーサーの作成に役立つオープンソースツールがあるかどうか疑問に思っています。
Linuxボックスで実行するようにこれを作成する場合は、flex / bisonを使用できます。8ビットプラットフォームに制限したので、すべて手作業で行う必要がありますか?
私は、avr-gccツールチェーンを使用したCのAVRマイクロコントローラーの演習として、言語のような単純なBASIC用の小さなインタープリターを書いています。ただし、レクサーとパーサーの作成に役立つオープンソースツールがあるかどうか疑問に思っています。
Linuxボックスで実行するようにこれを作成する場合は、flex / bisonを使用できます。8ビットプラットフォームに制限したので、すべて手作業で行う必要がありますか?
回答:
ATmega328pを対象とした単純なコマンド言語用のパーサーを実装しました。このチップには32kROMと2kRAMしかありません。RAMは間違いなくより重要な制限です。特定のチップにまだ縛られていない場合は、できるだけ多くのRAMを搭載したチップを選択してください。これはあなたの人生をはるかに楽にします。
最初はフレックス/バイソンの使用を検討しました。私は2つの主な理由でこのオプションに反対することにしました:
Flex&Bisonを拒否した後、私は他のジェネレーターツールを探しに行きました。これが私が考えたいくつかです:
また、を見てみたいことがありますWikipediaの比較。
最終的に、レクサーとパーサーの両方を手動でコーディングすることになりました。
構文解析には、再帰下降パーサーを使用しました。Ira Baxterはすでにこのトピックをカバーするのに十分な仕事をしており、オンラインにはたくさんのチュートリアルがあると思います。
私のレクサーでは、すべての端末の正規表現を作成し、同等のステートマシンを図解し、状態goto
間をジャンプするためにを使用して1つの巨大な関数として実装しました。これは面倒でしたが、結果はうまくいきました。余談ですが、これgoto
はステートマシンを実装するための優れたツールです。すべての状態で、関連するコードのすぐ横に明確なラベルを付けることができ、関数呼び出しや状態変数のオーバーヘッドはなく、取得できる速度とほぼ同じです。Cには、静的ステートマシンを構築するための優れた構造が実際にはありません。
考えるべきこと:レクサーは実際にはパーサーの単なる専門分野です。最大の違いは、通常、字句解析には正規文法で十分ですが、ほとんどのプログラミング言語には(ほとんど)文脈自由文法があります。したがって、再帰下降パーサーとしてレクサーを実装したり、パーサージェネレーターを使用してレクサーを作成したりすることを妨げるものは何もありません。通常、より特殊なツールを使用するほど便利ではありません。
パーサーをコーディングする簡単な方法が必要な場合、またはスペースが限られている場合は、再帰下降パーサーを手動でコーディングする必要があります。これらは本質的にLL(1)パーサーです。これは、Basicのように「単純」な言語で特に効果的です。(私は70年代にこれらのいくつかをやりました!)。幸いなことに、これらにはライブラリコードが含まれていません。あなたが書いたものだけです。
すでに文法がある場合は、コーディングが非常に簡単です。まず、左再帰ルール(X = XYなど)を削除する必要があります。これは一般的に非常に簡単なので、演習として残しておきます。(リスト形成ルールのためにこれを行う必要はありません。以下の説明を参照してください)。
次に、次の形式のBNFルールがある場合:
X = A B C ;
ルール(X、A、B、C)の各項目に対して、「対応する構文構造を見た」というブール値を返すサブルーチンを作成します。Xの場合、コード:
subroutine X()
if ~(A()) return false;
if ~(B()) { error(); return false; }
if ~(C()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end X;
A、B、Cについても同様です。
トークンが端末の場合は、端末を構成する文字列の入力ストリームをチェックするコードを記述します。たとえば、数値の場合、入力ストリームに数字が含まれていることを確認し、入力ストリームカーソルを数字を超えて進めます。これは、バッファスキャンポインタを単に進めるか進めないことによってバッファから解析している場合(BASICの場合、一度に1行を取得する傾向があります)は特に簡単です。このコードは、本質的にパーサーのレクサー部分です。
BNFルールが再帰的である場合...心配しないでください。再帰呼び出しをコーディングするだけです。これは、次のような文法規則を処理します。
T = '(' T ')' ;
これは次のようにコーディングできます。
subroutine T()
if ~(left_paren()) return false;
if ~(T()) { error(); return false; }
if ~(right_paren()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end T;
代替のBNFルールがある場合:
P = Q | R ;
次に、別の選択肢を使用してPをコーディングします。
subroutine P()
if ~(Q())
{if ~(R()) return false;
return true;
}
return true;
end P;
リスト形成ルールに遭遇することがあります。これらは再帰的に残る傾向があり、このケースは簡単に処理できます。基本的な考え方は、再帰ではなく反復を使用することです。これにより、これを「明白な」方法で行う無限の再帰を回避できます。例:
L = A | L A ;
これは、反復を使用して次のようにコーディングできます。
subroutine L()
if ~(A()) then return false;
while (A()) do { /* loop */ }
return true;
end L;
この方法で、1日か2日で数百の文法規則をコーディングできます。記入する詳細はありますが、ここでの基本は十分すぎるはずです。
スペースが非常に限られている場合は、これらのアイデアを実装する仮想マシンを構築できます。それは私が70年代にやったことで、8K16ビットワードがあなたが得ることができたものでした。
これを手動でコーディングしたくない場合は、本質的に同じものを生成するメタコンパイラー(Meta II)を使用して自動化できます。これらは驚くべき技術的な楽しみであり、大きな文法であっても、これを行うことからすべての作業を実際に取り除きます。
2014年8月:
「パーサーでASTを構築する方法」という要望が多く寄せられています。この回答を本質的に詳しく説明しているこれの詳細については、他のSO回答https://stackoverflow.com/a/25106688/120163を参照してください。
2015年7月:
簡単な式の評価者を書きたいと思っている人はたくさんいます。上記の「ASTビルダー」リンクが示唆しているのと同じ種類のことを行うことでこれを行うことができます。ツリーノードを構築する代わりに、算術演算を行うだけです。ここにあります。この方法で行わ式の評価は。
GCCはさまざまなプラットフォームにクロスコンパイルできますが、コンパイラーを実行しているプラットフォームでflexとbisonを実行します。コンパイラがビルドするCコードを吐き出すだけです。それをテストして、結果の実行可能ファイルが実際にどれだけ大きいかを確認します。それらにはランタイムライブラリ(libfl.a
など)があり、ターゲットにクロスコンパイルする必要があることに注意してください。
Boost :: Spiritをお試しください。これはヘッダーのみのライブラリであり、C ++で完全に高速でクリーンなパーサーを作成できます。特別な文法ファイルの代わりに、C ++でオーバーロードされた演算子が使用されます。
車輪の再発明の代わりに、LUAを見てください:www.lua.org。これは、他のソフトウェアに組み込まれ、組み込みシステムなどの小規模システムで使用されることを意図したインタープリタ型言語です。組み込みの手続き型構文解析ツリー、制御ロジック、数学、変数のサポート—他の何千人もの人々がすでにデバッグして使用しているものを再発明する必要はありません。また、拡張可能です。つまり、独自のC関数を追加することで文法に追加できます。