3.語彙解析

[例題のプログラミング言語の文法]
 内  容
(1) 語彙解析の考え方
(2) 文字種別による識別
(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();
      }
    }
  }


    目 次

1.言語プロセッサの処理

2.文の識別

3.語彙解析

4.逆ポーランド記法

5.構文解析とコード生成

6.インタプリタ


ダウンロード
(以下をクリックするとダウンロードできます)

逆ポーランド記法への変換
本記事で示すソースプログラム例