私は同様のシナリオで Yacc (Bison に類似) で作業しました。
標準文法は、「構文による構文解析」と呼ばれることがあります。
このケースは、「セマンティクスによる構文解析」のようなものと呼ばれることもあります。
例:
...
// shift operator example
if ((x >> 2) == 0)
...
// consecutive template closing tag example
List<String, List<String>> MyList =
...
覚えておいてください、私たちの心はコンパイラのように機能します。人間の心はこれをコンパイルできますが、以前の文法ではできません。うーん。人間の心がこのコードをどのようにコンパイルするか見てみましょう。
ご存知のように、連続する ">" および ">" トークンの前の "x" は、式または左辺値を示します。心は、「式の後、2 つの連続する大なり記号は、1 つのシフト演算子トークンになるべきである」と考えます。
また、「文字列」トークンについては、「型識別子の後の 2 つの連続する大なり記号は、2 つの連続するテンプレート終了タグ トークンになる必要があります」。
このケースは、通常の演算子の優先順位、シフトまたは削減、または単なる文法では処理できないと思いますが、パーサー自体が提供するいくつかの機能を使用 (「ハッキング」) します。
あなたの例の文法規則に誤りはありません。「演算子」記号は、あなたが言及した2つのケースを混同しないようにします。注意すべき部分は、シフト演算子が使用されている文法と、連続したテンプレートの終了タグが使用されている部分です。
operator_expr_example:
lvalue "<<" lvalue |
lvalue ">>" lvalue |
lvalue "&&" lvalue |
;
template_params:
identifier |
template_declaration_example |
array_declaration |
other_type_declaration
;
template_declaration_example:
identifier "<" template_params ">"
;
乾杯。