3.語彙解析 |
|||||||||||||
[例題のプログラミング言語の文法]
(1)語彙解析の考え方 文字列を単語に分ける処理です。 単語に分けた結果を表現するために, たとえば次のような構造体を宣言しておきましょう。 public struct WordData { public string type; public string str; public WordData(string tp,string st) { type = tp; str = st; } } 語彙解析で注意する点は, 複数の文字種別による単語がありうることです。 例えば,C#では「12.5E-12」のような指数表現です。 また,C#の演算子「==」,「>=」のように, 複数の区切り記号でひとつの演算子を表現することもあります。 文の終わりの判別とこれらの比較を同時に行うと 判定が非常に複雑になります。 また,語彙解析のバグのほとんどは, これらの処理の部分に潜在しがちです。 この複雑さを避ける常套手段は, (a) 文字種別による識別 (b) 単語の識別 の2段階に分離することです。 すなわち,単語識別では, 文字種別による識別を呼び出すことにします。 (2)文字種別による識別 まず,文字種別を判別するためのメソッドを作成します。 public string 数字="0123456789"; public string 区切記号=",.+-/*^=:<>[]()|&%!{}"; private int 該当文字(string str, char CH) { for(int i=0;i<str.Length;i++) if(str[i]==CH) return i; return -1; } 文字種別による識別処理は,以下のように簡単です。 private WordData LA0() // 文字種別による識別 { while(ptr<str.Length) { if(str[ptr]==' ' || str[ptr]==' ' || str[ptr]=='\t') ptr++; else if(str[ptr]=='\n') ptr++; else if(str[ptr]=='\r') ptr++; else if(str[ptr]=='\"') return 文字列設定(); else if(str[ptr]=='.' ) return 数字列設定(); else if(該当文字(数字,str[ptr])>=0) return 数字列設定(); else if(該当文字(区切記号,str[ptr])>=0) return 区切記号設定(); else return 名前設定(); } return new WordData("End",""); } 次の2重引用符(")がくるまでを文字列とします。 なお,2重引用符が2個続く場合は, 1個の2重引用符としています。 private WordData 文字列設定() { string S=""ptr++; while(ptr<str.Length) { if(str[ptr]=='\n'||str[ptr]=='\r') { MessageBox.Show("\"がありません"); return new WordData("String",S); } else if(str[ptr]=='"') { ptr++; if((ptr==str.Length) || (str[ptr]!='"')) return new WordData("String",S); } S=S+str[ptr].ToString(); ptr++; } MessageBox.Show("\"がありません"); return new WordData("String",S); } 数字列,またはドット(.)がある場合,続く数字列を数値とします。 ただし,ドットだけの場合,区切り記号としてのドットとみなします。 private WordData 数字列設定() { string S=""; while(ptr<str.Length && 該当文字(数字,str[ptr])>=0) { S += str[ptr].ToString(); ptr++; } if(ptr<str.Length && str[ptr]=='.') { S +="."; ptr++; while(ptr<str.Length && 該当文字(数字,str[ptr])>=0) { S += str[ptr].ToString(); ptr++; } } if(S==".") return new WordData("Delimiter",S); else return new WordData("Number",S); } 次の区切り記号がくるまでの文字列は,名前とみなします。 private WordData 名前設定() { string S=""; while(ptr<str.Length) { if ( str[ptr]=='"' || str[ptr]==' ' || str[ptr]==' ' || str[ptr]=='\n'|| str[ptr]=='\r') break; int N = 該当文字(区切記号,str[ptr]); if (N>=0) break; S += str[ptr].ToString(); ptr++; } return new WordData("Name",S); } この段階では,1文字の区切り記号を区切り記号とします。 private WordData 区切記号設定() { WordData P= new WordData("Delimiter",str[ptr].ToString()); ptr++; return P; } (3)単語の識別 語彙解析の結果を格納する配列を用意します。 public int numTokenX=0 ; // 語彙解析結果の格納位置 public WordData[] tokenX=new WordData[500]; // 語彙解析結果 指数形式の場合,数値(ドットを含む)の後のパターンとして 次のバラエティが考えられます。 E+数値,E-数値,E数値 しかし,最後のパターンは,文字種別による識別で 「名前」として識別されていますので, これを判別するメソッドを用意します。 // Eに続いて数字列になっているか(指数形式の判別) private bool E_exp(string str) { if(str.Length<2) return false; if(str[0]!='E' && str[0]!='e') return false; for(int i=1;i<str.Length;i++) if(該当文字(数字,str[i])<0) return false; return true; } デバッグ用に途中経過を表示するプログラムを用意します。 このようにあらかじめ, 途中経過を表示するプログラムを用意して, デバッグモードを用意し, いつでも途中経過を表示できるようにしておけば, 期待しない結果になったとき便利です。 デバッグモードはcheckBox1で指定することにしています。 // 途中経過表示用(途中経過表示なければ必要ない) private void debugLA (WordData token1,WordData token2,WordData token3,WordData token4) { string S=" 1:"+ token1.type+ " \t ["+token1.str+"]\n"+ " 2:"+ token2.type+" \t ["+token2.str+"]\n"+ " 3:"+ token3.type+" \t ["+token3.str+"]\n"+ " 4:"+ token4.type+" \t ["+token4.str+"]"; DialogResult result = MessageBox.Show (S,"語彙解析途中",MessageBoxButtons.OKCancel); if(result==DialogResult.Cancel) checkBox1.Checked=false; } 語彙解析結果をセットするメソッドです。 private void set_TokenX(string type, string str) // 語彙解析結果のセット { tokenX[numTokenX].type=type; tokenX[numTokenX].str=str; numTokenX++; } 複数区切り記号による演算子を結合します。 private void combineDelimiter(string str1, string str2) { set_TokenX("Delimiter",str1+str2);} 以下のように文字種別の識別結果のパターンにより単語を識別します。 ただし,途中を省略していますので,全文を見たい場合は, ソースプログラムをダウンロードして下さい。 [⇒今すぐダウンロードする] なお,token1が文の終わりのとき,その他のトークンも文の終わりになります。 private void LA1() { numTokenX=0; WordData token1,token2,token3,token4; token1=LA0();token2=LA0();token3=LA0();token4=LA0(); while(token1.type!="End") { if(checkBox1.Checked)debugLA(token1,token2,token3,token4); if( token1.type=="Number" && token2.type=="Name" && token2.str=="E" && token3.type=="Delimiter" && (token3.str=="+" || token3.str=="-")&& token4.type=="Number") { set_TokenX("Number",token1.str+token2.str+token3.str+token4.str); token1=LA0();token2=LA0();token3=LA0();token4=LA0(); } else if( token1.type=="Number" && token2.type=="Name" && E_exp(token2.str)) { set_TokenX("Number",token1.str+token2.str); token1=token3;token2=token4;token3=LA0();token4=LA0(); } else if( token1.type=="Delimiter" && token1.str==">" && token2.type=="Delimiter" && token2.str=="=" ) { combineDelimiter(token1.str,token2.str); token1=token3;token2=token4;token3=LA0();token4=LA0(); } else if(token1.type=="Delimiter" && token1.str==">" && token2.type=="Delimiter" && token2.str=="=" ) { combineDelimiter(token1.str,token2.str); token1=token3;token2=token4;token3=LA0();token4=LA0(); } ・ ・ ・ (中略: 全ソースを見るには,例題プログラムをダウンロードしてください) ・ ・ } else if( token1.type=="Delimiter" && token1.str=="/" && token2.type=="Delimiter" && token2.str=="=") { combineDelimiter(token1.str,token2.str); token1=token3;token2=token4;token3=LA0();token4=LA0(); } else if( token1.type=="Name" && token1.str=="end" && token2.type=="Name" && token2.str=="if") { // 「end if」 は,「endif」という単一の単語とみなす。 set_TokenX(token1.type,token1.str+token2.str); token1=token3;token2=token4;token3=LA0();token4=LA0(); } else { // 複数単語のパターンなしのとき set_TokenX(token1.type,token1.str); token1=token2;token2=token3;token3=token4;token4=LA0(); } } } 目 次 ダウンロード
|
|||||||||||||