私はC++ 11でシフト/リデュースパーサージェネレーターに取り組んでいますが、入力プロダクションとリダクションアクション関数のインターフェイスタイプを指定して、入力したい情報を保持する方法がわかりません。
文法を静的に指定したいのですが、C++ 型を使用します (別のビルド ツールではありません)。
各記号 (終端記号と非終端記号) に対して、ユーザーは文字列名とタイプを指定します。
次に、各プロダクションでヘッド シンボル名と 1 つ以上のボディ シンボル名を指定します。
プロダクションごとに、ヘッドの非終端型を返し、(対応するタイプの) プロダクション ボディ シンボルに対応するパラメーターを持つアクション関数がユーザー (ハード パーツ) によって提供されます。
主な問題は、これらのアクション関数のパラメーターの型と戻り値の型を対応するシンボルの型に静的にバインドすることです
たとえば、次のようになります。
非終端記号 があるとしますX
。A
B
C
それらの名前/タイプは次のとおりです。
"X" Foo
"A" string
"B" string
"C" int
そして、文法では、生産があるかもしれません:
X -> A B C
そして、そのプロダクションのためにユーザーによって提供されるアクション関数があります:
Foo f(string A, string B, int C)
そのプロダクションが関数 f よりも削減されている場合、プロダクション ボディ パラメータを使用して呼び出す必要があります。f によって返された値は、そのシンボルが上位のリダクションで使用されたときに格納されます。
したがって、パーサー ジェネレーターに文法を指定するには、次のようなものを提供する必要があります。
(私は以下が無効であることを知っています)
struct Symbol
{
string name;
type T;
}
struct Production
{
string head;
vector<string> body;
function<head.T(body[0].T, body[1].T, ..., body[n].T)> action;
}
struct Grammar
{
vector<Symbol> symbols;
vector<Production> productions;
}
そして、前の例を指定すると、次のようになります。
Grammar example =
{
// symbols
{
{ "X", Foo },
{ "A", string },
{ "B", string },
{ "C", int }
},
// productions
{
{
"X",
{ "A", "B", "C" },
[](string A, string B, int C) { ... return Foo(...); }
}
}
}
もちろん、これは機能しません。型パラメーターとランタイムパラメーターをそのように混在させることはできません。
1つの解決策は、いくつかの一般的なベースを持つことです:
struct SymbolBase
{
...
}
template<class SymbolType>
struct SymbolDerived<SymbolType> : SymbolBase
{
SymbolType value;
}
次に、次のタイプのすべてのアクション関数を作成します。
typedef function<SymbolBase(vector<SymbolBase>)> ActionFunction;
実行時に並べ替えます。しかし、これにより使用が難しくなり、すべてのキャストが遅くなります。コンパイル時に関数シグネチャをチェックして、メカニズムをユーザーから隠しておきたいと思います。
Symbol、Production、および Grammar の型を再構築して、法的な C++11 で伝えようとしている情報を保持するにはどうすればよいですか?
(はい、私は Boost Spirit とその友人を見てきました。これは素晴らしいフレームワークですが、再帰的降下であるため、1 回のパスで処理できる言語は LALR パーサーよりも少なく、バックトラッキングを使用するため、リダクション アクションが複数回呼び出されます。など)