8ビット組み込みシステムで使用できるflex / bisonの代替手段はありますか?


83

私は、avr-gccツールチェーンを使用したCのAVRマイクロコントローラーの演習として、言語のような単純なBASIC用の小さなインタープリターを書いています。ただし、レクサーとパーサーの作成に役立つオープンソースツールがあるかどうか疑問に思っています。

Linuxボックスで実行するようにこれを作成する場合は、flex / bisonを使用できます。8ビットプラットフォームに制限したので、すべて手作業で行う必要がありますか?


1
使用する予定の特定のチップはありますか?どのくらいのROM / RAMがありますか?
スティーブS

@mreのリンクを更新します。Embedded.comはURLを破棄しました。(embedded.com/design/prototyping-and-development/4024523/...
pgvoorhees

スタック言語(forth&Co)のみが2KB RAMでチャンスがあり、カーネルがフラッシュされているようです
Jacek Cz

回答:


59

ATmega328pを対象とした単純なコマンド言語用のパーサーを実装しました。このチップには32kROMと2kRAMしかありません。RAMは間違いなくより重要な制限です。特定のチップにまだ縛られていない場合は、できるだけ多くのRAMを搭載したチップを選択してください。これはあなたの人生をはるかに楽にします。

最初はフレックス/バイソンの使用を検討しました。私は2つの主な理由でこのオプションに反対することにしました:

  • デフォルトでは、Flex&Bisonは、avr-libcで使用できない、または同じように機能しないいくつかの標準ライブラリ関数(特にI / O用)に依存しています。サポートされている回避策があると確信していますが、これは考慮に入れる必要のある追加の作業です。
  • AVRにはハーバードアーキテクチャがあります。Cはこれを考慮して設計されていないため、定数変数でさえデフォルトでRAMにロードされますフラッシュEEPROMにデータを保存してアクセスするには、特別なマクロ/関数を使用する必要があります。Flex&Bisonは、比較的大きなルックアップテーブルをいくつか作成します。これらは、RAMをすぐに使い果たします。私が間違えない限り(これはかなり可能です)、特別なフラッシュとEEPROMインターフェイスを利用するために出力ソースを編集する必要があります。

Flex&Bisonを拒否した後、私は他のジェネレーターツールを探しに行きました。これが私が考えたいくつかです:

また、を見てみたいことがありますWikipediaの比較

最終的に、レクサーとパーサーの両方を手動でコーディングすることになりました。

構文解析には、再帰下降パーサーを使用しました。Ira Baxterはすでにこのトピックをカバーするのに十分な仕事をしており、オンラインにはたくさんのチュートリアルがあると思います。

私のレクサーでは、すべての端末の正規表現を作成し、同等のステートマシンを図解し、状態goto間をジャンプするためにを使用して1つの巨大な関数として実装しました。これは面倒でしたが、結果はうまくいきました。余談ですが、これgotoはステートマシンを実装するための優れたツールです。すべての状態で、関連するコードのすぐ横に明確なラベルを付けることができ、関数呼び出しや状態変数のオーバーヘッドはなく、取得できる速度とほぼ同じです。Cには、静的ステートマシンを構築するための優れた構造が実際にはありません。

考えるべきこと:レクサーは実際にはパーサーの単なる専門分野です。最大の違いは、通常、字句解析には正規文法で十分ですが、ほとんどのプログラミング言語には(ほとんど)文脈自由文法があります。したがって、再帰下降パーサーとしてレクサーを実装したり、パーサージェネレーターを使用してレクサーを作成したりすることを妨げるものは何もありません。通常、より特殊なツールを使用するほど便利ではありません。


221

パーサーをコーディングする簡単な方法が必要な場合、またはスペースが限られている場合は、再帰下降パーサーを手動でコーディングする必要があります。これらは本質的に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ビルダー」リンクが示唆しているのと同じ種類のことを行うことでこれを行うことができます。ツリーノードを構築する代わりに、算術演算を行うだけです。ここにあります。この方法で行わ式の評価は


2
ええ、単純な言語のために再帰下降パーサーを手で転がすのはそれほど難しいことではありません。可能な場合は末尾呼び出しを最適化することを忘れないでください。RAMが数キロバイトしかない場合、スタックスペースは非常に重要です。
スティーブS

2
すべて:はい、末尾呼び出しの最適化を行うことができます。解析されたコードのネストが本当に深くなることを期待しない限り、これは問題ではありません。BASICコード行の場合、10パラテンスをはるかに超える深さの式を見つけるのは非常に困難であり、いつでも深さ制限カウントを入れて起動できます。確かに、組み込みシステムはスタックスペースが少ない傾向があるため、少なくともここでの選択に注意してください。
Ira Baxter

2
@Mark:2012年かもしれませんが、私が参照している1965年のテクニカルペーパーは、当時と同じように今は良いものであり、特にあなたがそれを知らない場合はかなり良いものです。
Ira Baxter

2
@マーク、ああ、わかりました、ありがとう!不思議なことに日付が修正されたようです。ありがとう、タイムロード。
Ira Baxter

2
空の文字列を処理するにはどうすればよいですか?
ダンテ

11

Linuxでネイティブgccを使用してflex / bisonを使用してコードを生成し、それを組み込みターゲットのAVRgccとクロスコンパイルできます。


2

GCCはさまざまなプラットフォームにクロスコンパイルできますが、コンパイラーを実行しているプラ​​ットフォームでflexとbisonを実行します。コンパイラがビルドするCコードを吐き出すだけです。それをテストして、結果の実行可能ファイルが実際にどれだけ大きいかを確認します。それらにはランタイムライブラリ(libfl.aなど)があり、ターゲットにクロスコンパイルする必要があることに注意してください。


私はまだそれらのライブラリのサイズを調査する必要があり、それが私が最初に質問した理由です。特に小さなMCUをターゲットにしたものが欲しいです。
ヨハン

-1

Boost :: Spiritをお試しください。これはヘッダーのみのライブラリであり、C ++で完全に高速でクリーンなパーサーを作成できます。特別な文法ファイルの代わりに、C ++でオーバーロードされた演算子が使用されます。


あなたの答えの問題は、それが8ビットプラットフォームの制約を認識していないことです。ブーストとそのような小さなプラットフォームを同時にサポートするツールチェーンを手に入れるのは難しいでしょう。
waslap 2017

-5

車輪の再発明の代わりに、LUAを見てください:www.lua.org。これは、他のソフトウェアに組み込まれ、組み込みシステムなどの小規模システムで使用されることを意図したインタープリタ型言語です。組み込みの手続き型構文解析ツリー、制御ロジック、数学、変数のサポート—他の何千人もの人々がすでにデバッグして使用しているものを再発明する必要はありません。また、拡張可能です。つまり、独自のC関数を追加することで文法に追加できます。


2
ルアは小さいかもしれませんが、それでも大きすぎると確信しています。
icktoofay 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.