Avr-gccツールチェーンを使用したCのAVRマイクロコントローラーでの演習として、言語のような単純なBASICの小さなインタープリターを作成しています。ただし、レクサーとパーサーを作成するのに役立つオープンソースツールがあるかどうか疑問に思っています。
Linuxボックスで実行するためにこれを記述する場合、flex/bisonを使用できます。 8ビットのプラットフォームに自分自身を制限したので、すべて手作業でやらなければならないのですか?
ATmega328p を対象とした単純なコマンド言語のパーサーを実装しました。このチップには32k ROMと2k RAMのみがあります。RAMは間違いなくより重要な制限です。特定のチップにまだ結び付けられていない場合、できるだけ多くのRAMを選択してください。これにより、あなたの人生はずっと楽になります。
最初はflex/bisonの使用を検討しました。私は2つの主な理由でこのオプションに反対しました:
Flex&Bisonを拒否した後、他のジェネレーターツールを探しました。ここに私が検討したいくつかがあります:
Wikipediaの比較 もご覧ください。
最終的に、レクサーとパーサーの両方を手作業でコーディングしました。
解析には、再帰降下パーサーを使用しました。 Ira Baxter はすでにこのトピックをカバーするのに十分な仕事をしており、オンラインでたくさんのチュートリアルがあります。
字句解析器では、すべての端末の正規表現を作成し、同等の状態マシンを図式化し、goto
を使用して状態間をジャンプする1つの巨大な関数として実装しました。これは退屈でしたが、結果はうまくいきました。余談ですが、goto
はステートマシンを実装するための優れたツールです。すべてのステートは、関連するコードのすぐ横に明確なラベルを付けることができ、関数呼び出しやステート変数のオーバーヘッドはありません。あなたが得ることができるように。 Cには、静的状態マシンを構築するためのより優れた構造がありません。
考慮すべき点:レクサーは、実際には単なるパーサーの特殊化です。最大の違いは、通常の文法は語彙分析に通常十分であるのに対して、ほとんどのプログラミング言語には(ほとんど)コンテキストのない文法があることです。したがって、レクサーを再帰下降パーサーとして実装したり、パーサージェネレーターを使用してレクサーを記述したりすることを妨げるものは何もありません。通常、より専門的なツールを使用するほど便利ではありません。
パーサーをコーディングする簡単な方法が必要な場合、またはスペースが限られている場合は、再帰降下パーサーを手動でコーディングする必要があります。これらは基本的に [〜#〜] ll [〜#〜] (1)パーサーです。これは、Basicと同じくらい「単純」な言語で特に効果的です。 (私はこれらのいくつかを70年代にやりました!)。幸いなことに、これらにはライブラリコードが含まれていません。まさにあなたが書いたもの。
既に文法を持っている場合、それらは非常に簡単にコーディングできます。最初に、左の再帰ルールを削除する必要があります(例:X = X Y)。これは一般的に非常に簡単なので、演習として残しておきます。 (リスト形成ルールのためにこれを行う必要はありません。以下の議論を参照してください)。
次に、次の形式の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年代に私がやったことで、8K 16ビットワードが得られました。
これを手作業でコーディングしたくない場合は、metacompiler( Meta II )で自動化できます。本質的に同じこと。これらは驚くほどの技術的な楽しみであり、大きな文法であっても、これを行うことですべての作業を実際に行います。
2014年8月:
「ASTをパーサーで構築する方法」」について多くのリクエストを受け取りました。これについての詳細は、本質的にこの答えを詳しく説明しています。他のSO =回答 https://stackoverflow.com/a/25106688/12016
2015年7月:
単純な式エバリュエーターを作成したい人がたくさんいます。これを行うには、上記の「ASTビルダー」リンクが提案するものと同じ種類のことを行います。ツリーノードを構築する代わりに、算術演算を行うだけです。 この方法で行われた式エバリュエーター です。
Linuxのflex/bisonとそのネイティブgccを使用してコードを生成し、そのコードを組み込みターゲットのAVR gccとクロスコンパイルできます。
GCCはさまざまなプラットフォームにクロスコンパイルできますが、コンパイラを実行しているプラットフォームでflexとbisonを実行します。コンパイラがビルドするCコードを吐き出すだけです。テストして、結果の実行可能ファイルが実際にどれだけ大きいかを確認します。ランタイムライブラリ(libfl.a
など)をターゲットにクロスコンパイルする必要があります。