JavaCC 構文の繰り返し表現

JavaCCを使える環境を作り、簡単な構文解析のサンプルプログラムを作成しました。
JavaCCを使用した構文解析プログラムの開発環境を整える

作成したプログラムは、ファイルに

print python


と記載して実行すると

印字: パイソン



と出力し、


print perl


と記載すると

印字: パール



と出力する簡単なプログラムです。


ファイルに

print python
print perl


と記載すると、

印字: パイソン


としか出力されません。

これを

印字: パイソン
印字: パール


と出力するようにしてみます。







jjtファイルの修正



前回と大きく変更した箇所の抜粋です。


ASTSample Sample():
{}
{
    <PRINT> Lang() { return jjtThis;}
}




このように

print [キーワード]


と解析していた箇所を

(print [キーワード] [改行])*


で解析するようにしました。

これで繰り返しの表現となります。
正規表現と似たような考え方で良いようです。



ASTStart Start():
{}
{
    Analyze() { return jjtThis; }
}


/* print [言語]の定義 */
void Analyze() #void:
{}
{
    (<PRINT> Lang() "\n")*
}





Sample.jjtファイル全体ではこのようになります。

  1. /* 生成オプション */
  2. options {
  3. STATIC = false;
  4. MULTI=true;
  5. VISITOR=true;
  6. }
  7. /*
  8.     出力するソースファイルのひな形定義開始
  9.     SampleParser」がクラス名になる
  10. */
  11. PARSER_BEGIN(SampleParser)
  12. /** Simple brace matcher. */
  13. package sample.parser;
  14. public class SampleParser {
  15. }
  16. PARSER_END(SampleParser)
  17. /* 無視する文字を定義 */
  18. SKIP:
  19. {
  20.     " " | "\r" | "\t"
  21. }
  22. /* 出現するトークンを定義 */
  23. TOKEN:
  24. {
  25.         <PRINT: "print">
  26.     | <PYTHON: "python">
  27.     | <PERL: "perl">
  28. }
  29. ASTStart Start():
  30. {}
  31. {
  32.     Analyze() { return jjtThis; }
  33. }
  34. /* print [言語]の定義 */
  35. void Analyze() #void:
  36. {}
  37. {
  38.     (<PRINT> Lang() "\n")*
  39. }
  40. /* [言語]は、Python()かPerl()を取る */
  41. void Lang() #void :
  42. {}
  43. {
  44.     Python()
  45.     | Perl()
  46. }
  47. /* Python()は、pythonという文字列 */
  48. void Python():
  49. {}
  50. {
  51.     <PYTHON>
  52. }
  53. /* Python()は、perlという文字列 */
  54. void Perl():
  55. {}
  56. {
  57.     <PERL>
  58. }





定義の変更に伴い、解析プログラムも若干修正しました。
ParserTest.javaは以下のようになりました。

  1. package sample;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import sample.parser.ASTPerl;
  5. import sample.parser.ASTPython;
  6. import sample.parser.ASTStart;
  7. import sample.parser.Node;
  8. import sample.parser.SampleParser;
  9. import sample.parser.SampleParserVisitor;
  10. import sample.parser.SimpleNode;
  11. public class ParserTest implements SampleParserVisitor {
  12.     public static void main(String[] args) throws Exception {
  13.         
  14.         FileInputStream is = new FileInputStream(new File("Token.txt"));
  15.         
  16.         SampleParser parser = new SampleParser(is, "UTF-8");
  17.         Node node = parser.Start();
  18.         
  19.         ParserTest visitor = new ParserTest();
  20.         node.jjtAccept(visitor, null);
  21.         
  22.     }
  23.     @Override
  24.     public Object visit(SimpleNode node, Object data) {
  25.         return null;
  26.     }
  27.     @Override
  28.     public Object visit(ASTPython node, Object data) {
  29.         return "パイソン";
  30.     }
  31.     @Override
  32.     public Object visit(ASTPerl node, Object data) {
  33.         return "パール";
  34.     }
  35.     @Override
  36.     public Object visit(ASTStart node, Object data) {
  37.         
  38.         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
  39.             String word = node.jjtGetChild(i).jjtAccept(this, null)
  40.                     .toString();
  41.             
  42.             System.out.println("印字: " + word);
  43.         }
  44.         
  45.         return null;
  46.     }
  47.     
  48. }




解析対象のToken.txtに

print python
print perl


と記載して実行すると

印字: パイソン
印字: パール


上記のように狙った通りの出力が得られます。








キーワードをフリーに



繰り返しの定義を処理できるようになりましたが、


print python
print perl
print ruby



このように、jjtで定義していない言語名が来ると解析エラーになってしまいます。


print [何かのキーワード]


という定義と解析をおこなってみます。


まず、解析した[何かのキーワード]を保持するために、
独自のNodeクラスを作成します。

今回は、MyNodeクラスを作成しました。

  1. package sample;
  2. public class MyNode {
  3.     private String value;
  4.     
  5.     public void setValue(String value) {
  6.         this.value = value;
  7.     }
  8.     
  9.     public String getValue() {
  10.         return value;
  11.     }
  12.     
  13. }



setValueというsetteと、getValueというgetterを持つ簡単なクラスです。



次に、jjtファイルの定義を以下のように、がらっと変更しました。

  1. /* 生成オプション */
  2. options {
  3. STATIC = false;
  4. MULTI=true;
  5. VISITOR=true;
  6. JAVA_UNICODE_ESCAPE = true;
  7. NODE_EXTENDS="sample.MyNode";
  8. }
  9. /*
  10.     出力するソースファイルのひな形定義開始
  11.     SampleParser」がクラス名になる
  12. */
  13. PARSER_BEGIN(SampleParser)
  14. /** Simple brace matcher. */
  15. package sample.parser;
  16. public class SampleParser {
  17. }
  18. PARSER_END(SampleParser)
  19. /* 無視する文字を定義 */
  20. SKIP:
  21. {
  22.     " " | "\r" | "\t"
  23. }
  24. /* 出現するトークンを定義 */
  25. TOKEN:
  26. {
  27.     <PRINT: "print">
  28.     | <LETTER:(["0"-"9","a"-"z", "A"-"Z"])+>
  29. }
  30. ASTStart Start():
  31. {}
  32. {
  33.     Analyze() { return jjtThis; }
  34. }
  35. /* print [言語]の定義 */
  36. void Analyze() #void:
  37. {}
  38. {
  39.     (<PRINT> Lang() "\n")*
  40. }
  41. /* #voidを付けないこと! */
  42. void Lang():
  43. {
  44. Token token;
  45. }
  46. {
  47.     token=<LETTER>
  48.     { jjtThis.setValue(token.image);}
  49. }




キモは2点。
1点目は、生成オプションの箇所で、

NODE_EXTENDS="sample.MyNode";


とし、今回作成したクラスをNodeに使用することを宣言します。


2点目は、Lang():の箇所です。

/* #voidを付けないこと! */
void Lang():
{
Token token;
}
{
    token=<LETTER>
    { jjtThis.setValue(token.image);}
}



tokenという変数を作成し、解析結果をtokenに代入します。
token.imageで解析結果の文字列が取得できます。

ここで、jjtThisは作成したMyNodeになりますので、
setValue(token.image)として、解析した文字列(想定では、pythonやperlという文字列)を
Nodeに退避します。


注意点として、Lang: #voidとすると、狙い通りにクラスが生成されず
Nodeオブジェクトに触れることができませんでした。
この戻り値指定はいまいち理解できていません。



最後に、解析ロジック部分のParserTest.javaです。
不要となった箇所がわかるよう、コメントアウトして残しています。

  1. package sample;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. //import sample.parser.ASTPerl;
  5. //import sample.parser.ASTPython;
  6. import sample.parser.ASTLang;
  7. import sample.parser.ASTStart;
  8. import sample.parser.Node;
  9. import sample.parser.SampleParser;
  10. import sample.parser.SampleParserVisitor;
  11. import sample.parser.SimpleNode;
  12. public class ParserTest implements SampleParserVisitor {
  13.     public static void main(String[] args) throws Exception {
  14.         
  15.         FileInputStream is = new FileInputStream(new File("Token.txt"));
  16.         
  17.         SampleParser parser = new SampleParser(is, "UTF-8");
  18.         Node node = parser.Start();
  19.         
  20.         ParserTest visitor = new ParserTest();
  21.         node.jjtAccept(visitor, null);
  22.         
  23.     }
  24.     @Override
  25.     public Object visit(SimpleNode node, Object data) {
  26.         return null;
  27.     }
  28. //    @Override
  29. //    public Object visit(ASTPython node, Object data) {
  30. //        return "パイソン";
  31. //    }
  32. //    @Override
  33. //    public Object visit(ASTPerl node, Object data) {
  34. //        return "パール";
  35. //    }
  36.     @Override
  37.     public Object visit(ASTStart node, Object data) {
  38.         
  39.         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
  40.             String word = node.jjtGetChild(i).jjtAccept(this, null)
  41.                     .toString();
  42.             
  43.             System.out.println("印字: " + word);
  44.         }
  45.         
  46.         return null;
  47.     }
  48.     @Override
  49.     public Object visit(ASTLang node, Object data) {
  50.         
  51.         return node.getValue();
  52.     }
  53.     
  54. }




Token.txtを

print python
print perl
print ruby
print php


このように変更して実行し見ると、狙い通りの出力が得られました。

印字: python
印字: perl
印字: ruby
印字: php










関連記事

コメント

非公開コメント

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
Symfoware まとめ

PR




検索フォーム

月別アーカイブ