前: Mfcalc Rules, 上: Multi-function Calc
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を呼びます。
mainとinit_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は、登録すべきオブジェクトの
名前と型(VARやFNCT)を渡されます。
オブジェクトはリストの先頭にリンクされ、
オブジェクトへのポインタが返されます。
関数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;
}
このプログラムは、強力かつ柔軟です。
新しい関数の追加は簡単です。
piやeのようにあらかじめ定義された変数を追加するために
プログラムを変更することは、簡単な仕事でしょう。