 
[例題のプログラミング言語の文法]
(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();
}
}
}
目 次
1.言語プロセッサの処理
2.文の識別
3.語彙解析
4.逆ポーランド記法
5.構文解析とコード生成
6.インタプリタ
ダウンロード
(以下をクリックするとダウンロードできます)
|