4

文字列を一連のパターンと照合するためのライブラリを作成しました。これで、レキシカル スキャナーを C プログラムに簡単に埋め込むことができるようになりました。

レキシカルスキャナーを作成するために利用できる確立されたツールがたくさんあることを私は知っています(頭に浮かぶ最初の2つに名前を付けると、lexとre2c)。レクサーの例は、一般的な問題の具体的なケースにすぎません。

考えられる解決策は 2 つあります。

  1. 埋め込まれた字句解析器を含むソース ファイルをプレーンな C ファイルに変換するプリプロセッサを作成し、場合によっては、コンパイルで使用される他のファイルのセットに変換します。
  2. より読みやすい形式でレクサーを表す一連の C マクロを作成します。

私はすでに両方を行っていますが、質問は次のとおりです。

  • 読みやすさ。レクサーのロジックは明確で理解しやすいものにする必要があります
  • 保守性。バグを見つけて修正するのは悪夢ではありません。
  • ビルド プロセスの干渉。プリプロセッサは、ビルド プロセスで追加の手順を必要とし、プリプロセッサはパス内に存在する必要があります。

言い換えれば、2 つのアプローチのいずれかを使用するソフトウェアを保守または作成する必要がある場合、失望が少ないのはどちらでしょうか?

例として、次の問題のレクサーを次に示します。

  • すべての数値を合計します (1.3E-4.2 のような指数を含む 10 進数形式にすることができます)
  • スキップ文字列 (二重引用符と一重引用符)
  • スキップ リスト (LISP リストと同様: (3 4 (0 1)() 3) )
  • 単語 end (大文字と小文字は区別されません) に遭遇するか、バッファの最後で停止します

2つのスタイルで。

/**** SCANNER STYLE 1 (preprocessor) ****/
#include "pmx.h"

t = buffer

while (*t) {
  switch pmx(t) { /* the preprocessor will handle this */
    case "&q" :         /* skip strings */
      break; 

    case "&f<?=eE>&F" : /* sum numbers */ 
      sum += atof(pmx(Start,0));
      break;

    case "&b()":        /* skip lists */
      break;

    case "&iend" :      /* stop processing */ 
      t = "";
      break;

    case "<.>":         /* skip a char and proceed */
      break;
  }
}

/**** SCANNER STYLE 2 (macros) ****/
#include "pmx.h"
/* There can be up to 128 tokens per scanner with id x80 to xFF */
#define TOK_STRING x81
#define TOK_NUMBER x82
#define TOK_LIST   x83
#define TOK_END    x84
#define TOK_CHAR   x85

pmxScanner(   /* pmxScanner() is a pretty complex macro */
   buffer
 ,
   pmxTokSet("&q"         , TOK_STRING)
   pmxTokSet("&f<?=eE>&F" , TOK_NUMBER)
   pmxTokSet("&b()"       , TOK_LIST)
   pmxTokSet("&iend"      , TOK_END)
   pmxTokSet("<.>"        , TOK_CHAR)
 ,
   pmxTokCase(TOK_STRING) :   /* skip strings */
     continue; 

   pmxTokCase(TOK_NUMBER) :   /* sum numbers */ 
     sum += atof(pmxTokStart(0));
     continue;

   pmxTokCase(TOK_LIST):      /* skip lists */
     continue;

   pmxTokCase(TOK_END) :      /* stop processing */ 
     break; 

   pmxTokCase(TOK_CHAR) :     /* skip a char and proceed */
     continue;
);

現在の実装に興味がある方は、コードはhttp://sites.google.com/site/clibutlにあります。

4

2 に答える 2

6

プリプロセッサは、より堅牢で汎用的なソリューションを提供します。一方、マクロはすばやく作成でき、優れた概念実証を提供し、サンプル キーワード/トークン スペースが小さい場合は簡単です。スケールアップ/新しい機能を含めることは、ポイントの後にマクロを使用すると面倒になる場合があります。マクロを作成して開始し、それらをプリプロセッサ コマンドに変換します。

また、可能であれば、独自のプリプロセッサを作成するのではなく、汎用のプリプロセッサを使用できるようにしてください。

[...]別の依存関係を処理する必要があります(たとえば、Windowsのm4)。

はい。しかし、あなたが書いた解決策もそうです:) -そしてあなたはそれを維持しなければなりません。あなたが名前を付けたプログラムのほとんどは、利用可能な Windows ポートを持っています (例えば、m4 for windowsを参照してください)。このようなソリューションを使用する利点は、多くの時間を節約できることです。もちろん、欠点は、奇妙なバグが発生した場合に、おそらくソース コードに慣れる必要があることです (ただし、これらを維持している人々は非常に役に立ち、すべての助けが得られることを確認します)。

繰り返しますが、はい、私は自分で開発するよりもパッケージ化されたソリューションを好みます。

于 2009-03-28T07:36:29.533 に答える
3

カスタム プリプロセッサは、パーサー/インタープリター ジェネレーターの典型的なアプローチです。マクロの可能性は非常に限られており、拡張段階で潜在的な問題が発生し、デバッグに多大な労力がかかるためです。

古典的な Yacc/Lex Unix プログラムなどの実績のあるツールを使用することをお勧めします。または、C を「拡張」したい場合は、C++ と Boost::spirit (テンプレートを広範囲に使用するパーサー ジェネレーター) を使用することをお勧めします。

于 2009-03-28T07:35:14.843 に答える