前: Mfcalc Rules, 上: Multi-function Calc


2.4.3 mfcalcの記号表

変数と関数の名前と意味を保持するために、 多機能電卓は記号表を必要とします。 これは、アクションを除く文法規則とBison宣言には影響しませんが、 追加のCの関数がいくつか必要です。

記号表は、レコードのリンクリストからなります。 その定義は、後述の、ヘッダファイルcalc.hにあります。 関数と変数の両方を表に置くことができます。

     /* 記号表のリンクを表すデータ型                 */
     struct symrec
     {
       char *name;  /* 記号の名前                    */
       int type;    /* 記号の種類:VARまたはFNCT     */
       union {
         double var;           /* VARの値            */
         double (*fnctptr)();  /* FNCTの値           */
       } value;
       struct symrec *next;    /* 次の項目へのリンク */
     };
     
     typedef struct symrec symrec;
     
     /* `struct symrec'のリンクである記号表          */
     extern symrec *sym_table;
     
     symrec *putsym ();
     symrec *getsym ();

新しいmain関数は、記号表を初期化する関数である init_tableを呼びます。 maininit_tableを以下に示します。

     #include <stdio.h>
     
     main ()
     {
       init_table ();
       yyparse ();
     }
     
     yyerror (s)  /* エラーがあるとyyparseから呼び出される */
          char *s;
     {
       printf ("%s\n", s);
     }
     
     struct init
     {
       char *fname;
       double (*fnct)();
     };
     
     struct init arith_fncts[]
       = {
           "sin", sin,
           "cos", cos,
           "atan", atan,
           "ln", log,
           "exp", exp,
           "sqrt", sqrt,
           0, 0
         };
     
     /* 記号表:`struct symrec'のリスト       */
     symrec *sym_table = (symrec *)0;
     
     init_table ()  /* 数学関数を表に登録する */
     {
       int i;
       symrec *ptr;
       for (i = 0; arith_fncts[i].fname != 0; i++)
         {
           ptr = putsym (arith_fncts[i].fname, FNCT);
           ptr->value.fnctptr = arith_fncts[i].fnct;
         }
     }

単純に初期化リストを編集して、必要なインクルードファイルを追加するだけで、 電卓に関数を追加できます。

記号表に記号を登録して検索するために、2個の重要な関数があります。 関数putsymは、登録すべきオブジェクトの 名前と型(VARFNCT)を渡されます。 オブジェクトはリストの先頭にリンクされ、 オブジェクトへのポインタが返されます。 関数getsymは、検索すべき記号の名前を渡されます。 もし見つかれば記号へのポインタが返され、 見つからなければ0が返されます。

     symrec *
     putsym (sym_name,sym_type)
          char *sym_name;
          int sym_type;
     {
       symrec *ptr;
       ptr = (symrec *) malloc (sizeof (symrec));
       ptr->name = (char *) malloc (strlen (sym_name) + 1);
       strcpy (ptr->name,sym_name);
       ptr->type = sym_type;
       ptr->value.var = 0; /* 関数の場合にも値を0にする */
       ptr->next = (struct symrec *)sym_table;
       sym_table = ptr;
       return ptr;
     }
     
     symrec *
     getsym (sym_name)
          char *sym_name;
     {
       symrec *ptr;
       for (ptr = sym_table; ptr != (symrec *) 0;
            ptr = (symrec *)ptr->next)
         if (strcmp (ptr->name,sym_name) == 0)
           return ptr;
       return 0;
     }

今度の関数yylexは、変数、数値、1文字の算術演算子を 認識する必要があります。 英字で始まり英数字からなる文字列は、記号表にどう書かれているかに応じて、 変数と関数のどちらとも認識されます。

文字列は、記号表を検索するためにgetsymに渡されます。 もし名前が表にあれば、その場所へのポインタと 名前の型(VARまたはFNCT)が、 yyparseに返されます。 名前がまだ表になければ、putsymを使って、 VARとして登録されます。 そして、ポインタと型(この場合には必ずVAR)が yyparseに返されます。

yylexの中で、数値と算術演算子の扱いに関する部分は、 変更する必要がありません。

     #include <ctype.h>
     yylex ()
     {
       int c;
     
       /* 空白を読み飛ばし、空白以外を得る         */
       while ((c = getchar ()) == ' ' || c == '\t');
     
       if (c == EOF)
         return 0;
     
       /* 数値を読む   */
       if (c == '.' || isdigit (c))
         {
           ungetc (c, stdin);
           scanf ("%lf", &yylval.val);
           return NUM;
         }
     
       /* 識別子を読む */
       if (isalpha (c))
         {
           symrec *s;
           static char *symbuf = 0;
           static int length = 0;
           int i;
     
           /* バッファの長さの初期値は40文字       */
           if (length == 0)
             length = 40, symbuf = (char *)malloc (length + 1);
     
           i = 0;
           do
             {
               /* あふれたのでバッファを大きくする */
               if (i == length)
                 {
                   length *= 2;
                   symbuf = (char *)realloc (symbuf, length + 1);
                 }
               /* 文字をバッファに変える           */
               symbuf[i++] = c;
               /* 次の文字を読む                   */
               c = getchar ();
             }
           while (c != EOF && isalnum (c));
     
           ungetc (c, stdin);
           symbuf[i] = '\0';
     
           s = getsym (symbuf);
           if (s == 0)
             s = putsym (symbuf, VAR);
           yylval.tptr = s;
           return s->type;
         }
     
       /* その他の文字は文字リテラルトークン       */
       return c;
     }

このプログラムは、強力かつ柔軟です。 新しい関数の追加は簡単です。 pieのようにあらかじめ定義された変数を追加するために プログラムを変更することは、簡単な仕事でしょう。