| インタフェース | 説明 |
|---|---|
| TokenHook_if |
トークン取得タイミングでの呼び出しインターフェース
|
| クラス | 説明 |
|---|---|
| COMMAND |
symphonieコマンド(構文チェックなど).
|
| SymphonieApp |
構文解析アプレット.
|
| SyntaxConverter |
構文木表現への変換機.
|
| SyntaxDom |
構文解析を行い、XML-Document(DOM)を得る
構文定義と、その構文にそった文章を与え、文章を構文解析します。
|
| SyntaxElement |
解析結果としてユーザに通知されるデータ
構文定義法、入力解析法などの総合的な説明を
パッケージ otsu.symphonieの説明
で行ってありますので、まずご覧ください。
|
| SyntaxReader |
構文解析機。
|
| Token |
トークンデータを表す単純構造体
|
| TokenReader |
トークン解析機.
|
| 例外 | 説明 |
|---|---|
| SyntaxErrorException |
構文エラーを現す
|
1. 概要/使用例
Symphonieは次の解析機構を持ちます。
(1) 構文を解析し、構文要素を順次取得する
SyntaxReadersr =new SyntaxReader(); //(1)SyntaxReader作成 sr.readSyntaxFile("syntaxDef.sn","utf-8"); //(2)構文定義設定 sr.parseFile("text.txt","utf-8");//(3)入力設定SyntaxElementeml=new SyntaxElement(); while( sr.read(elm,sr.END_AT_EOF)){ //(4)読む _pr.println(hiU.indent(elm.nest) +elm.toXMLString(option_)); // 読み取った要素を表示 }
(2) 構文木をXML-DOMで得る機構
SyntaxDomsyntax=new SyntaxDom(); syntax.readSyntaxFile("syntaxDef.sn","utf-8"); Document doc= syntax.parseFile("text.txt","utf-8");
SyntaxConvertersyntax=new SyntaxConverter(); syntax.readSyntaxFile("syntaxDef.sn","utf-8"); String xmlStr = syntax.parseFile("text.txt","utf-8",syntax.AS_XML); String jsonStr= syntax.parseFile("text.txt","utf-8",syntax.AS_JSON);
java -jar symphonie.jar syntax-check ^ -syntax syntaxDef.sn -source text.txt -encoding utf-8
(5) 構文解析アプレット
<!-- in HTML -->
<applet id="app" archive="symphonie.jar"
code="otsu.symphonie.SymphonieApp" ...>
<script type="text/javascript>
var syntax="... 構文定義 ...";
var text="... 解析したい文 ...";
app.readSyntaxString(syntax);
var xml=app.parseString(text,"te");
</script>
構文はBNFに繰り返し定義、省略可能定義、結合度指定、優先選択指定 などを追加した SymphonicNotationで定義します。
以下の説明で用いる構文定義
以下の説明では次の構文定義を用います。
メモリ機能を持つ単純な数式演算機です。正負符号を許す四則演算に関数、括弧、後置演算を追加
してあります。
==== 構文定義 ====
@ ::= ?"[ \t\n\f\r ]+" @ ::= ?"//.*$" @VALUE ::= ?"[0-9]+\.[0-9]+" | ?"[0-9]+" @NAME ::= ?"[a-zA-Z_\u4e00-\u9fa5\u3041-\u3094\u30a1-\u30fc]" +"[a-zA-Z_0-9\u4e00-\u9fa5\u3041-\u3094\u30a1-\u30fc]*" actions ::= { assignment } assignment ::= NAME "=" expression [";"] expression ::= term | minus | plus | fract | mul | div | add | sub term ::= VALUE | NAME | function | "(" expression ")" function ::= NAME "(" [{ expression &","}] ")" minus/p:10/ ::= "-" expression plus/p:10/ ::= "+" expression fract/p:9/ ::= expression "!" // 後置演算、階乗 n! mul/p:5/ ::= expression "*" expression div/p:5/ ::= expression "/" expression add/p:2/ ::= expression "+" expression sub/p:2/ ::= expression "-" expression
@で始まる行はトークン定義です。@名前 ::= 定義の形をしています。名前
の無いトークンは読み飛ばしパターンです。ここでは空白と//から改行までが
読み飛ばされる定義をしています。
パターンにはunicodeの範囲の指定も可能です。ここではNAMEにカナ漢字を許す指定をしてあります。
名前 ::= 並びは構文定義です。{}は繰り返し、[]は省略可能、|は選択項目を現します。
名前に付加した/p:数値/は演算子結合の優先度です。数値が高い程優先度が高くなります。 ここでは二項演算の+,-より*,/の方が優先度が高く、さらに前置の+,-が高く指定して あります。-a*b-c*dは((-a)*b)-(c*d))と解釈されます。なお、優先度が同じ場合は 左から適用されます。a+b+cは((a+b)+c)。
構文定義と解析対象文と構文木
構文定義と解析対象文と構文木の関連を構文の1部を抜き出して示します。
mul/p:5/ ::= expression "*" expression add/p:2/ ::= expression "+" expression
"*"を使うmul構文は2つのexpressionを参照します。"+"を使うadd構文も 2つのexpressionを参照します。expressionはmul,add,termなどを参照 します。
次の文を与えると
x = a + b * c
次のように解析されます。addは2つのexpressionを持ちmulも2つのexpressionを持っています。 expressionの中にはadd,mulあるいはtermなどがあります。
<actions>
| <assignment>
| | <NAME text="x"/>
| | <expression>
| | | <add>
| | | | <expression>
| | | | ! <term>
| | | | ! | <NAME text="a"/>
| | | | ! </term>
| | | | </expression>
| | | | <expression>
| | | | ! <mul>
| | | | ! | <expression>
| | | | ! | | <term>
| | | | ! | | | <NAME text="b"/>
| | | | ! | | </term>
| | | | ! | </expression>
| | | | ! | <expression>
| | | | ! | | <term>
| | | | ! | | | <NAME text="c"/>
| | | | ! | | </term>
| | | | ! | </expression>
| | | | ! </mul>
| | | | </expression>
| | | </add>
| | </expression>
| </assignment>
</actions>
構文要素を順次取得する場合、この木の行の順に情報が取得されます。
要素の結合が (a+(b*c)) となっており ((a+b)*c)
でないのは/p:n/で与えている結合度がmulの方が大きいためです。
指定要素(階層)を省略するomit指定があります。
#omit指定は構文定義の前に
//#omit 省略したい構文名/トークン名 ....
または各構文定義名の後ろに/omit/を付け
term/omit/ ::= ...構文定義...
の形で指定します。複数の構文名/トークン名を空白を挟んで並べることができます。
termとexpressionをomitすると次のような構文木となります。
<actions>
| <assignment>
| | <NAME text="x"/>
| | <add>
| | | <NAME text="a"/>
| | | <mul>
| | | | <NAME text="b"/>
| | | | <NAME text="c"/>
| | | </mul>
| | </add>
| </assignment>
</actions>
omit指定の使用には注意が必要ですが、うまく使えば解析を単純化 することができます。
omit指定のある/なしで通知要素が変わりますので順次解析のプログラムは変わります。
指定の
無い場合のプログラムを
1.1 SyntaxReaderによる順次解析に、
指定のある場合のプログラムを
1.6 omitを指定した構文解析の例
に載せています。
以下の説明で用いる入力データ
以下の説明では次の入力を解析しています。
a = 1+2-3+4*5-6.0/2.0 // 17 二項演算の結合則
b = -7.5-+-a/+4.0-0.75 // -4 左単項演算子の結合則
// (-7.5) - (+(-a))/(+4.0) - 0.75
c = (a-14)!- -b!+9 // -9 右単項演算子の結合則(!は階乗を現す)
// (a-14)! - ((-b)!) + 9
d = sqrt(a-1.0)+sqrt(-c)! // 10 関数呼び出し
少し複雑な例となっていますが、符号(-/+)の付加や後置単項演算の 結合則も実現されていることが分かります。「-+-」と言った記述を実際 に行うことは考えにくいことですが、対応できるということは重要 なことです。
少し長くなりますが、入力データの構文木を載せます。 これは解析し、構文木を表示させるコマンドで出した ものに、入力の対応行を付けくわえたものです。
[an error occurred while processing this directive]<actions> | <assignment> <!-- (1) a = 1+2-3+4*5-6.0/2.0 // 17 二項演算の結合則 --> | | <NAME text="a"/> | | <expression> | | | <sub> | | | | <expression> | | | | ! <add> | | | | ! | <expression> | | | | ! | | <sub> | | | | ! | | | <expression> | | | | ! | | | | <add> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <term> | | | | ! | | | | ! | | <VALUE text="1"/> | | | | ! | | | | ! | </term> | | | | ! | | | | ! </expression> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <term> | | | | ! | | | | ! | | <VALUE text="2"/> | | | | ! | | | | ! | </term> | | | | ! | | | | ! </expression> | | | | ! | | | | </add> | | | | ! | | | </expression> | | | | ! | | | <expression> | | | | ! | | | | <term> | | | | ! | | | | ! <VALUE text="3"/> | | | | ! | | | | </term> | | | | ! | | | </expression> | | | | ! | | </sub> | | | | ! | </expression> | | | | ! | <expression> | | | | ! | | <mul> | | | | ! | | | <expression> | | | | ! | | | | <term> | | | | ! | | | | ! <VALUE text="4"/> | | | | ! | | | | </term> | | | | ! | | | </expression> | | | | ! | | | <expression> | | | | ! | | | | <term> | | | | ! | | | | ! <VALUE text="5"/> | | | | ! | | | | </term> | | | | ! | | | </expression> | | | | ! | | </mul> | | | | ! | </expression> | | | | ! </add> | | | | </expression> | | | | <expression> | | | | ! <div> | | | | ! | <expression> | | | | ! | | <term> | | | | ! | | | <VALUE text="6.0"/> | | | | ! | | </term> | | | | ! | </expression> | | | | ! | <expression> | | | | ! | | <term> | | | | ! | | | <VALUE text="2.0"/> | | | | ! | | </term> | | | | ! | </expression> | | | | ! </div> | | | | </expression> | | | </sub> | | </expression> | </assignment> | <assignment> <!-- (2) b = -7.5-+-a/+4.0-0.75 // -4 左単項演算子の結合則 --> | | <NAME text="b"/> | | <expression> | | | <sub> | | | | <expression> | | | | ! <sub> | | | | ! | <expression> | | | | ! | | <minus> | | | | ! | | | <expression> | | | | ! | | | | <term> | | | | ! | | | | ! <VALUE text="7.5"/> | | | | ! | | | | </term> | | | | ! | | | </expression> | | | | ! | | </minus> | | | | ! | </expression> | | | | ! | <expression> | | | | ! | | <div> | | | | ! | | | <expression> | | | | ! | | | | <plus> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <minus> | | | | ! | | | | ! | | <expression> | | | | ! | | | | ! | | | <term> | | | | ! | | | | ! | | | | <NAME text="a"/> | | | | ! | | | | ! | | | </term> | | | | ! | | | | ! | | </expression> | | | | ! | | | | ! | </minus> | | | | ! | | | | ! </expression> | | | | ! | | | | </plus> | | | | ! | | | </expression> | | | | ! | | | <expression> | | | | ! | | | | <plus> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <term> | | | | ! | | | | ! | | <VALUE text="4.0"/> | | | | ! | | | | ! | </term> | | | | ! | | | | ! </expression> | | | | ! | | | | </plus> | | | | ! | | | </expression> | | | | ! | | </div> | | | | ! | </expression> | | | | ! </sub> | | | | </expression> | | | | <expression> | | | | ! <term> | | | | ! | <VALUE text="0.75"/> | | | | ! </term> | | | | </expression> | | | </sub> | | </expression> | </assignment> | <assignment> <!-- (4) c = (a-14)!- -b!+9 // -9 右単項演算子の結合則(!は階乗を現す) --> | | <NAME text="c"/> | | <expression> | | | <add> | | | | <expression> | | | | ! <sub> | | | | ! | <expression> | | | | ! | | <fract> | | | | ! | | | <expression> | | | | ! | | | | <term> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <sub> | | | | ! | | | | ! | | <expression> | | | | ! | | | | ! | | | <term> | | | | ! | | | | ! | | | | <NAME text="a"/> | | | | ! | | | | ! | | | </term> | | | | ! | | | | ! | | </expression> | | | | ! | | | | ! | | <expression> | | | | ! | | | | ! | | | <term> | | | | ! | | | | ! | | | | <VALUE text="14"/> | | | | ! | | | | ! | | | </term> | | | | ! | | | | ! | | </expression> | | | | ! | | | | ! | </sub> | | | | ! | | | | ! </expression> | | | | ! | | | | </term> | | | | ! | | | </expression> | | | | ! | | </fract> | | | | ! | </expression> | | | | ! | <expression> | | | | ! | | <fract> | | | | ! | | | <expression> | | | | ! | | | | <minus> | | | | ! | | | | ! <expression> | | | | ! | | | | ! | <term> | | | | ! | | | | ! | | <NAME text="b"/> | | | | ! | | | | ! | </term> | | | | ! | | | | ! </expression> | | | | ! | | | | </minus> | | | | ! | | | </expression> | | | | ! | | </fract> | | | | ! | </expression> | | | | ! </sub> | | | | </expression> | | | | <expression> | | | | ! <term> | | | | ! | <VALUE text="9"/> | | | | ! </term> | | | | </expression> | | | </add> | | </expression> | </assignment> | <assignment> <!-- (6) d = sqrt(a-1.0)+sqrt(-c)! // 10 関数呼び出し --> | | <NAME text="d"/> | | <expression> | | | <add> | | | | <expression> | | | | ! <term> | | | | ! | <function< | | | | ! | | <NAME text="sqrt"/> | | | | ! | | <expression> | | | | ! | | | <sub> | | | | ! | | | | <expression> | | | | ! | | | | ! <term> | | | | ! | | | | ! | <NAME text="a"/> | | | | ! | | | | ! </term> | | | | ! | | | | </expression> | | | | ! | | | | <expression> | | | | ! | | | | ! <term> | | | | ! | | | | ! | <VALUE text="1.0"/> | | | | ! | | | | ! </term> | | | | ! | | | | </expression> | | | | ! | | | </sub> | | | | ! | | </expression> | | | | ! | </function> | | | | ! </term> | | | | </expression> | | | | <expression> | | | | ! <fract> | | | | ! | <expression> | | | | ! | | <term> | | | | ! | | | <function< | | | | ! | | | | <NAME text="sqrt"/> | | | | ! | | | | <expression> | | | | ! | | | | ! <minus> | | | | ! | | | | ! | <expression> | | | | ! | | | | ! | | <term> | | | | ! | | | | ! | | | <NAME text="c"/> | | | | ! | | | | ! | | </term> | | | | ! | | | | ! | </expression> | | | | ! | | | | ! </minus> | | | | ! | | | | </expression> | | | | ! | | | </function> | | | | ! | | </term> | | | | ! | </expression> | | | | ! </fract> | | | | </expression> | | | </add> | | </expression> | </assignment> </actions>
omit指定で項目毎に通知を省略し、構文木に入れないことが出来ます。
この例ではexpressionとtermは省略しても文の内容は問題なく
知ることができます。(構文解析には必要です)
omit指定を行った場合の構文木を載せます。
[an error occurred while processing this directive]
<actions> | <assignment> <!-- (1) a = 1+2-3+4*5-6.0/2.0 // 17 二項演算の結合則 --> | | <NAME text="a"/> | | <sub> | | | <add> | | | | <sub> | | | | ! <add> | | | | ! | <VALUE text="1"/> | | | | ! | <VALUE text="2"/> | | | | ! </add> | | | | ! <VALUE text="3"/> | | | | </sub> | | | | <mul> | | | | ! <VALUE text="4"/> | | | | ! <VALUE text="5"/> | | | | </mul> | | | </add> | | | <div> | | | | <VALUE text="6.0"/> | | | | <VALUE text="2.0"/> | | | </div> | | </sub> | </assignment> | <assignment> <!-- (2) b = -7.5-+-a/+4.0-0.75 // -4 左単項演算子の結合則 --> | | <NAME text="b"/> | | <sub> | | | <sub> | | | | <minus> | | | | ! <VALUE text="7.5"/> | | | | </minus> | | | | <div> | | | | ! <plus> | | | | ! | <minus> | | | | ! | | <NAME text="a"/> | | | | ! | </minus> | | | | ! </plus> | | | | ! <plus> | | | | ! | <VALUE text="4.0"/> | | | | ! </plus> | | | | </div> | | | </sub> | | | <VALUE text="0.75"/> | | </sub> | </assignment> | <assignment> <!-- (4) c = (a-14)!- -b!+9 // -9 右単項演算子の結合則(!は階乗を現す) --> | | <NAME text="c"/> | | <add> | | | <sub> | | | | <fract> | | | | ! <sub> | | | | ! | <NAME text="a"/> | | | | ! | <VALUE text="14"/> | | | | ! </sub> | | | | </fract> | | | | <fract> | | | | ! <minus> | | | | ! | <NAME text="b"/> | | | | ! </minus> | | | | </fract> | | | </sub> | | | <VALUE text="9"/> | | </add> | </assignment> | <assignment> <!-- (6) d = sqrt(a-1.0)+sqrt(-c)! // 10 関数呼び出し --> | | <NAME text="d"/> | | <add> | | | <function< | | | | <NAME text="sqrt"/> | | | | <sub> | | | | ! <NAME text="a"/> | | | | ! <VALUE text="1.0"/> | | | | </sub> | | | </function> | | | <fract> | | | | <function< | | | | ! <NAME text="sqrt"/> | | | | ! <minus> | | | | ! | <NAME text="c"/> | | | | ! </minus> | | | | </function> | | | </fract> | | </add> | </assignment> </actions>[an error occurred while processing this directive]
omit指定のある/なしで通知要素が変わりますので順次解析のプログラムは変わります。
指定の
無い場合のプログラムを
1.1 SyntaxReaderによる順次解析に、
指定のある場合のプログラムを
1.6 omitを指定した構文解析の例
に載せています。
1.1. SyntaxReaderによる順次解析
SyntaxReaderに
構文要素を順に読み取るAPIが用意されています。
プログラムは
SyntaxReader
インスタンスに
.readSyntaxFile("構文定義ファイル名","文字コード")
.parseFile("解析対象ファイル名","文字コード")
形をとります。文字コード指定は省略可能で省略時は"utf-8" となります。
SyntaxReader sr= new SyntaxReader();
sr.readSyntaxFile("syntaxDef.sn","shift-jis"); // 構文定義を読む
sr.parseFile("sampleIn.txt","shift-jis"); // 解析対象ファイルを指定
//... 構文要素を取得する
構文記述を文字列で設定するSyntaxReader.readSyntaxString(String)
や文字列の配列で設定するSyntaxReader.readSyntaxString(String[])
も用意されています。解析対象を文字列、または文字列の配列で与えることもできます。
構文要素はSyntaxElementで現されます。
構文要素には次の種類があります。
構文要素を順次取得するread関数が用意されています。
| read関数 | 説明 |
read()
|
要素を特定せず取得する。 EOFでfalseリターンとするかEOFExceptionとするかオプションで 指定することもできる。 |
readBegin(要素名)
|
指定要素のBEGINを読み取る。 要素が指定要素のBEGINでない場合はExceptionが発生する。 |
readToken(トークン名)
|
指定名のTOKENを読み取る。 要素が指定TOKENでない場合はExceptionが発生する。 |
readEnd(要素名)
|
指定要素のENDを読み取る。 要素が指定要素のBEGINでない場合はExceptionが発生する。 |
tryReadBegin(要素名)
|
指定要素のBEGINを読み取る。 要素が指定要素のBEGINでない場合はfalseが返る。その場合 同一要素を再びreadすることができる。 |
この他read関数には受け取るSyntaxElementを引数に指定 するなどの色々なバリエーションが用意されています。
取得したSyntaxElementを一旦元に戻す
push(要素)
関数も用意されています。push()した要素は再びread()することが
できます。
import otsu.symphonie.SyntaxReader;
import otsu.symphonie.SyntaxElement;
import java.util.HashMap;
class test01 {
public static void main(String[] args_){
try{
HashMap<String,Double> variables= new HashMap<String,Double>();
SyntaxReader sr = new SyntaxReader();
sr.readSyntaxFile("sampleSyntax.sn","utf-8");
sr.parseFile("sampleIn.txt","utf-8");
sr.readBegin("actions"); // 最外殻
while( sr.tryReadBegin("assignment") ){ // assignmentの繰り返し
assignment(sr,variables); // assignment処理
sr.readEnd("assignment");
}
}
catch(Exception e){
e.printStackTrace(hiU.err);
System.exit(1);
}
}
static void assignment(SyntaxReader sr_
,HashMap<String,Double> variables_)throws Exception{
String name = sr_.readToken("NAME").text; // 変数名
double value = expression(sr_,variables_); // 値
variables_.put(name,value); // 変数をセットする
System.out.printf("%s = %f\n",name,value);
}
static double expression(SyntaxReader sr_
,HashMap<String,Double> variables_)throws Exception{
double ret= 0;
sr_.readBegin("expression");
SyntaxElement elm= sr_.read(); // 要素取得
if( elm.name.equals("term") ){
sr_.push(elm);
ret= term(sr_,variables_);
}
else{
if( elm.name.equals("minus") ){
ret= -expression(sr_,variables_);
}
else if( elm.name.equals("plus") ){
ret= expression(sr_,variables_);
}
else if( elm.name.equals("fract") ){
ret= 1;
for(int n=(int)expression(sr_,variables_);n>1;--n) ret*=n;
}
else if( elm.name.equals("mul") ){
ret= expression(sr_,variables_) * expression(sr_,variables_);
}
else if( elm.name.equals("div") ){
ret = expression(sr_,variables_);
double d= expression(sr_,variables_);
if( d!=0 ) ret= ret/d;
else hiU.err.println("0 divide");
}
else if( elm.name.equals("add") ){
ret= expression(sr_,variables_) + expression(sr_,variables_);
}
else if( elm.name.equals("sub") ){
ret= expression(sr_,variables_) - expression(sr_,variables_);
}
sr_.readEnd(); // minus~subの終了
}
sr_.readEnd("expression");
return ret;
}
static double term(SyntaxReader sr_
,HashMap<String,Double> variables_)throws Exception{
double ret= 0;
sr_.readBegin("term");
SyntaxElement elm= sr_.read(); // 要素取得
if( elm.isToken("VALUE") ) { // 値リテラル
ret= Double.parseDouble(elm.text);
}
else if( elm.isToken("NAME") ) { // 変数参照
Double var= variables_.get(elm.text);
if( var!=null ) ret= var;
else hiU.err.printf("%s not defined%n",elm.text);
}
else if( elm.isBegin("function") ){
String funcName= sr_.readToken("NAME").text;
if( funcName.equals("sqrt") ){
ret= Math.sqrt(expression(sr_,variables_));
}
else {
hiU.err.printf("function %s not defined%n",funcName);
}
sr_.readEnd("function");
}
else if( elm.mustBegin("expression") ){ // 括弧はexpressionのみ通知
sr_.push(elm);
ret= expression(sr_,variables_);
}
sr_.readEnd("term");
return ret;
}
}
プログラム構造は構文定義をなぞるように作られています。
assignment-expression-termという呼び出し階層、再帰呼び出し
構造をしています。
各階層はBEGIN要素から始まりEND要素で終わります。この情報を
readBegin(XXX)とreadEnd(XXX)などで受け取っ
ています。トークン情報は
readToken(XXX)で受け取ります。
単純にread()で受けっとった要素に対して、isBegin(XXX)や
isToken(XXX)で内容を確認することも可能です。
演算子毎の結合の強さなどはライブラリで全て解決しています
のでプログラムで意識することはありません。
==== 実行例 ====
次の結果が得られます。
a = 17.000000 b = -4.000000 c = -9.000000 d = 10.000000
1.2. 構文解析木をDOM(Document)で得る
SyntaxDomを用いれば、構文解析木を
DOM(Document)で得る事ができます。
プログラムは
SyntaxDom
インスタンスに
.readSyntaxFile("構文定義ファイル名","文字コード")
.parseFile("解析対象ファイル名","文字コード")
形をとります。
入力文は全文解析されたXML/DOMによる構文木として得られます。
[an error occurred while processing this directive] ==== プログラム ====
import otsu.symphonie.SyntaxDom;
import org.w3c.dom.*;
import java.util.HashMap;
class test01 {
public static void main(String[] args_){
try{
HashMap<String,Double> variables= new HashMap<String,Double>();
SyntaxDom syntax=new SyntaxDom();
syntax.readSyntaxFile("sampleSyntax.sn","utf-8");
Document doc = syntax.parseFile("sampleIn.txt","utf-8");
NodeList assignments = doc.getElementsByTagName("assignment");
for(int idx=0;idx<assignments.getLength();++idx){
assignment((Element)assignments.item(idx),variables);
}
}
catch(Exception e){
e.printStackTrace(hiU.err);
System.exit(1);
}
}
static void assignment(Element ass_
,HashMap<String,Double> variables_)throws Exception{
NodeList elms = ass_.getChildNodes();
String name = ((Element)elms.item(0)).getAttribute("text");
double value = expression((Element)elms.item(1),variables_);
variables_.put(name,value); // 変数をセットする
System.out.printf("%s = %f\n",name,value);
}
static double expression(Element expr_
,HashMap<String,Double> variables_)throws Exception{
double ret = 0;
Element elm = (Element)expr_.getFirstChild();
String tag = elm.getTagName();
if( tag.equals("term") ){
ret= term(elm,variables_);
}
else{
Element e1= (Element)elm.getFirstChild();
if( tag.equals("minus") ){
ret= -expression(e1,variables_);
}
else if( tag.equals("plus") ){
ret= expression(e1,variables_);
}
else if( tag.equals("fract") ){
ret= 1;
for(int n=(int)expression(e1,variables_);n>1;--n) ret*=n;
}
else {
Element e2= (Element)elm.getLastChild();
if( tag.equals("mul") ){
ret= expression(e1,variables_) * expression(e2,variables_);
}
else if( tag.equals("div") ){
ret = expression(e1,variables_);
double d= expression(e2,variables_);
if( d!=0 ) ret= ret/d;
else hiU.err.println("0 divide");
}
else if( tag.equals("add") ){
ret= expression(e1,variables_) + expression(e2,variables_);
}
else if( tag.equals("sub") ){
ret= expression(e1,variables_) - expression(e2,variables_);
}
}
}
return ret;
}
static double term(Element trm_
,HashMap<String,Double> variables_)throws Exception{
double ret= 0;
Element elm = (Element)trm_.getFirstChild();
String tag = elm.getTagName();
if( tag.equals("VALUE") ) { // 値リテラル
ret= Double.parseDouble(elm.getAttribute("text"));
}
else if( tag.equals("NAME") ) { // 変数参照
Double var= variables_.get(elm.getAttribute("text"));
if( var!=null ) ret= var;
else hiU.err.printf("%s not defined%n",elm.getAttribute("text"));
}
else if( tag.equals("function") ){
String funcName = ((Element)elm.getFirstChild()).getAttribute("text");
if( funcName.equals("sqrt") ){
ret= Math.sqrt(expression((Element)elm.getLastChild(),variables_));
}
else {
hiU.err.printf("function %s not defined%n",funcName);
}
}
else if( tag.equals("expression") ){ // 括弧はexpressionのみ通知
ret= expression(elm,variables_);
}
return ret;
}
}
プログラムは構文木をXML-DOM(org.w3c.dom.Document)で得て、それを改めて なめています。DOMを取得した後はsymphonieライブラリの呼び出しは行って いません。
==== 実行例 ====
次の結果が得られます。
a = 17.000000 b = -4.000000 c = -9.000000 d = 10.000000
1.3. 構文解析木をXML/JSON文字列で得る
SyntaxConverterを用いれば
構文解析を行い、構文木をXML文字列またはJSON文字列で得ることができます。
例えば次のようなプログラムでXML表示とJSON表示を得ることができます。
import otsu.symphonie.SyntaxConverter;
class test01 {
public static void main(String[] args_){
try{
SyntaxConverter conv = new SyntaxConverter();
conv.readSyntaxFile("sampleSyntax.sn","utf-8");
String xml = conv.parseFile("sampleIn.txt","utf-8"
,conv.AS_XML); // XML形式で得る
System.out.println("xml-text="+xml);
String json = conv.parseFile("sampleIn.txt","utf-8"
,conv.AS_JSON);// JSON形式で得る
System.out.println("json-text="+json);
}
catch(Exception e){
e.printStackTrace(hiU.err);
System.exit(1);
}
}
}
まず、短い文
x = -a + b * c
の解析結果出力を載せます。
[an error occurred while processing this directive]
xml-text=<actions>
<assignment>
<NAME text="x"/>
<expression>
<add>
<expression>
<minus>
<expression>
<term>
<NAME text="a"/>
</term>
</expression>
</minus>
</expression>
<expression>
<mul>
<expression>
<term>
<NAME text="b"/>
</term>
</expression>
<expression>
<term>
<NAME text="c"/>
</term>
</expression>
</mul>
</expression>
</add>
</expression>
</assignment>
</actions>
json-text=[
{
"NAME" : "x",
"expression" : {
"add" : {
"expression" : {
"minus" : {
"expression" : {
"term" : {
"NAME" : "a"
}
}
}
},
"expression" : {
"mul" : {
"expression" : {
"term" : {
"NAME" : "b"
}
},
"expression" : {
"term" : {
"NAME" : "c"
}
}
}
}
}
}
}
]
同じ入力文で omit指定でtermとexpressionを省略した例を載せます。
[an error occurred while processing this directive]
xml-text=<actions>
<assignment>
<NAME text="x"/>
<add>
<minus>
<NAME text="a"/>
</minus>
<mul>
<NAME text="b"/>
<NAME text="c"/>
</mul>
</add>
</assignment>
</actions>
json-text=[
{
"NAME" : "x",
"add" : {
"minus" : {
"NAME" : "a"
},
"mul" : {
"NAME" : "b",
"NAME" : "c"
}
}
}
]
長い例を載せます。
[an error occurred while processing this directive]次の入力では
a = 1+2-3+4*5-6.0/2.0 // 17 二項演算の結合則
b = -7.5-+-a/+4.0-0.75 // -4 左単項演算子の結合則
// (-7.5) - (+(-a))/(+4.0) - 0.75
c = (a-14)!- -b!+9 // -9 右単項演算子の結合則(!は階乗を現す)
// (a-14)! - ((-b)!) + 9
d = sqrt(a-1.0)+sqrt(-c)! // 10 関数呼び出し
次の様な出力が得られます。
xml-text=<actions>
<assignment>
<NAME text="a"/>
<expression>
<sub>
<expression>
<add>
<expression>
<sub>
<expression>
<add>
<expression>
<term>
<VALUE text="1"/>
</term>
</expression>
<expression>
<term>
<VALUE text="2"/>
</term>
</expression>
</add>
</expression>
<expression>
<term>
<VALUE text="3"/>
</term>
</expression>
</sub>
</expression>
<expression>
<mul>
<expression>
<term>
<VALUE text="4"/>
</term>
</expression>
<expression>
<term>
<VALUE text="5"/>
</term>
</expression>
</mul>
</expression>
</add>
</expression>
<expression>
<div>
<expression>
<term>
<VALUE text="6.0"/>
</term>
</expression>
<expression>
<term>
<VALUE text="2.0"/>
</term>
</expression>
</div>
</expression>
</sub>
</expression>
</assignment>
<assignment>
<NAME text="b"/>
<expression>
<sub>
<expression>
<sub>
<expression>
<minus>
<expression>
<term>
<VALUE text="7.5"/>
</term>
</expression>
</minus>
</expression>
<expression>
<div>
<expression>
<plus>
<expression>
<minus>
<expression>
<term>
<NAME text="a"/>
</term>
</expression>
</minus>
</expression>
</plus>
</expression>
<expression>
<plus>
<expression>
<term>
<VALUE text="4.0"/>
</term>
</expression>
</plus>
</expression>
</div>
</expression>
</sub>
</expression>
<expression>
<term>
<VALUE text="0.75"/>
</term>
</expression>
</sub>
</expression>
</assignment>
<assignment>
<NAME text="c"/>
<expression>
<add>
<expression>
<sub>
<expression>
<fract>
<expression>
<term>
<expression>
<sub>
<expression>
<term>
<NAME text="a"/>
</term>
</expression>
<expression>
<term>
<VALUE text="14"/>
</term>
</expression>
</sub>
</expression>
</term>
</expression>
</fract>
</expression>
<expression>
<fract>
<expression>
<minus>
<expression>
<term>
<NAME text="b"/>
</term>
</expression>
</minus>
</expression>
</fract>
</expression>
</sub>
</expression>
<expression>
<term>
<VALUE text="9"/>
</term>
</expression>
</add>
</expression>
</assignment>
<assignment>
<NAME text="d"/>
<expression>
<add>
<expression>
<term>
<function<
<NAME text="sqrt"/>
<expression>
<sub>
<expression>
<term>
<NAME text="a"/>
</term>
</expression>
<expression>
<term>
<VALUE text="1.0"/>
</term>
</expression>
</sub>
</expression>
</function>
</term>
</expression>
<expression>
<fract>
<expression>
<term>
<function<
<NAME text="sqrt"/>
<expression>
<minus>
<expression>
<term>
<NAME text="c"/>
</term>
</expression>
</minus>
</expression>
</function>
</term>
</expression>
</fract>
</expression>
</add>
</expression>
</assignment>
</actions>
json-text=[
{
"NAME" : "a",
"expression" : {
"sub" : {
"expression" : {
"add" : {
"expression" : {
"sub" : {
"expression" : {
"add" : {
"expression" : {
"term" : {
"VALUE" : "1"
}
},
"expression" : {
"term" : {
"VALUE" : "2"
}
}
}
},
"expression" : {
"term" : {
"VALUE" : "3"
}
}
}
},
"expression" : {
"mul" : {
"expression" : {
"term" : {
"VALUE" : "4"
}
},
"expression" : {
"term" : {
"VALUE" : "5"
}
}
}
}
}
},
"expression" : {
"div" : {
"expression" : {
"term" : {
"VALUE" : "6.0"
}
},
"expression" : {
"term" : {
"VALUE" : "2.0"
}
}
}
}
}
}
},
{
"NAME" : "b",
"expression" : {
"sub" : {
"expression" : {
"sub" : {
"expression" : {
"minus" : {
"expression" : {
"term" : {
"VALUE" : "7.5"
}
}
}
},
"expression" : {
"div" : {
"expression" : {
"plus" : {
"expression" : {
"minus" : {
"expression" : {
"term" : {
"NAME" : "a"
}
}
}
}
}
},
"expression" : {
"plus" : {
"expression" : {
"term" : {
"VALUE" : "4.0"
}
}
}
}
}
}
}
},
"expression" : {
"term" : {
"VALUE" : "0.75"
}
}
}
}
},
{
"NAME" : "c",
"expression" : {
"add" : {
"expression" : {
"sub" : {
"expression" : {
"fract" : {
"expression" : {
"term" : {
"expression" : {
"sub" : {
"expression" : {
"term" : {
"NAME" : "a"
}
},
"expression" : {
"term" : {
"VALUE" : "14"
}
}
}
}
}
}
}
},
"expression" : {
"fract" : {
"expression" : {
"minus" : {
"expression" : {
"term" : {
"NAME" : "b"
}
}
}
}
}
}
}
},
"expression" : {
"term" : {
"VALUE" : "9"
}
}
}
}
},
{
"NAME" : "d",
"expression" : {
"add" : {
"expression" : {
"term" : {
"function" : {
"NAME" : "sqrt",
"expression" : {
"sub" : {
"expression" : {
"term" : {
"NAME" : "a"
}
},
"expression" : {
"term" : {
"VALUE" : "1.0"
}
}
}
}
}
}
},
"expression" : {
"fract" : {
"expression" : {
"term" : {
"function" : {
"NAME" : "sqrt",
"expression" : {
"minus" : {
"expression" : {
"term" : {
"NAME" : "c"
}
}
}
}
}
}
}
}
}
}
}
}
]
1.4. 構文解析を行い構文木を表示する
利用者プログラムを作成しなくても、構文定義と 対象文を与えると、構文木をXMLの形で得ることが できます。
コマンド形式:
java -jar symphonie.jar syntax-check
-syntax 構文定義ファイル -source 対象文ファイル -xml
[-out 出力ファイル] [-encoding 文字コード]
-outを省略すると標準出力に出ます。文字コードはutf-8をデフォルト としています。utf-8でない場合は-encodingで指定してください。
構文木の例は 入力データの[構文木を表示する] 出力の長い例の[出力例を表示する] を参照してください。
1.5. 構文解析アプレット
構文解析アプレットSymphonieAppを用いれば
WEBクライアント上で構文解析を行い、構文木をXML文字列またはJSON
で得ることができます。
次の例は、簡素な式記述を解析し、MathML-XML形式に対応する構文木を 得、式表示をしているものです。 [an error occurred while processing this directive]
次のHTML記述
<html><head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head><body>
<applet id="app" archive="../../../otsu/lib/symphonie.jar"
code="otsu.symphonie.SymphonieApp"
width="1" height="1" ></applet>
<script type="text/javascript" src="syntax.js"></script>
<p>
二次方程式
<span id=ex1>a@x^2 + b@x + c =0</span>
の解は、
<span id=ex2>x = { -b±√{b^2 - 4@a@c } } % {2@a}</span>
となります。
</p>
<script type="text/javascript">
mathConv('ex1');
mathConv('ex2');
</script>
</body></html>
(青部分が通常の文記述です。ここのex1とex2を置き換えています)
と構文定義、html要素置き換え用のjavascript
var syntax=[
'//#omit expression term-list term op-term math-list'
,'@ ::= ?"[ \\t\\n\\f\\r\\u3000]+"'
,'@ ::= ?"//.*$"'
,'@mn ::= ?"[0-9]+\\.[0-9]+" | ?"[0-9]+" '
,'@mi ::= ?"[a-zA-Z][0-9a-zA-Z]*"'
,'@mo ::= "+" | "-" | "/" | "*" | "=" '
,' | "⁢" / "@" | "±"/ "±"/"+-"'
,' | "×"/"×" | "÷"/"÷" | "&[A-Za-z]+;"'
,'@mo.pSta ::= "("'
,'@mo.pEnd ::= ")"'
,'math-list ::= {math &";"}'
,'math ::= expression'
,'expression::= {op-term}'
,'op-term ::= mo | term'
,'term ::= mn | mi | mrow | msqrt | mfrac | msup'
,'mrow ::= "{" expression "}"'
,'msqrt ::= "√" term | "&sqrt;" term | "#SQRT" term'
,'mfrac ::= term "%" term'
,'msup ::= term "^" term'
,'mfenced ::= "[" mrow_0s "]"'
,'mrow_0s ::= { mrow.0 &","}'
,'mrow.0 ::= expression'
,'mrow.paren ::= mo.pSta expression mo.pEnd'];
function $E(id){ return document.getElementById(id); }
var symphonie;
function initSymp(appId){
symphonie= document.getElementById(appId);
symphonie.readSyntaxLines(syntax);
}
function mathConv(p){
var txt=$E(p).firstChild.nodeValue;
var math= symphonie.parseString(txt,"te");
$E(p).innerHTML=math;
}
initSymp('app');
で<p>の中の記述
"a@x + b@y + c =0"
と
"x={-b±√{b^2-4@a@c}} % {2@a}"
を解析し、
次のXML
<math> <mi>a</mi> <mo>⁢</mo> <mi>x</mi> <mo>+</mo> <mi>b</mi> <mo>⁢</mo> <mi>y</mi> <mo>+</mo> <mi>c</mi> <mo>=</mo> <mn>0</mn> </math>
と
<math>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mo>-</mo>
<mi>b</mi>
<mo>±</mo>
<msqrt>
<mrow>
<msup>
<mi>b</mi>
<mn>2</mn>
</msup>
<mo>-</mo>
<mn>4</mn>
<mo>⁢</mo>
<mi>a</mi>
<mo>⁢</mo>
<mi>c</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mo>⁢</mo>
<mi>a</mi>
</mrow>
</mfrac>
</math>
を得、<span>のinnerHTMLを置き換えることにより、次の画面を得ます。
⁢は見えない掛け算です。(4ac=4×a×c)
上の表示はMathMLを有効にしたfirefox3.6.8で 実際に表示したものをキャプチャたものです。
1.6. omitを指定した構文解析の例
omit指定を行うと、通知する構文階層を省略することができます。
例えば、次の指定でexpressionとterm階層を省略した通知が行われます。
順次アクセスAPIでも構文木取得でも省略されます。
//#omit expression term
@ ::= ?"[ \t\n\f\r ]+"
@ ::= ?"//.*$"
@VALUE ::= ?"[0-9]+\.[0-9]+" | ?"[0-9]+"
@NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*"
actions ::= { assignment }
assignment ::= NAME "=" expression [";"]
expression ::= term | minus | plus | fract | mul | div | add | sub
term ::= VALUE | NAME | function | "(" expression ")"
function ::= NAME "(" [{ expression &","}] ")"
minus/p:10/ ::= "-" expression
plus/p:10/ ::= "+" expression
fract/p:9/ ::= expression "!"
mul/p:5/ ::= expression "*" expression
div/p:5/ ::= expression "/" expression
add/p:2/ ::= expression "+" expression
sub/p:2/ ::= expression "-" expression
順次アクセスでも省略されるため、それを意識した呼び出しとする 必要があります。
[an error occurred while processing this directive] ==== プログラム ====
import otsu.symphonie.SyntaxReader;
import otsu.symphonie.SyntaxElement;
import java.util.HashMap;
class test01 {
public static void main(String[] args_){
try{
HashMap<String,Double> variables= new HashMap<String,Double>();
SyntaxReader sr = new SyntaxReader();
sr.readSyntaxFile("sampleSyntax.sn","utf-8");
sr.parseFile("sampleIn.txt","utf-8");
sr.readBegin("actions"); // 最外殻
while( sr.tryReadBegin("assignment") ){ // assignmentの繰り返し
assignment(sr,variables); // assignment処理
sr.readEnd("assignment");
}
}
catch(Exception e){
e.printStackTrace(hiU.err);
System.exit(1);
}
}
static void assignment(SyntaxReader sr_
,HashMap<String,Double> variables_)throws Exception{
String name = sr_.readToken("NAME").text; // 変数名
double value = expression(sr_,variables_); // 値
variables_.put(name,value); // 変数をセットする
System.out.printf("%s = %f\n",name,value);
}
static double expression(SyntaxReader sr_
,HashMap<String,Double> variables_)throws Exception{
double ret= 0;
SyntaxElement elm= sr_.read(); // 要素取得
if( elm.isToken("VALUE") ) { // 値リテラル
ret= Double.parseDouble(elm.text);
}
else if( elm.isToken("NAME") ) { // 変数参照
Double var= variables_.get(elm.text);
if( var!=null ) ret= var;
else hiU.err.printf("%s not defined%n",elm.text);
}
else{
if( elm.name.equals("minus") ){
ret= -expression(sr_,variables_);
}
else if( elm.name.equals("plus") ){
ret= expression(sr_,variables_);
}
else if( elm.name.equals("fract") ){
ret= 1;
for(int n=(int)expression(sr_,variables_);n>1;--n) ret*=n;
}
else if( elm.name.equals("mul") ){
ret= expression(sr_,variables_) * expression(sr_,variables_);
}
else if( elm.name.equals("div") ){
ret = expression(sr_,variables_);
double d= expression(sr_,variables_);
if( d!=0 ) ret= ret/d;
else hiU.err.println("0 divide");
}
else if( elm.name.equals("add") ){
ret= expression(sr_,variables_) + expression(sr_,variables_);
}
else if( elm.name.equals("sub") ){
ret= expression(sr_,variables_) - expression(sr_,variables_);
}
else if( elm.isBegin("function") ){
String funcName= sr_.readToken("NAME").text;
if( funcName.equals("sqrt") ){
ret= Math.sqrt(expression(sr_,variables_));
}
else {
hiU.err.printf("function %s not defined%n",funcName);
}
}
sr_.readEnd(); // minus~subの終了
}
return ret;
}
}
==== 実行例 ====
次の結果が得られます。
a = 17.000000 b = -4.000000 c = -9.000000 d = 10.000000
omit指定は十分に注意をする必要はありますが、使い方に よって、プログラムを単純化できます。
2. symphonie構文定義(SymphonicNotation)
構文はBNFに繰り返しを導入したようなSymponie構文定義形式で 与えます。この表記法をSN形式(SymphonicNotation:Symphonie式記述)と呼びます。
定義法自身をこの定義法で表すと次のようになります。
// 単純 トークン、構文解析 試験
// トークン定義
@pragma-def ::= ?"^//#[a-zA-Z].*$"
@ ::= ?"[ \t\n\r\f\u3000]+" // 空白
@ ::= "/*" -> com // C形式ブロックコメントの開始
@com: ::= "*/" -> 0 // C形式ブロックコメントの終了
@com: ::= ?"." // ブロックコメント内の文字
@ ::= ?"//.*$" // C形式行末までのコメント
@name ::= ?"[a-zA-Z][0-9a-zA-Z_\.\-]*"
@propery ::= ?"/[a-zA-Z][^/\n]*/" // プロパティ記述
@part-string ::= '"' ~ '"' | "'" ~ "'" // 文字列
@zero ::= "0"
@nospace ::= "!"
@Q ::= "?"
// 構文定義
definitions ::= { definition }
definition ::= pragma-definition
| token-definition
| syntax-definition
// プラグマ定義
pragma-definition ::= pragma-def
// トークン定義構文
token-definition ::= "@" [on-condition] [name] "::="
{ token-pattern-list &"|"} [to-context] [";"]
on-condition ::= name ":"
token-pattern-list ::= { token-pattern &"/"}
token-pattern ::= string
| string "~" string ["(" { conversion &","} ")" ]
string ::= [Q] { part-string &"+"}
simple-string ::= { part-string &"+"}
conversion ::= string "->" simple-string
to-context ::= "->" ( name | zero )
// 構文定義構文
syntax-definition ::= name[property] "::=" syntax-pattern [";"]
syntax-pattern ::= { [sub-name] syntax-element-list &"|"}
syntax-element-list ::= { syntax-element &[nospace]}
sub-name ::= name ":"
syntax-element ::= refer
| mark
| par:"(" syntax-pattern ")"
| opt:"[" syntax-pattern "]"
| rep:[zero] "{" syntax-pattern [dlmt] "}"
refer ::= name
mark ::= string
dlmt ::= "&" mark-or-refer
| opt:"&" "[" mark-or-refer "]"
mark-or-refer ::= refer | string
// 補足:
// プロパティ記述は次の構文規則を持ちます
// @name ::= ?"[a-zA-Z][0-9a-zA-Z_\-]*"
// @value ::= ?"[0-9a-zA-Z][0-9a-zA-Z_\-]*"
// properties ::= { property &";" }
// property ::= name [ ":" value ]
2.1. 基本項目
定義は
で構成されます。
プラグマ定義は曖昧性の除去指定など補助指定を 行うもので、記述の先頭に起きます。
トークン定義は@で始まります。固定文字列(引用符で囲む あるいは正規表現でトークンの文字列を定義します。正規表現は?の後引用符 で囲みます。
名前は英文字あるいはアンダースコアで始まり、後ろに英文字数字、
アンダースコア、ハイフン、ドットが続きます。
ドットは特殊な取り扱いを受けます。解析はドットも含め行われます
がAPIを介した構文通知で通知される構文名/トークン名はドットより
前の部分のみとなります。
バージョン1.31以降では<と>で囲む文字列も名前扱いとなります。
C/C++形式のコメントが書けます。
全角空白も空白とみなされます。
2.2. トークン定義
冒頭に並ぶ@を含む定義がトークン定義です。
@文脈名:トークン名 ::= パターン
定義の区切りとして最後に";"に付けても構いません。
2.2.1. 文脈制御
トークン読み込みは文脈制御ができるようになっています。
@の後ろに文脈名+":"、トークン名を書きます。 文脈名:は省略できます。
@文脈名:トークン名 @option:file-name option文脈、トークン名:file-name @part-string 文脈条件無し、トークン名:part-string @ 文脈条件無し、トークン名無し(読み飛ばし指定)
トークン名を書かない場合は、読み飛ばされます。
次の定義は空白を読み飛ばす定義です。なお\u3000は全角の空白です。
@ ::= ?"[ \t\n\r\f\u3000]+"// 空白
->の後ろに移行する文脈を書きます。
0はデフォルト文脈です。
次の定義は"/*"で文脈comに入りcom中では一文字ずつ読み飛ばし、"*/"で
comを抜けるという定義、即ちC/C++のブロックコメントの形式です。
@ ::= ?"/\*" -> com 文脈条件無し、マッチすると文脈をcomにする @com: ::= ?"\*/" -> 0 ; 文脈comで*/があると文脈をデフォルトに戻す @com: ::= ?"." 文脈comで一文字読み飛ばし
2.2.2. 適用順
トークンは最長一致のものが選択されます。
同じ長さの候補がある場合、正規表現と直接表現であれば直接表現が採用されます。
次の定義で"if"が現れるとpat1,pat2とも2文字だが直接表現のpat2が採用される。
"ifa"であればpat1が長くマッチするのでpat1となる。
@pat1 ::= ?"[a-z]+"
@pat2 ::= "if"
普通は有り得ませんが、正規表現同士、直接表現同士で同じ長さの場合は 先に定義されている方が採用されます。
2.2.3. パターンの定義法、単純文字列と正規表現
文字パターンは単純文字列か正規表現を引用符で囲んで与えます。
引用符はダブルクオートあるいはシングルクオートです。
ダブルクオートで囲んだ文字列の中にはシングルクオートを置けます。
シングルクオートで囲んだ文字列の中にはダブルクオートを置くことができます。
"ab'cd" シングルクオートを内部に持つ文字列 'wx"yz' ダブルクオートを内部に持つ文字列
正規表現の場合、引用符の前に?を付けます。
"[" 通常の文字列 ?"[+-]" 正規表現
2.2.4. 文字列の連結
分割記述し + で連結することがでます。
例えばダブルクオー
ト " とシングルクオート '
を持つ文字列
ab'cdwx"yz
は
"ab'cd" + 'wx"yz'と書けます。
正規表現の場合、連結する最初の文字列の前に?を付けます。
?"ab'cd" + 'wx\"yz' + "[0-9]+"
2.2.5. 複数パターンからの選択
パターンは | でつなぐことにより複数記述できます。
例えばASN.1の型名 のように先頭が大文字で、その後は大文字小文字またはハイフンだけど、最後がハイフン であってはならないというパターンは
@typereference ::= "[A-Z][a-zA-Z\-]+[a-zA-Z]" | "[A-Z][a-zA-Z]"
と書けます。
同じトークン名で2つ定義を書いても構いません。
@typereference ::= "[A-Z][a-zA-Z\-]+[a-zA-Z]"
@typereference ::= "[A-Z][a-zA-Z]"
2.2.6. 代表パターンと代替パターン
複数のパターンにマッチしたものを一つの代表パターンの文字列と
して受け取る事ができます。
パターンは / でつなぎます。並びの内最初に出現する
単純文字列指定のパターンが代表パターンとなります。単純文字列
パターンがない場合、 | による記述と同等となります。
代表パターンと代替パターンセットは | でつなぎ複数 記述できます。
次の記述では
@mo ::= "+" | "-" | "×"/"*" | "÷" / "/"
| "&VisibleTime;" / "@" | "±" / "±" / "+-"
"/"が来ても"÷"が来てもトークン名は"mo"でtextは代表パターンの"÷"
になります。
同様に"@"が来ても"&VisbleTime;"が来てもトークン名は"mo"でtextは
"&VisibleTime;"となります。
2.2.7. 開始パターンと終了パターンを指定する定義
トークン定義には開始パターン、終了パターンをチルダ~でつないで指定します。
開始パターン ~ 終了パターン
開始パターンから終了パターンまでがトークンとなりますが、
得られる文字列は開始終了パターンを省いたものとなります。
例えば次の定義では
@mo ::= "+" | "-" | "×"/"*" | "÷" / "/"
| "&VisibleTime;" / "@" | "±" / "±" / "+-"
| "#MO{"~"}"
"+"や"-"などの他"#MO{"と"}"で囲んだ記述"#MO{%%}"などでも テキスト"%%"を持つトークン mo となります。
パターン間の文字列に関しては部分置き換えが可能となっています。
形式は括弧の中に
パターン->置き換え文字列
の並びを書く形です。複数書く場合は間にカンマを入れます。
例えばCの文字列定義は次のように表されます。文字列中の\\は\に置き換えられます。
@STRING ::= '"' ~ '"' ( '\\'->'\','\"'->'"' )
この定義ではダブルクオートからダブルクオートまでが
トークンとなります。
その中に現れる逆スラッシュが前に付くダブルクオートはSTRINGの
終了ではなく単なる文字としています。
2.2.8. トークン名と参照
トークン定義されたものは構文定義で名前で参照されます。
通常の名前の他引用符で囲まれた表記も使用できます。
@Number ::= ?"[0-9]+"
@"+|-" ::= ?"[+-]"
@MUL_DIV ::= ?"[*/]"
//
expr ::= factor { "+|-" factor}
factor ::= term { MUL_DIV term }
term ::= "+|-" Number
引用符表記は構文表記を分かりやすくするためので通常は 、構文定義でのマークとトークン参照の混乱を避けるため、 名前を用います。
トークン名はドット"."を含むことができます。
解析はドットも含んだ定義で行われますが、利用者への通知は
ドットより前の部分で行われます。
例えば
num ::= [0-9][0-9]*
mo ::= "+" | "-" | "*" | "/"
mo.parenSta ::= "("
mo.parenEnd ::= ")"
exp ::= term mo term
term ::= num
| mo.parenSta term mo.parenEnd
という定義では解析はmoとmo.parenSta,moParenEndは全く別の 扱いとなりますが、利用者への通知は同じ"mo"でなされます
2.2.9. 行、改行コードの取り扱い
parseString(String[] lines)で予め
行分解されてある場合を除き、文は一旦行毎に分解され、
行端に改行コード"\n"が追加された上で解析します。
parseString(String[] lines)で予め
行分解されてある場合も各行端に改行コード"\n"が追加されます。この時
各行内に予め改行コード"\n","\r","\r\n"が含まれている部分に関して
はそのまま解析されます。行データの途中に改行コードがある場合、
複数行扱いとなり正規表現の^(行先頭)および$(行最後)は機能します。
ただし、エラーメッセージで表示される行番号は、内部の改行コードを
無視したものとなります。
2.3. 構文定義法
構文定義は
構文名 ::= 構文要素並び
の形で行います。
構文名には/で囲むプロパティ設定を付加することができます。
プロパティに関してはプロパティ指定を
参照してください。
構文名はドット"."を含むことができます。
解析はドットも含んだ定義で行われますが、利用者への通知は
ドットより前の部分で行われます。
構文要素としては
があります。
参照には/で囲むプロパティ設定を付加することができます。
ただし、現版ではトークン定義に対するaltプロパティのみ
が有効です。プロパティに関してはプロパティ指定を
参照してください。
並びを制御するものとして
があります。
2.3.1. 他構文定義の参照
文書内他の場所で定義された構文名で参照することができます。
参照は前後を問いません。
def_4 ::= ...
def_1 ::= def_4 "," def_3 // OK 前後どちらも参照可能
def_2 ::= ...
def_3 ::= ...
構文には補助名をつけることができますが、補助名は別定義から 参照することはできません。
def_1 ::= def_b // NG! 補助名は参照できない def_2 ::= def_a : .... // def_aは補助名 | def_b : ....
2.3.2. トークンの参照
既出トークンをその名前で参照することができます。
トークンは参照より前に定義されていなければなりません。
@token_a ::= ?"[a-z]+"
def_1 ::= token_a token_b // NG!token_aは参照できるが
// token_bは参照できない
@token_b ::= ?"[A-Z]+"
トークンの名前は引用符付きのものも使えます。引用符付きで 定義されたものは引用符付きで参照します。
@Number ::= ?"[0-9]+"
@"+|-" ::= ?"[+-]"
@MUL_DIV ::= ?"[*/]"
//
expr ::= factor { "+|-" factor}
factor ::= term { MUL_DIV term }
term ::= "+|-" Number
次の文字列パターンと混乱しないようにしなくてはなりません。
2.3.3. 文字列パターン
構文解析には用いられますが、readによる通知をしないマークを 構文定義上に文字列パターンで記述できます。
assignment ::= Name "=" expression ";"
// a = 1+2 ;
// "="と";"は構文解釈のマークとはなるが、read要素とはならない
void assignment(SyntaxReader sr_){
String name= sr_.readToken("Name").text;
// "="の読み飛ばしは不要
sr_.readBegin("expression");
...
}
2.3.4. 順並び
文法上の要素並びに従って要素を並べます。
例えば、Aという構文はBの後ろにC、その後ろにDが並ぶ場合、
A ::= B C D
と書きます。
要素と要素の間は1個以上の空白文字(空白、改行、全角空白)です。
2.3.5. オプショナル指定
省略を許すオプショナル指定は [...] です。
@Number ::= ?"[0-9]+" // この行の[..]は省略ではなく正規表現です @"+|-" ::= ?"[+-]" // この行の[..]は省略ではなく正規表現です term ::= ["+|-"] Number // +|-が省略可能 123,-123,+123
2.3.6. 繰り返し指定
1個以上の繰り返しを {...} で表します。
0 を付加した 0{...} は0個以上の繰り返しとなります。
オプショナル指定と直接組み合わせた [{...}] と0個以上指定 0{...} は正確に同じ意味合いと解釈されます。
name-list ::= { Name } // Nameが一個以上
name-list-0 ::= [{ Name }] // Nameが0個以上
繰り返し時に間に挟む区切り要素を & の後ろに記述できます。
要素はトークンか文字列です。
この要素は 2回以上の繰り返し時のみ出現するもので、0個または1個の場合は出現しません。
name_list ::= { name &","} //区切り要素としてカンマ指定の繰り返し
例)
A 一個の場合カンマ無し
A,B 複数の場合カンマあり
A,B,C,D 複数の場合カンマあり
区切り要素指定は記述の簡素化が目的であり、区切り要素指定を使わず 記述することもできます。
name_list ::= name [{ "," name }]
// name_list ::= { name &","} と記述することと同じ
繰り返し時に区切り要素があってもなくても良い場合は区切り要素を [ ] で囲みます。
name_list ::= { name &[","]} //区切り要素としてオプショナルカンマ指定
例)
A,B C D,E 区切りとしてのカンマはあってもなくても良い
2.3.7. 複数候補選択指定
要素並びを | で分けることにより、複数の並び候補とすることができます。
A ::= B C
| D E
この例では B C の並びか D E の並びの何れかということになります。
選択要素に補助名を付加することができます。要素並びの前に名前:を書きます
A ::= sub1: B C // A_sub1構文
| sub2: D E // A_sub2構文
補助名は構文上参照したりは出来ませんが、通知される構文の名前付きの階層
となります。
親構文の名前にアンダースコアとともに補助名を付加したものが補助構文の
名前となります。上の例ではA_sub1、A_sub2という名前で通知されます。
なお、補助名は選択指定でない場合も付加することができます。
2.3.8. 無名構文構造
構文要素並びの中に()で囲んで名前のない構文を定義できます。主に 選択構文をblockで記述したいときに用います。
A ::= B ( C | D ) E
この指定では B C E または B D E が許されます。
無名の構文階層は階層としては通知されません。
2.3.9. 空白禁止指定(現版では無効です)
構文要素と要素の間に!を書くと、その要素間に空白があってはならない ことを示します。
A ::= "<" ! NAME C ">" // < と NAMEの間に空白があってはならない
この例で、NAMEがbooだったとして <boo は許されるが < boo は 許されません。
!のあるなしで構文の区別はできないことに注意をしてください。
2.4. プロパティ指定
構文定義名と参照名にはプロパティ指定ができます。
プロパティ指定は/で囲まれた文字列で、;で分断された複数の 指定が、:で名前と値に分かれています。値を持たないプロパティ もあります。値は,で複数並んでいる場合もあります
name/prop-A:val-1;prop-B;prop-C:x,y,x/
---
nameは次のプロパティを持つ
prop-A 値 val-1
prop-B 値 無し
prop-C 値 x y zの3値ならび
例えば次のように指定します。
@ ::= ?"[ \t\n\f\r ]+" @ ::= ?"//.*$" @VALUE ::= ?"[0-9]+\.[0-9]+" | ?"[0-9]+" @NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*" // actionsが構文の開始 actions/start/ ::= { assignment } // トークンを"NAME"という名でなく"variable-name"という名通知する assignment ::= NAME/alt:variable-name/ "=" expression // expression階層は通知しない expression/omit/ ::= term | minus | plus | mul | div | add | sub // term階層は通知しまい。トークンを"NAME"でなく"variable-refer"で通知 term/omit/ ::= VALUE | NAME/alt:variable-refer/ | function | "(" expression ")" // トークンを"NAME"でなく"func-name"で通知 function ::= NAME/alt:func-name/ "(" [ expression ] ")" // 結合度9 minus/p:9/ ::= "-" expression // 結合度9 plus/p:9/ ::= "+" expression // 左優先、結合度5 mul/p:5/ ::= expression "*" expression // 左優先、結合度5 div/p:5/ ::= expression "/" expression // 左優先、結合度3 add/p:3/ ::= expression "+" expression // 左優先、結合度3 sub/p:3/ ::= expression "-" expression
alt以外のプロパティはプラグマ指定でも同様の効果を得ることができます。
構文定義名部と参照部では指定できるプロパティに差があります。
| プロパティ名 | 値 | 使用場所 | 説明 |
| priority prio p |
数値 構文名リスト |
構文定義 |
値は数値または、構文名リストです。 数値の場合は/left:数値/と同じです。二項演算子または 前置子、後置子の結合度を指定します。 構文名リストの場合、 この構文を、リスト内の構文より優先します。 /priority:a/、/prio:a/、/p:a/何れも同じ意味です。 |
| left | 数値 | 構文定義 |
2項演算の演算子、または前置子、後置子の結び付きの強さを
指定します。前置の場合leftという表現は適切ではありません
ので/p:数値/を使うことをお勧めします。 数値の大きな定義が優先されます。 同じ数値の場合、2項演算では左優先となります。 /p:数値/指定でも同じです。 |
| right | 数値 | 構文定義 |
2項演算の演算子の結び付きの強さを指定します。 2項演算が並ぶ場合数値の大きな定義が優先されます。 同じ数値の場合、右優先となります。 |
| long | 無し | 構文定義 |
構文が長くなる解釈を優先します。 プラグマ指定の//#priority-longerと同じ効果を持ちます。 |
| short | 無し | 構文定義 | 構文が短くなる解釈を優先します。 |
| start | 無し | 構文定義 |
構文の開始を指定します。 省略時は先頭の構文定義が開始となります。 |
| omit | 無し | 構文定義、参照 |
解析時この定義を通知しないことを指定します。 プラグマ指定の//#omitと同じ効果を持ちます。 |
| alt | 代理名 | 参照 |
本来の名前でなく、代理名で通知することを指定します。 適用できるのはトークンの参照のみです。 |
一連の優先度指定に関しては 曖昧性の解決 を参照してください。
2.5. プラグマ定義
次の補助的定義があります。
| 形式 | 説明 |
| //#omit 構文名またはトークン名 | 指定構文またはトークンをreadで読み出さないことを指定します |
| //#start-syntax 開始構文名 | 入り口となる構文を指定します。 省略すると、先頭の構文定義が入り口となります |
| //#priority-left[:数値] トークン文字列の列 | 2項演算のオーぺレータの優先度を指定します。 複数オペレータを記述するとそれらは同一優先度となります。 同一優先度の場合左項を先に解釈します。 優先度は数値で指定することもできます。数値を指定しない場合、 記述順に優先度が設定されます。先に記述したものが強く なります。 数値指定はpriority-leftの後ろに:数値を書きます。。 |
| //#priority-right[:数値] トークン文字列の列 | 2項演算のオーぺレータの優先度を指定します。 複数オペレータを記述するとそれらは同一優先度となります。 同一優先度の場合右項を先に解釈します。 優先度は数値で指定することもできます。数値を指定しない場合、 記述順に優先度が設定されます。先に記述したものが強く なります。 数値指定はpriority-rightの後ろに:数値を書きます。 |
| //#priority-mono[:数値] トークン文字列の列 | 単項演算(前置、後置)オーぺレータの優先度を指定します。 複数オペレータを記述するとそれらは同一優先度となります。 同一優先度の場合右項を先に解釈します。 優先度は数値で指定することもできます。数値を指定しない場合、 記述順に優先度が設定されます。先に記述したものが強く なります。 数値指定はpriority-rightの後ろに:数値を書きます。 前置、後置の区別は付けられません。前置、後置で別の 扱いが必要な場合は pプロパティ で指定してください。 |
| //#priority-longer 構文名 | 指定構文の長めの解釈が優先するとして曖昧性を解決します。 |
| //#priority-shorter 構文名 | 指定構文の短めの解釈が優先するとして曖昧性を解決します。 |
| //#priority 構文名>構文名 | 構文の優先度を指定して曖昧性を解決します。 |
| //#ignore-case | 引用符で囲まれた部分以外を大文字にして解釈します。これは IBM系の大文字小文字を区別しないという構文に簡便に対応する ためのものです |
#omitと#start-syntax以外は構文の解釈そのものの指定ですので、
各構文のプロパティとして設定することを推奨します。
#omitに関しても、概念として置いた構文階層ではなく、単にまとめるためだけの
構文階層の場合、プロパティ/omit/を書くこともできます。
一連のpriority指定に関しては 曖昧性の解決 を参照してください。
同じ意味合いをプロパティで指定したものと、プラグマで指定した 例を載せます。
====== プロパティ指定 ======
@ ::= ?"[ \t\n\f\r ]+"
@ ::= ?"//.*$"
@VALUE ::= ?"[0-9]+\.[0-9]+" | ?"[0-9]+"
@NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*"
actions ::= { assignment }
assignment ::= NAME "=" expression [";"]
expression/omit/ ::= term | minus | plus | fract | mul | div | add | sub
term/omit/ ::= VALUE | NAME | function | "(" expression ")"
function ::= NAME "(" [{ expression &","}] ")"
minus/p:10/ ::= "-" expression
plus/p:10/ ::= "+" expression
fract/p:9/ ::= expression "!"
mul/p:5/ ::= expression "*" expression
div/p:5/ ::= expression "/" expression
add/p:2/ ::= expression "+" expression
sub/p:2/ ::= expression "-" expression
====== プラグマ指定 ======
//#omit term expression
//#priority-left:5 * /
//#priority-left:2 + -
//#priority-mono:10 + -
//#priority-mono:9 !
@ ::= ?"[ \t\n\f\r ]+"
@ ::= ?"//.*$"
@VALUE ::= ?"[0-9]+\.[0-9]+" | ?"[0-9]+"
@NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*"
actions ::= { assignment }
assignment ::= NAME "=" expression [";"]
expression ::= term | minus | plus | fract | mul | div | add | sub
term ::= VALUE | NAME | function | "(" expression ")"
function ::= NAME "(" [{ expression &","}] ")"
minus ::= "-" expression
plus ::= "+" expression
fract ::= expression "!"
mul ::= expression "*" expression
div ::= expression "/" expression
add ::= expression "+" expression
sub ::= expression "-" expression
3.留意する点
3.1 先頭再帰(左再帰)
一般的BNFには繰り返しを定義できませんので、 その代わりの誤魔化しとして先頭再帰(左再帰)を 良く用います。
name-list ::= name
| name-list name
symphonieでもこの記述は受け付けますが、symphonieには繰り返し 表記がありますので、繰り返しの表現には繰り返しを用いるべきです。
name-list ::= { name }
symphonieは構文要素は構文位置が決定し次第readに上げられます が、先頭再帰の場合、繰り返しの最後まで位置は決定されません。 内部での状態保留が多くなりますので、注意が必要です。
[an error occurred while processing this directive]
@ ::= ?"[ \t\n ]+"
@ ::= ?"//.*$"
@NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*"
defs ::= { def }
def ::= NAME ":" name-list
name-list ::= [{ NAME }]
で次のデータ
a : b c d e : f g : h i
を読み込むと次の動作をします。
入力 解析結果
a <defs>
| <def>
| | <NAME text="a" />
:
b | | <name-list>
c | | | <NAME text="b" />
d | | | <NAME text="c" />
e | | | <NAME text="d" />
: | | </name-list>
| </def>
| <def>
| | <NAME text="e" />
f | | <name-list>
< EOF > | | | <NAME text="f" />
| | </name-list>
| </def>
</defs>
これに対し、次の先頭再帰(左再帰)定義
@ ::= ?"[ \t\n ]+"
@ ::= ?"//.*$"
@NAME ::= ?"[a-zA-Z][0-9a-zA-Z]*"
defs ::= { def }
def ::= NAME ":" name-list
name-list ::= /* null */
| name-list NAME
の場合、同じデータを読み込むと次のような動作をします。
入力 解析結果
a <defs>
| <def>
| | <NAME text="a" />
:
b
c
d
e
: | | <name-list>
| | | <name-list>
| | | | <name-list>
| | | | ! <name-list>
| | | | ! </name-list>
| | | | ! <NAME text="b" />
| | | | </name-list>
| | | | <NAME text="c" />
| | | </name-list>
| | | <NAME text="d" />
| | </name-list>
| </def>
| <def>
| | <NAME text="e" />
f
< EOF > | | <name-list>
| | | <name-list>
| | | </name-list>
| | | <NAME text="f" />
| | </name-list>
| </def>
</defs>
[an error occurred while processing this directive]
式のような小さなものが先頭再帰(左再帰)で書かれることは 殆ど問題ありませんが、大量の文からなる構造の文の並びが 先頭再帰で書かれた場合、構文確定が全ての文を読み込んだ 後、EOFに達した時点になります。
3.2 曖昧性の解決
構文の曖昧性は構文を解析する中でチェックされます。構文定義 を読み込んだだけでは曖昧性のチェックは行われません。
曖昧な構文は通常エラーとしますが、プラグマ指定で 優先度を与えることにより解決することができるように なっています。
3.2.1. 左右再帰の二項演算の曖昧性回避
二項演算を左右再帰で定義した場合、曖昧性が生じます。
add ::= expression "+" expression の場合 A + B + C を ( ( A + B ) + C ) と解釈すべきか ( A + ( B + C )) と解釈すべきか不明
また、+と*など接続の強弱の違いも考慮しなくてはなりません。
A + B * C + D は ( ( A + ( B * C ) ) + D )
と解釈される必要があります。
左右再帰形式の2項演算定義の場合、pまたはrightプロパティ
を設定することにより、曖昧性を回避できます。
プロパティ値は10進数値で、大きな値程優先度が高くなります。
同じ値で同じp/rightの場合は、左優先、右優先で解釈されます。
例えば
add/p:8/ ::= expression "+" expression sub/p:8/ ::= expression "-" expression mul/p:10/ ::= expression "*" expression div/p:10/ ::= expression "/" expression assign/right:4/ ::= expression "=" expression
の場合
A + B + C - D なら ( ( A + B ) + C ) - D
即ち sub(add(add(A,B),C),D)
A = B = C = D なら A = ( B = ( C = D ) )
即ち assign(A,assign(B,assign(C,D)))
と解釈されます。優先度が異なる場合は優先度に沿って解釈 されます。
A + B * C - D なら ( ( A + ( B * C ) ) - D
即ち sub(add(A,mul(B,c)),D)
A = B + C = D なら A = ( ( B + C ) = D )
即ち assign(A,asign(add(B,C),D))
となります。なお、pの代わりにleftも使えます。leftの方が 左優先という事を示すことが出来ますが、殆どの場合左 優先であり「右優先」指定を目立たせる意味でもpを使うこと が推奨されます。
pはpriorityの省略形であり省略せず/priority:数値/と書くこと あるいは少し省略した/prio:数値/と書く方法も用意してあり ます。
プロパティ指定の他、プラグマ定義(//#priority-right,//#priority-left) でも同様に指定できます。プロパティ定義と異なり指定は構文名では なくオペレータとなっていることに注意してください。
//#priority-left:8 + - //#priority-left:10 * / //#priority-right:4 = add ::= expression "+" expression sub ::= expression "-" expression mul ::= expression "*" expression div ::= expression "/" expression assign ::= expression "=" expression
3.2.2. 左右再帰の二項演算、前置、後置の曖昧性回避
前置子"-"も曖昧性を持ちます。例えば、前置子と二項演算子を持つ次の式
- a + b
は
1: (-(a+b)) 2: ((-a)+b)
の何れとも解釈できます。前置子と後置子でも曖昧性が発生します。
-a! // !は階乗を現す
は
1: (-(a!)) 2: ((-a)!)
の何れの解釈も可能です。
前置、後置の演算子の結合則もpプロパティで指定します。
minus/p:18/ ::= "-" expression plus/p:18/ ::= "+" expression fract/p:15/ ::= expression "!" // n!形式で階乗を現す add/p:8/ ::= expression "+" expression sub/p:8/ ::= expression "-" expression mul/p:10/ ::= expression "*" expression div/p:10/ ::= expression "/" expression
数値の大きな結合が優先されます。
- A - B * - C ! / D
は
((-A)-((B * ((-C)!))/D )
となります。
3.2.3. 終了曖昧性の回避(if-if-else問題)
一般的なプログラム言語でのif-elseには曖昧性があり、例えば次のような文
if(a) if(b) c=2; else d=3; // if(a)のelseかif(b)のelseか
ではelseがif(a)のelseなのかif(b)のelseなのか曖昧です。
if(a){ if(b) c=2; else d=3; } か if(a){ if(b) c=2; } else d=3; のどちらともとれる
一般には前者、if文が長く解釈される方がとられます。
構文が長くなる解釈を優先するか、短くなる解釈を優先するか はlongプロパティ、shortプロパティで指定できます。
if-else部のみに限定した構文定義を示します。
// 論理構造終了曖昧性の解決 if if else
@ ::= ?"[ \t\n\f\r\u3000]+"
@ ::= ?"//.*$"
@NUMBER ::= ?"[0-9]+"
@NAME ::= ?"[a-zA-Z]+"
sentences ::= { sentence }
sentence ::= assign-sentence | if-sentence
assign-sentence ::= NAME "=" NUMBER ";"
if-sentence/long/ ::= "if" "(" NAME ")" sentence [ else-part ]
else-part ::= "else" sentence
ここではif-sentenceにlongを指定しており、if-sentenceが長くなる 解釈を優先します。
if(a){ if(b) c=2;
else d=3; }
longの代わりにshort 指定を行うと、if-sentenceが短くなる解釈が行われます。
if(a){ if(b) c=2; }
else d=3;
(解釈の分かれ道は内部のifにありますので、内部のifの長さによる 解決を行っています)
プロパティによる曖昧性解決は曖昧なデータについてのみ行われ ますので、曖昧でない、次のような文
if(a) if(b) c=2; else d=3; // if(b)のelseでしかありえない else e=4; // if(a)のelseでしかありえない
では何も影響を与えません。
プロパティ定義と同等の指定をプラグマ指定(//#priority-longer,//#priority-shorter) で行うことができます。
// 論理構造終了曖昧性の解決 if if else
//#property-longer if-sentence
@ ::= ?"[ \t\n\f\r\u3000]+"
@ ::= ?"//.*$"
@NUMBER ::= ?"[0-9]+"
@NAME ::= ?"[a-zA-Z]+"
sentences ::= { sentence }
sentence ::= assign-sentence | if-sentence
assign-sentence ::= NAME "=" NUMBER ";"
if-sentence ::= "if" "(" NAME ")" sentence [ else-part ]
else-part ::= "else" sentence
3.2.4. 構文選択の曖昧性
次のような2つの形式がある構文で
{ a 1 2 3 } // oid:名前の後に数値が続く
{ b 4 , c 5 , d 6 } // struct:名前 数値パターンが","を挟み繰返す
次のような文の場合
{ e 7 }
どちらの構文なのか曖昧です。
このような曖昧性を持つ構文に優先順位を付け曖昧性
を回避するpriorityプロパティがあります。
ある構文が他の構文より優先度が高い場合、高い構文
に他の構文の並びを値としてもつpriorityプロパティ
を設定します。構文の並びはカンマで区切ります。
構文1/priority:構文2,構文3/ //構文1は構文2,構文3より優先度が高い
次の構文定義では、struct-valueがoid-valueより優先度 が高いと指定しています。
@ ::= ?"[ \t\n\f\r\u3000]+"
@ ::= ?"//.*$"
@NUMBER ::= ?"[0-9]+"
@NAME ::= ?"[a-zA-Z]+"
sentences ::= { sentence }
sentence ::= oid-value | struct-value
oid-value ::= "{" NAME { NUMBER } "}"
struct-value/priority:oid-value/ ::= "{" NAME NUMBER [{ "," NAME NUMBER }] "}"
この定義では{ e 7 }はstruct-valueと解釈されます。
優先度定義を入れ替え
oid-value/priority:struct-value/ ::= "{" NAME { NUMBER } "}"
struct-value ::= "{" NAME NUMBER [{ "," NAME NUMBER }] "}"
とすると{ e 7 }はoid-valueと解釈されます。
プラグマ指定(//#priority)でも優先度を指定できます。
記述法は//#priorityの後に 優先構文>構文
または 構文&優先構文 を並べます。
例えば
//#priority struct-value>oid-value
@ ::= ?"[ \t\n\f\r\u3000]+"
@ ::= ?"//.*$"
@NUMBER ::= ?"[0-9]+"
@NAME ::= ?"[a-zA-Z]+"
sentences ::= { sentence }
sentence ::= oid-value | struct-value
oid-value ::= "{" NAME { NUMBER } "}"
struct-value ::= "{" NAME NUMBER [{ "," NAME NUMBER }] "}"
SyntaxReader,TokenReaderはC,C++のyacc/lexと
同様に構文定義を与え、構文解析を行うシステムですが、色々な
点で大きく異なっています。
ライブラリである:
yacc/lexはプログラムを生成するツールとして提供されます。
利用者はトークンパターン、構文パターンに部分的なばらばらの プログラム記述を付加します。 それらを構文解析部から呼び出す形に再構築した一つの プログラムソースをyacc/lexは生成します。
これに対し、SyntaxReader,SyntaxDom,SyntaxConverterは純粋なライブラリです。 構文パターンも実行時にデータとして入力されます。
イベント駆動ではなく能動的取り込みである:
yacc/lexに書く利用者プログラムは、パターンがマッチした 時に呼ばれるパーツパーツのイベント駆動式のものです。
これに対しSyntaxReaderでは、その名の通り、 アプリケーションが能動的にreadを行い、ライブラリは それに対し、構造情報、トークン情報をシーケンシャル 引き渡していく形をとります。
構文要素は順次確定して行く:
yaccでは内側の要素が先に確定し、外側がまだ確定
していないため、処理が難しくなることがあります。
SyntaxReaderでは常に外から順に構文は確定します。
構文木データを直接得るインターフェースがある:
Symphonieには構文木を直接得るインターフェース が用意されています。
LR(2)制限がない:
yaccは1トークンの先読みで解決できる文法しか取り扱えません がSymphonieにはこの制限はありません。
デリミタ付きの繰り返し、オプショナル定義がある:
yaccには繰り返し定義がありませんので、繰り返しは再帰定義に
よりごまかす必要があります。またオプショナル定義もありません
ので例えば関数の引数、例えば
func() // 引数はオプショナル
func(A) // 一個の場合
func(A,B) // 複数の場合は","で区切る
といった形の定義はかなり面倒なものとなります。
Symphonieではデリミタ付き繰り返し定義がありますので
function ::= NAME "(" [{ expression &","}] ")"
のようにで定義ができます。
トークン定義は固定文字列と正規表現が別記述:
トークン定義法は固定文字列と正規表現を分けています。
@NAME ::= ?"[a-zA-Z_][a-zA-Z0-9_]+" // 正規表現 @sqBrSta ::= "[" // 固定文字列 @IF ::= "if"
lexでは[などの記号をトークンとしたい場合、正規表現との 衝突を避けるための記述が煩わしいものになりがちですが、 SymphonicNotationでは固定文字列と正規表現は別になります ので、煩わしさとそれに導かれるバグから解放されます。
パターンは最長マッチングが行われますが、正規表現と
固定文字列で同じ長さとなった場合は固定文字列が優先
されます。
例えば上の定義では"ifa"はNAMEに"if"はIFにマッチします。
lexでは記述順に神経を使わないと"if"がNAMEになって
しまいます。
結合則は文法の1部として記述:
yaccでは2項演算の結合則left/rightはBNFとは別の レベルの、%typeなどと同じ実装補助として記述 します。
SymphonicNotationでは文法定義の一環であり BNFによる構文定義にプロパティとして記述します。