2

私は、あいまいなトークンをJavaCCでエレガントに(または何らかの形で)処理する方法を理解する上で、すでに多くの問題を抱えていました。この例を見てみましょう:

XML処理命令をパースしたい。

形式は次のとおりです: :"<?" <target> <data> "?>"XML名targetです。data?>

それでは、JavaCC でこれを定義しましょう:
(この場合は語彙状態を使用しDEFAULTPROC_INST)

TOKEN : <#NAME : (very-long-definition-from-xml-1.1-goes-here) >
TOKEN : <WSS : (" " | "\t")+ >   // WSS = whitespaces
<DEFAULT> TOKEN : {<PI_START : "<?" > : PROC_INST}
<PROC_INST> TOKEN : {<PI_TARGET : <NAME> >}
<PROC_INST> TOKEN : {<PI_DATA : ~[] >}   // accept everything
<PROC_INST> TOKEN : {<PI_END : "?>" > : DEFAULT}

処理命令を認識する部分:

void PROC_INSTR() : {} {
(
    <PI_START>
    (t=<PI_TARGET>){System.out.println("target: " + t.image);}
    <WSS>
    (t=<PI_DATA>){System.out.println("data: " + t.image);}
    <PI_END>
) {}
}

でテストしましょう<?mytarget here-goes-some-data?>

ターゲットが認識されました: "target: mytarget"。しかし今、お気に入りのJavaCC 解析エラーが発生します。

!!  procinstparser.ParseException: Encountered "" at line 1, column 15.
!!  Was expecting one of:
!!      

何も遭遇しませんでしたか?何も期待していませんでしたか?または何?ありがとう、JavaCC!

JavaCCのキーワードを使用できることはわかっていますMOREが、これにより処理命令全体が1つのトークンとして提供されるため、自分でさらに解析/トークン化する必要がありました。なぜそれをすべきなのか?解析しないパーサーを書いていますか?

問題は(私が推測する)です:したがって<PI_DATA>、「すべて」を認識します。私の定義は間違っています。JavaCC に「」以外のすべてを?>処理命令データとして認識するように指示する必要があります。

しかし、どうすればそれを行うことができますか?

注: を使用して単一の文字のみを除外できます。またはなどの文字列を除外することはできません。JavaCC のもう 1 つの優れたアンチ機能です。~["a"|"b"|"c"]~["abc"]~["?>"]

ありがとうございました。

4

2 に答える 2

4

トークナイザーについて一言

トークナイザー (*TokenManager) は、できるだけ多くの入力文字に一致します。PI_DATA は "~[]" (1 文字) であるため、より長い一致が見つからない場合は、任意の 1 つの入力文字と一致します。PI_ENDは「?>」(2文字)なので、PI_DATAではなく必ずマッチします。あなたの文法のこの部分は正しいです。

思わぬ容疑者

問題は実際には NAME から発生している可能性があります。あなたはそのトークンの実際の定義を書いていないので、私はそれについて推測することしかできません。NAME の定義が貪欲すぎると、PROC_INST 状態の入力文字と一致しすぎて、PI_DATA または PI_END に遭遇しない可能性があります。

空白を含む "(...)+" や、EOF まですべてを食い尽くす邪悪な "(~[])*" に注意してください。

その他の容疑者

私が見る潜在的な問題は、PI_DATA が一致することを期待しているにもかかわらず、PI_TARGET がおそらく数回一致することです。繰り返しますが、NAME の定義がないため、推測することしかできません。

明確にする必要があるもう 1 つのポイントは、WSS トークンを定義しますが、PROC_INST 状態では使用しません。PI_DATA の一部にする必要がありますか? そうでない場合は、スキップすることをお勧めします。

トークナイザーを悪用しないでください

トークナイザーをあなたに従わせることができないことがわかった場合は、代わりにトリッキーな部分をパーサーに移動することをお勧めします。あなたの場合、(上記のように) PI_TARGET と PI_DATA を区別するのはおそらく難しいでしょう。

パーサーはPI ターゲットの後に PI データを予期できますが、トークナイザーはトークンから次のトークンへの期待を持てません (またはほとんど期待できません)。

パーサーのもう 1 つの利点は、次のトークンを確認してそれに応じて反応する Java コードを作成できることです。これは最後の手段と考える必要がありますが、複数のトークンを既知のトークンに連結するなどの操作を行う必要がある場合に役立ちます。これは、ここで探しているものかもしれません (ターミネータ トークンとして PI_END を使用)。

最後に裏技

文法を少し単純化するためのトリックを次に示します。

  1. PI_START をスキップしますが、それでも状態を PROC_INST に変更します
  2. PROC_INST で、PI_DATA を MORE として定義します (名前を PI_DATA_CHAR に変更するか、まったく名前を付けないでください)。
  3. PROC_INST で、トークン イメージから最後の 2 文字を削除し、PI_DATA を発行して状態を DEFAULT に変更します。
  4. パーサー プロダクションでは、PI_DATA のトークン イメージがすぐに使用できるように、処理命令を単に として定義します。

トークナイザー アクションでトークン イメージを操作する方法の詳細は、JavaCC の (sparse...) ドキュメントに記載されています。StringBuffer の長さを設定するのと同じくらい簡単です。

于 2011-02-08T15:34:36.770 に答える
0

あなたの文法の問題の 1 つは、WSS が既定の状態でのみ適用されることです。のように書き換えます。

<DEFAULT, PROC_INST> TOKEN : {< WSS: (" " | "\t")+ > \}

エラー メッセージは、WSS を期待していましたが、" " が見つかったというものです。

文字列全体を除外する方法については、FAQ で概説されている方法がいくつかあります。

于 2013-05-04T14:58:27.880 に答える