Previous: Mfcalc Rules, Up: 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
のようにあらかじめ定義された変数を追加するために
プログラムを変更することは、簡単な仕事でしょう。