はじめに
この連載ではJavaCC+JJTreeを使ってスクリプト言語を作成していきます。前回は、JJTreeのオプション指定と生成クラスの定義、構築方法について説明しましたが、肝心の文法定義は説明していませんでした。今回は、基本的な文法定義の構文を説明します。
環境
前回と同じく、JavaCC 4.0、J2SE 5.0、Antを用意しておいてください。また、前回のサンプルプログラムも必要です。
構文解析の用語
ここから文法定義の書き方を説明しますが、その前に構文解析で出てくる用語をまとめておきます。
生成文法
構文解析のプログラムを組むときには、与えられた文がどのような構文になっているかという構造を解析していくわけですが、構文解析の元となる理論では「その文がどのような規則から生成されているか」という考え方をします。そのため、使われている用語に感覚的でないと思えるものがいくつかあります。
この理論はもともと、人が話しをするときにどのように言葉を生成してるかというところから始まっていて、生成文法と呼ばれます。そのため、文法規則のことを生成規則と言います。
ある文を解析した結果はツリー構造になるので、このツリーのことを構文木と言いますが、このツリーは、その文を生成することができる生成規則のツリーでもあるので、導出木とも言います。
文法での最小の単位になる単語や記号などはトークンと言います。導出木として構文木をみたときに、トークンはそれ以上の規則を生成しないので終端記号と言い、生成規則は非終端記号と言います。また、非終端記号の中で、ツリーの頂点となるものを開始記号と言います。
BNF
文法定義で標準的に使われる表記方法にはBNFがあります。BNFは次のような記述方法です。
Command ::= JavaCC | JJTree | Name
通常の文章では、文法の定義には必ずと言っていいほどBNFが使われています。ただ、もともとのBNFでは正規表現の「*」や「+」に該当するような繰り返し表現がないため、EBNFなど繰り返し表現などを追加したものが使われることも多いようです。
JavaCCでの構文定義はEBNFをJava構文に近いもので置き換えた書き方になっています。例えば先ほどのBNFの例はJavaCCでは次のように記述します。
void Command() :
{}
{
JavaCC() | JJTree() | Name()
}
JavaCCに付属のJJDocを使うと、JavaCCの構文定義ファイル(.jjファイル)をBNF形式で出力することができます。
今回利用するファイル
今回は、前回のサンプルを少し変更して「hello xxx」のような形に対応できるようにしてみます。利用するファイルは次の通りです。
ファイル/フォルダ | 内容 |
/CodeZine | |
build.xml | 構築スクリプト(前回のものをそのまま利用) |
build.properties | 環境設定(前回のものをそのまま利用) |
/src | |
/codezine | |
/hello | |
Hello.java | 処理プログラム(変更) |
HelloParser.jjt | 文法定義ファイル(変更) |
BaseNode.java | ノードの基底クラス(新たに作成) |
/parser | |
/build | |
/classes |
サンプルプログラム
このファイルは、前回にはなかったもので、生成されるノードの基底クラスになります。
package codezine.hello; public class BaseNode { public String nodeValue; }
文法定義ファイルでは、オプションにノードの基底クラスの指定を記述して、TOKEN
にNAME
というトークンを登録、またName
という生成規則を定義しています。
//オプション定義 options{ STATIC=false; MULTI=true; VISITOR=true; NODE_EXTENDS="codezine.hello.BaseNode"; } //パーサークラスの定義 PARSER_BEGIN(HelloParser) package codezine.hello.parser; public class HelloParser{ } PARSER_END(HelloParser) //トークンの定義 SKIP: { " " | "\r" | "\t" | "\n" } TOKEN: { <HELLO: "hello"> | <JAVACC: "javacc"> | <JJTREE: "jjtree"> | <NAME: (["A"-"Z", "a"-"z"])+> } //文法の定義 ASTHello Hello(): {} { <HELLO> Command() { return jjtThis;} } void Command() #void : {} { JavaCC() | JJTree() | Name() } void JavaCC(): {} { <JAVACC> } void JJTree(): {} { <JJTREE> } void Name(): { Token t;} { t = <NAME> { jjtThis.nodeValue = t.image;} }
処理プログラムには、Name
ノードの処理をするメソッドを追加しています。
package codezine.hello; import codezine.hello.parser.*; public class Hello implements HelloParserVisitor{ public static void main(String[] args){ try { HelloParser parser = new HelloParser(System.in); Node node = parser.Hello(); Hello visitor = new Hello(); node.jjtAccept(visitor, null); } catch(TokenMgrError ex){ System.out.println("字句解析エラー:" + ex.getMessage()); } catch (ParseException ex) { System.out.println("構文解析エラー:" + ex.getMessage()); } } public Object visit(SimpleNode node, Object data) { //ここには来ない return null; } public Object visit(ASTHello node, Object data) { String word = node.jjtGetChild(0).jjtAccept(this, null).toString(); System.out.println("Hello " + word +"!!"); return null; } public Object visit(ASTJavaCC node, Object data) { return "ステキJavaCC"; } public Object visit(ASTJJTree node, Object data) { return "イカスJJTree"; } public Object visit(ASTName node, Object data) { String name = node.nodeValue; return "よくわからん" + name; } }
ビルドスクリプトと構築方法は前回と同じです。
実行すると、「hello」に続けて「JavaCC」「JJTree」以外を入力した際に、「こんにちは よくわからんxxx」と表示されるようになります。
C:\java\product\CodeZine\build\classes>java codezine.hello.Hello hello JavaScript こんにちは よくわからんJavaScript!!