数値計算レポート(大石先生)
第5回

出題日: 7月 11日(水)
提出期限: 8月15日 (水)
提出日: 8月 15日 (水)
氏名:益子 理絵

Contents

1  問題内容
2  事前知識
    2.1  Flex(Lex)について
    2.2  Bison(Yacc)について
    2.3  yaccとlex
    2.4  その他
        2.4.1  GC(ガベージコレクタ)
        2.4.2  Gnuplot
3  実行環境
4  プログラムの作成
    4.1  bisonの機能を利用して電卓を作る
    4.2  前橋氏の電卓
    4.3  制御文であるfor文の追加
    4.4  べき乗の追加
    4.5  sin,cos,tanの追加
    4.6  gnuplot、appletの呼び出しで絵を描けるようにする
        4.6.1  gnuplotの呼び出し
        4.6.2  appletの呼び出し
    4.7  ガベージコレクタ(Boehm GC)の使用
5  ソースコード
    5.1  コード解説
        5.1.1  字句解析
        5.1.2  構文解析
        5.1.3  構文解析木の生成
        5.1.4  構文解析木の評価
        5.1.5  メモリ管理
    5.2  使用方法
    5.3  ソースコード
6  実行結果
7  考察
    7.1  インタプリタの機能一覧
    7.2  その他
8  感想
A  インタプリタ用ソースコード
    A.1  calc.l
    A.2  calc.y
    A.3  calc.h
    A.4  create.c
    A.5  eval.c
B  絵描きアプレット用ソースコード
    B.1  example1.html
    B.2  DrawTest.java

1  問題内容

今回の課題は「数値計算用インタプリタを作れ 」である。このインタプリタを記述 する言語は原則としてCとする。GUIなどにJavaを利用して,ポータビリティを持たせ ることは問題ない。一部C++なども利用してよい。

  1. bisonの機能を用いて,かけ算わり算を足し算引き算より優先させて計算する倍 精度浮動小数点数を用いた電卓を作れ(40点)。

    1. 実部,虚部がともにdoubleの複素数が扱える,電卓をつくれ。
      (以下との兼ね合 いがあるが+30点程度)。
    2. 行列(double)が扱えるようにする。ただし,加減乗算にはBLASの関数を用いる こと。
      (+30点程度)。
    3. 行列(複素数)が扱えるようにする。ただし,加減乗算にはBLASの関数を用い ること。
      (+30点程度)。
    4. 変数を扱えるようにする(メモリ電卓となる+10点)。
    5. sin,cosなどやべき乗もできるようにする(メモリ付き関数電卓となる+10点)。
  2. 制御文(if文やwhile文やfor文)を扱えるようにする。(+40点)
  3. ユーザが関数を定義できるようにする。(+40点)
    上の二つの問題を解くためには,bisonとflexのwebでは不十分となる。これらができるようになるための説明のあるwebを挙げておく。
    前橋和弥氏のページ ここの「電卓を作ってみよう」がある。
    (http://member.nifty.ne.jp/maebashi/programmer/c_yota/calc.html)参照
    
    
    
  4. ファイルも読み込めるようにするというのは前橋氏の電卓はすでにできる。スク リプトと関数定義を読み込めるようにしてみよう。(+30点)。
  5. オブジェクトが定義でき,演算子多重定義ができるようにするのも面白い。 Scilabのtyped listは演算子多重定義できるオブジェクトである(多分参考になるは ず)。MATLABのクラス定義の仕方も変わっていて面白い(中がみれないので仕様しか参 考にならない)。+50点
  6. 絵が書けるようになりたい。gnuplotを呼び出す仕組みにするのは簡単だろう。 ただ,これだけでは寂しい。Javaで絵を描くのはいいかもしれない。+50点。

2  事前知識

課題を解くにあたって、あまり予備知識が無かったので自分なり に必要であった個所を以下にまとめておくことにした。

2.1  Flex(Lex)について

Lexは、正規表現のという表記法に基づいて書かれたテキストファイルを渡すと、Cで書かれたソースコードの形に変換する字句解析器生成ツールである。字句解析とは入力された文字列から意味のある最小単位(トークン)を見つけ出すための処理である。そして、FlexはLexの上位互換であり、Lexで書かれた正しいソースをほとんど変更なくFlexでも処理することが出来る。Flexは、 Lexのより優れた再実装であり、 Lexと同様、パターンとアクションの記述情報を入力として受け取って、そのパターンにマッチする能力を持つCのスキャナに変換するもである。Flexの方がLexより効率的であるとしばしば言われている。
具体的には、Flexは記述情報を含むファイルを入力として受け取り、スキャナ機能を持つCのファイルに変換する。 Flexを起動するためのコマンド行は以下のようになる。
flex [-bcdfinpstvFILT8] [-C[efmF]] [-Sskeleton] [file ...]

一般的には、単に「flex」に続けて処理すべきファイル名を入力すればよい。ファイル名の末尾は「.l」とし、FlexもしくはLexの記述ファイルであることを示唆する。ファイル(filename.l)を読み込み、そこに記述されたパターンを認識するスキャナ機能を持つ「lex.yy.c」という名前のC言語ファイルを生成し、記述情報の中になんらかのエラーがあれば、 対応するエラー・メッセージをstderrに出力する。

2.2  Bison(Yacc)について

Yaccは、LALR(1)文脈自由文法の文法定義を、その文法を解析するためのCで書かれたプログラムに変換する、汎用の構文解析ツールである。構文解析とは、字句解析で切り出されたトークンの意味のある並びを見つけ出す処理である。BisonはYaccの上位互換であり、正しく書かれた全てのYacc文法を、ほとんど変更なくBisonで処理することができる。
Bisonの使用手順は次のようになる。

  1.  Bisonが認識できる形式で、文法を形式的に指定する。言語の各文法規則に対して、その規則のインスタンスが認識されたときに実行されるアクションを記述する。アクションは、C言語の文の並びで書きく。
  2.  入力を処理し、トークンを構文解析器に渡すために、字句解析器を書く。字句解析器は、Cで手作業で書いてもかまわない。 Lexを使って生成することも可能である。
  3.  Bisonが生成した構文解析器を呼び出す、制御関数を書く。
  4.  エラー報告関数を書く。
このソースプログラムを実行可能なプログラムにするためには、次の手順が必要である。

  1.  構文解析器を生成するために、Bisonを実行する。
  2.  Bisonが生成したソースプログラムとその他のソースプログラムを、コンパイルする。
  3.  オブジェクトファイルをリンクして、最終的なプログラムを得る。
Bisonを起動するためのコマンド行は以下のようになる。
bison filename.y

単に「bison」に続けて処理すべきファイル名を入力すればよい。オプションは、伝統的な一文字のオプションと、記憶しやすいオプション名の両方を受け付けるため、長いオプション名は「-」の代わりに、「--」で指定する。ファイル名の末尾は「.y」とし、YaccもしくはBisonの記述ファイルであることを示唆する慣例を使う。生成される構文解析器ファイルは、文法ファイル名の.y「.tab.c」に変えたものである。従って、「filename.y」からは、「filename.tab.c」を得る。

2.3  yaccとlex

字句解析器を生成するlexと、構文解析器を生成するyaccは、コンパイラを作るためのコンパイラでもあるので、コンパイラ・コンパイラと呼ばれることもあるが、UNIXのプログラム開発用のツールである。lexは正規表現、yaccはBNF記法に基づいて作られた一種のプログラミング言語を使って、字句解析器や構文解析器の動作を記述する。これらのソフトに、それぞれの表記法にしたがって書かれたテキストファイルを渡すと、字句解析プログラムや構文解析プログラムを、C言語で書かれたソースコードの形で生成してくれる。
yaccで作った構文解析器と、lexで作った字句解析器を連携することもできる。デフォルトで、lexが生成する字句解析器は、「yylex()」という名前の関数として生成される。yylex()はテキストファイルなどの入力ソースから文字列を読み込んでいって、任意のパターンにマッチしていることを確認すると、「トークン」を関数の戻り値として返す。(実際には、トークンの種類ごとに割り当てられた整数値として返す。)一方、yaccがデフォルトで生成する構文解析器は「yyparse()」という名前の関数である。yyparse()は「yylex()」という名の関数を繰り返し呼び出して、トークンを得ながら構文解析処理を進めていく。その結果、構文解析器と字句解析器が連携する形となってくるわけである。
簡潔に整理すると次のようであるということがわかった。

2.4  その他

2.4.1  GC(ガベージコレクタ)

ガベージコレクタとは、メモリ管理機構である。Cなどでmalloc やnew で確保したメモリを監視し使われなくなった時点で自動的に開放する。以前ではLispやsmalltalk、最近ではJavaに搭載されている。GCの利点は、使われなくなったオブジェクトを自動的に開放してくれることによってdelete し忘れの心配をする必要がなくなる。Boehm GC(参考文献参照)は、CとC++で動作する。Boehm GC はC/C++から利用可能なGCライブラリであり、ダウンロードできる.このライブラリは誰でも無料で利用できる。Boehm GCが提供するのは、基本的にGC_malloc(),GC_malloc_atomic(),GC_realloc() という3つの関数である。これらは、C のライブラリの malloc() と realloc()に対応していて、使い方も同じだ。GC_free() という関数も提供されるが、なまじGC_free()を使うとバグの元なので,使わない方が良いらしい。GC_malloc()とGC_malloc_atomic()は基本的には同じだが、GC_malloc_atomic()は内部にポインタを含まないデータの確保に使う.例えば文字列データを確保する場合などにGC_malloc_atomic()を使う.この場合にGC_malloc()を使ってもちゃんと動くのだが、GC_malloc_atomic()を使った方がGCの効率が上がる.

2.4.2  Gnuplot

gnuplotとは、対話的にグラフを書くプログラムである。グラフは、関数(x2やsin(x)など)の他に、実験データーなどの離散的な数値も描くことができる。2次元と3次元のグラフが描ける。そして、CやBASIC、fortranなどの言語で使用できる式をそのまま入力できることも特徴である。複素数の計算もでき、三角関数からべっセル関数まで沢山の関数が用意されている上に自分で関数の定義もできるので、関数電卓としても使用できる。gnuplotを使うには、コマンドプロンプトが出ている状態で、gnuplotと打ち込めばよい。メッセージが出て、プロンプトがgnuplot>になったら、gnuplotが立ち上がっている。gnuplotを抜け出すにはgnuplot> quit や、gnuplot> qのように打ち込めばよい。

3  実行環境

実行環境は以下のとおりである。
  <第四端末室>
     OS    Red Hat Linux release 6.2 (Zoot)
     CPU    Pentium III
  クロック      800MHz
   メモリ     256MB

  <自宅>
     OS   windows98+cygwin
     CPU   Intel Celeron
  クロック   500MHz
   メモリ   64MB

学校が閉鎖されてしまったので、途中からは家のcygwinで実行した。

4  プログラムの作成

4.1  bisonの機能を利用して電卓を作る

まずは、bisonのホームページを参考にしてプログラムを作ってみた。ここでは、flexを使用せずにごりごり字句解析を行う。これに関しては、結構短いプログラムなので、ここにソースを載せながら解説する。
 <calc.y>
%{         <宣言部>
#include <math.h>   数学関数使用のため
#include "calc.h"   symrec定義のため
%}
%union {        複数の意味値の形式があるためunionにする
  double     val;
  symrec  *tptr;
}
%token <val>  NUM    返り値としてdoubleを取る
%token <tptr> VAR FNCT  返り値としてsymrecをとる
%type  <val>  exp    同非終端記号expはdouble
%right '='       = が複数あった場合は右から解釈
%left '-' '+'      左から解釈
%left '*' '/'      同上
%left NEG        同上
%right '^'        べきは右から解釈
%%         <ルール部>
input:   /* blank */
        | input line
;            この記述でlineが0個以上続くことが表せる
line:          そのlineは何かというと、
          '\n'      改行コードか、もしくは
          | exp '\n' { printf ("\t%.10g\n", $1); } exp+改行か、
| error '\n' { yyerrok; } エラーである。
;
exp:      NUM { $$ = $1; }   double型数値の場合にはそれを返す
| VAR { $$ = $1->value.var; }  以下同様に文法記述とアクション
| VAR '=' exp { $$ = $3; $1->value.var = $3; }
| FNCT '(' exp ')' { $$ = (*($1->value.fnctptr))($3); }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp  %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
;
%%
#include <stdio.h> <プログラム部(省略して別プログラムにすることも可能)>
main ()
{
  init_table ();
  yyparse ();
}
yyerror (s)
     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
};   記号表 symrecのリスト
symrec *sym_table = (symrec *)0;
init_table (){      math関数を記号表に登録する
  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;
    }
}
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;
}
#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;
      if (length == 0)     バッファの長さの初期値は40文字
        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;
}
 <calc.h>
struct symrec  記号表のリンクを表すデータ型
{
  char *name;  記号の名前を入れる
  int type;   記号の種類を入れる(varかnameか)
  union {
    double var;      varの値
    double (*fnctptr)();  FNCTの値
  } value;
  struct symrec *next;   次の項目へのリンク
};
typedef struct symrec symrec;
extern symrec *sym_table;  外部変数symrec構造体のリンクである記号表
symrec *putsym ();
symrec *getsym ();

実行結果は以下のようになる。
<コマンドライン>
$ bison -dv mfcalc.y     これでmfcalc.tab.cが生成する
$ cc mfcalc.tab.c -o mfcalc  そのcプログラムをmfcalcにコンパイル
$ ./mfcalc          mfcalc.exeの実行
3*2
        6
2.4*6            double型である
        14.4
3/5
        0.6
1+2*3^4           加減より乗除、乗除よりべきを優先
        163
2^3^2            べきは右から優先
        512
a              変数は最初は全て0
        0
a = 2            2を代入
        2
a
        2
a * 3            メモリ電卓
        6
a ^ 2
        4
sin(a)           sin関数も使える
        0.9092974268
電卓終了はCtrl−dを入力する

ひとまずbisonの使用方法などを確認することが出来た。 終端記号に対するBisonの表現は、 トークン型である。通常、非終端記号からこれらを区別するために、大文字で書く。トークンとは、言語の、記法的で、文法的に分割できない単位である。また、終端記号errorは、エラーからの回復処理のために予約されている。これを使用して登録していない文字の検出に使用できる。また、bisonのプログラムは、宣言部、ルール部、プログラム部からなる。宣言部とプログラム部は省略することが出来る。また、コメントはCと同様に記述することが出来る。bisonの機能が注目しやすいのはルール部である。ここの記述の仕方は、構文規則にのっとる。入力すべき構文の構成を構文規則としてきじゅつする。構文規則の形式は、コンパイラ技術でよく使用される文脈自由文法に基づいていて、基本的に以下のようになっている。
  a : body ;

aは構文規則の名前であり、bodyはその構文規則の構成を記述する。構文規則の名前、すなわちaにあたるところは、非終端記号であり、bodyは非終端または終端記号のどちらでもよい。終端記号は実際に入力される個々の単語である。上のプログラムでは、ルール部に記述されている名前input、line、expが非終端記号である。それ以外の名前がトークンであり、宣言部の%tokenを使用して全て宣言しておかなければならない。ここで、ルール部にある構文規則が認識されたとき、その構文規則に対応して、ある処理を実行することが出来る。この処理をアクションと呼ぶ。アクションは、構文規則中に”{”と”}”で囲んで記述する。アクションは基本的に以下の形式で構文規則の最後に記述することになっている。
  a : body {action} ;

また、複数のパターンがある場合には以下のようになる。
  a : body1 {action1}
    | body2 {action2}
    ;

アクションは特に必要が無ければ記述しなくてもよく、アクションの中の処理はC言語で記述する。しかし、このアクションの中では、Cとは別の拡張した書き方がサポートされている。1つは構文規則感のコミュニケーションをとるために使用する擬似変数というもんである。これは、対応する構文規則のトークンおよび非終端記号のそれぞれの値を参照する変数であり、$で始まる変数名を持つ。構文規則の名前である非終端記号の場合には$$であり、:の右辺に対しては、左の記号から順に、$1,$2,$3,・・・と対応する。$$は、別の構文規則に値を渡すために使用する。bisonプログラムがyylvalで返した値は、そのトークンに対応する$1,$2,・・・で参照することができる。このプログラムでは、
exp :
      NUM { $$ = $1; }
  | VAR { $$ = $1->value.var; }
    | VAR '=' exp { $$ = $3; $1->value.var = $3; }
    | FNCT '(' exp ')' { $$ = (*($1->value.fnctptr))($3); }
      ・・・・・・
    ;

を見ても分かるように、アクションの部分に擬似変数を使用している。bisonのプログラムで参照するyylvalのデータ型の基底値はintである。これを変えたい場合、例えばlong型にしたい場合には、宣言部の%{と%}の間にdefine YYSTYPE longと記述すればよい。しかし、この電卓プログラムのように複数の型を持ちたい場合には、%unionで意味値がもちうるデータ型の集合を宣言する必要がある。今回の場合には、以下のようになっていた。
%union {
  double     val;
  symrec  *tptr;
}

これでdoubleとsymrec(別で定義)の意味値をとることになる。tokenの個所で< val>などをオプション?のように追加して、トークンの意味値を決定仕分けることが出来るようになる。一方、非終端記号に対しては、%typeで意味値の型を宣言する。また、宣言のところでは、%rightとすると、右結合的な終端記号を宣言することになっている。逆もまた然りである。単に%tokenとする場合には、優先順位と結合性を指定せずに、終端記号を宣言する。プログラムの動作としては、まずinputという形式の文法を受理するため、inputをどんどん展開していく。正確には、トークンを一つ先読みすることによって、入力文字列の任意の部分を解析できるのであるが。inputは、
input :   /* blank */
        | input line
;

であるので、何もない、もしくは、input lineであることからlineが0個以上あるということを表している。そうして、何か入力があった場合には、lineをみていく。lineは、
line  : '\n'
       | exp '\n' { printf ("\t%.10g\n", $1); }
       | error '\n' { yyerrok; }
       ;

であるので、改行コード、exp+改行コードか、もしくはエラーであるということである。そして、また非終端記号のexpはどうなるのか・・・といった具合に見ていくのである。 プログラム部に関しては、字句解析のプログラムであるyylexの決定と、その他に関する補助的な関数の定義をしているが、ここでは、bisonに注目したので解説は省略する。実行結果を見てみると、期待通りに動いていることが分かる。bisonがcプログラムであるmfcalc.tab.cを生成したということも体感できた。

4.2  前橋氏の電卓

次に上の電卓を自分で展開して、関数定義やif文の追加などを行おうかと思ったが、少しきつそうだった(flexを使いたいというのもあった)ので、前橋氏のホームページからダウンロードしたものを実行してみた。ホームページの解説通りに、解凍し、展開したディレクトリ(mycalc)から、サブディレクトリ(memory)と(debug)でそれぞれmakeをかけてみた。第六端末室でやってみたのだが、ここでは、何故かエラーが発生した。内容を書き留めておかなかったので、何のエラーだったか忘れたが、ccをgccに直してやることで一応コンパイルできたようだった。その後、mycalcで再びmakeをかけたのだが、やはり実行できなかった。エラー内容は以下のようであった。
g99p1275@iris28[103] pwd
/homes/Solaris/home02/g99p1275/Calculator/mycalc
g99p1275@iris28[107] make
gcc lex.yy.o y.tab.o create.o eval.o interface.o util.o error.o main.o
 ./memory/mem.o ./debug/dbg.o -o mycalc -lreadline -lfl -lm
Undefined                       first referenced
 symbol                             in file
yyparse                             interface.o
yylval                              lex.yy.o
tgetnum                             /usr/local/lib/libreadline.a(terminal.o)
tgetstr                             /usr/local/lib/libreadline.a(terminal.o)
tgoto                               /usr/local/lib/libreadline.a(display.o)
tputs                               /usr/local/lib/libreadline.a(display.o)
tgetent                             /usr/local/lib/libreadline.a(terminal.o)
tgetflag                            /usr/local/lib/libreadline.a(terminal.o)
ld: fatal: Symbol referencing errors. No output written to mycalc
collect2: ld returned 1 exit status
make: *** [mycalc] Error 1

なんか未定義のシンボルがあって、最初に参照しているファイルはこれこれだといっているのはわかったのだが、/usr/local/lib/libreadline.aのファイルを見てみてももちろん機械語であったので、どうしようもなかった。その他、Makefileの内容を替えたりしてみたが、無駄だった。fatalなエラーなんて言われたのでややむかついて、第四端末室(red hat)でやってみようと思い、最初からやり直したら、あっけなくできた。原因を色々調べてみたが、頭がついていけずよくわからなかった。Solarisのなんかだとうまく動かないというようなことを書いてあるホームページもあった。とりあえず、以下が第四端末室でメイクした際のコマンドラインである。
[g99p1275@eris028 ~/dir1]$ ls
mycalc  mycalc.tgz
[g99p1275@eris028 ~/dir1]$ cd mycalc
[g99p1275@eris028 mycalc]$ cd memory
[g99p1275@eris028 memory]$ make
make: `mem.o' is up to date.
[g99p1275@eris028 memory]$ cd ..
[g99p1275@eris028 mycalc]$ cd debug
[g99p1275@eris028 debug]$ make
make: `dbg.o' is up to date.
[g99p1275@eris028 debug]$ cd ..
[g99p1275@eris028 mycalc]$ make
make: `mycalc' is up to date.

これで取りあえず、動く電卓はできた。以下が実行結果である。

  1. int型、double型の数値の加減乗除(乗除、括弧優先機能付き)等の計算
    g99p1275@eris069[106] ./mycalc
    > 1 + 3 * 5 ;       /* int型整数の加減乗除 */
    >>16
    >1-3*4+2;
    >>-9
    >5.3-1.9+2*3.3;      /* double型整数の加減乗除 */
    >>10.000000
    >3*(5 + 2);        /* 括弧を用いた計算 */
    >>21
    >100/(3.5-1);
    >>40.000000
    >19/3;          /* int型とdouble型では"/"(割算)の定義が違う */
    >>6
    >19.1/3;
    >>6.366667        /* mod演算子"%"の使用もできる */
    >19%3;
    >>1
    >8 % 3 ;
    >>2
    
    
  2. 変数の使用
    >a = 3;          /* 変数aに3を定義する */
    >>3
    >50*a;          /* 変数aを値として使用できる */
    >>150
    >b = -10.5;        /* double型の変数も可能 */
    >>-10.500000
    >c = a * b;       /* 新しい変数に、変数での計算値を代入可能 */
    >>-31.500000
    >c;
    >>-31.500000
    
    
  3. 制御文の使用
    >a;           /* 先に変数を定義しておく */
    >>3           /* a=3 , b=1とする */
    >b = 1;
    >>1
    >if a==3 { b = b + 3 } ; /* if文の使用(else無し、条件真) */
    >>4
    >b;
    >>4
    >if a!=3 { b = b - 3 } ; /* if文の使用(else無し、条件偽) */
    Segmentation fault
    >a = 3;
    >>3
    >b = 4;
    >>4
    >if a==2 { b = b - 3 } else { b = b + 3 }; /*if文の使用(else有り、条件偽)*/
    >>7
    >b;
    >>7
    >a;
    >>3
    >while a <= 6 {b = b + 1 , a = a + 1} ; /* while文の使用 */
    >>7
    >a;
    >>7
    >b;
    >>11
    
    
  4. 関数定義
    ユーザーが関数を定義することも出来る。
    >define add(d, e) { d + e }  /* 関数addの定義 */
    >add(3,5);          /* 関数addの実行 */
    >>8
    >define sub(f, g) { f - g }  /* 関数subの定義 */
    >sub(10.3, 2);        /* 関数subの実行 */
    >>8.300000
    
    
    ここでは、関数内の変数と、関数外の変数を区別することができる。グローバル変数とローカル変数の区別ができるのである。
  5. ファイルの読み込み
    ファイルは一行コマンドのみ読み込むことが出来る。
    <実行例1>
    ・test.txtの内容
      a = 2;
    ・実行結果
      $ ./mycalc < test.txt
      >a = 2;
      >>2
      >
    一行だけの命令はちゃんと読み込んで実行いる。
    <実行例2>
    ・test.txtの内容
      a = 2;
      a * 2;
    ・実行結果
      $ ./mycalc < test.txt
      >a = 2;
      >>2
      >
    ここで、二行目のa*2;が読み込まれていないので、改行をなくしてみた。
    <実行例3>
    ・test.txtの内容
      a = 2;a * 2;
    ・実行結果
      $ ./mycalc < test.txt
      >a = 2; a * 2;
      >>2
      >>4
      >
    となる。改行を入力しなければ複数の命令もこなす。
    つまり改行コードを認識するまで命令の入力を待っている。
    
    
いずれの場合も、正しく動いていることが分かる。ソースの解説は、このプログラムの最終的な拡張が終わった後にまとめてします。

4.3  制御文であるfor文の追加

まずは、前橋氏のソースを理解するためとbisonやflexになれるために、for文の追加を行ってみた。最初のソースからの変更部分は以下の通りである。

  1. calc.l
    ルール部の"while"の記述の次のラインに、
    <INITIAL>"for"   return FOR;
    
    
    を追加した。これによって、文字"for"を認識し、FORというコードをアクションとして返す。
  2. calc.y
    宣言部の、
    %token DEFINE IF ELSE WHILE LP RP LC RC SEMICOLON COMMA ASSIGN
            EQ NE GT GE LT LE ADD SUB MUL DIV MOD
        ・・・・・・
    %type   <expression> expression expression_list
            equality_expression relational_expression
            additive_expression multiplicative_expression
            unary_expression postfix_expression primary_expression
            if_expression while_expression
    
    
    の個所に、%tokenにFORを、%typeにfor_expression追加して以下のようにする。
    %token DEFINE IF ELSE WHILE FOR LP RP LC RC SEMICOLON COMMA ASSIGN
            EQ NE GT GE LT LE ADD SUB MUL DIV MOD
        ・・・・・・
    %type   <expression> expression expression_list
            equality_expression relational_expression
            additive_expression multiplicative_expression
            unary_expression postfix_expression primary_expression
            if_expression while_expression for_expression
    
    
    また、ルール部において、一次式(primary_expression)で、for文への書き換えも起こるように追加する。
    primary_expression(一次式は次のどれかである)
            : IDENTIFIER LP expression_list RP
           アクション
            | IDENTIFIER LP RP
            アクション
            | if_expression
            | while_expression
            | for_expression        <------ここにfor文を追加
            | LP expression RP
           アクション
            | IDENTIFIER
           アクション
            | INT_LITERAL
            | DOUBLE_LITERAL
    
    
    それからfor_expressionの定義をする。
    for_expression
            : FOR LP expression SEMICOLON expression SEMICOLON expression RP
                                                     LC expression_list RC
            {
                $$ = clc_create_for_expression($3, $5, $7, $10);
            } /* この$3,$5の番号は上の非終端記号(expression等)の順番の番号 */
            ;
    
    
    for文の定義は出来たが、関数がまだ具体的に決まっていないのでcreate.cで、for文の内容について実装する。(命令を生成する。)
  3. create.c
    calc.yのアクションで呼ばれる関数の内容を決定する。for文を作成する関数は以下のようになる。この内容をcreate.cに追加する。これで、解析木が構築できる。
    Expression *
    clc_create_for_expression(Expression *expression1,
                              Expression *condition,
                              Expression *expression2,
                              Expression *expression)
    {
        Expression *exp;
    
        exp = clc_alloc_expression(FOR_EXPRESSION);
        exp->u.for_expression.expression1 = expression1;
        exp->u.for_expression.condition = condition;
        exp->u.for_expression.expression2 = expression2;
        exp->u.for_expression.expression_list = expression;
    
        return exp;
    }
    
    
    ここで、ヘッダファイルの定義を変えておく。
  4. calc.h
    まずは、ExpressionTypeにFOR_EXPRESSIONを追加し、Expression_tagにも次のように追加する。
    typedef enum {
    ・・・・・・
        IF_EXPRESSION,
        WHILE_EXPRESSION,
        FOR_EXPRESSION,
    ・・・・・・
    } ExpressionType;
    
    struct Expression_tag {
        ExpressionType type;
        union {
        ・・・・・・
            WhileExpression         while_expression;
            ForExpression           for_expression;
        } u;
    };
    これで、\verb|FOR_EXPRESSION|を場合わけできるようになる。
    また、create.cのために以下のように宣言を加える。
    /* create.c */
    Expression *clc_create_for_expression(Expression *expression1,
                                          Expression *condition,
                                          Expression *expression2,
                                          Expression *expression);
    
    
    以上でfor文のためのヘッダファイルの定義は整った。次は解析木を生成するプログラムの編集である。
  5. eval.c
    create.cで構築した解析木の評価をおこなう。まず最初に呼ばれるeval_expressionに、FOR_EXPRESSIONの場合の行動を追加する。以下では、eval_for_expressionを呼んでいる。
    static Value
    eval_expression(LocalEnvironment *env, Expression *expr)
    {
        Value       v;
        switch (expr->type) {
       ・・・・・・・・・・・・・・・・・・・・・・・・・・・・
          case FOR_EXPRESSION:
            v = eval_for_expression(env,
                                      expr->u.for_expression.expression1,
                                      expr->u.for_expression.condition,
                                      expr->u.for_expression.expression2,
                                      expr->u.for_expression.expression_list);
            break;
          case    ・・・・・・・・・・・・・・・・・・・・・・・
        }
        return v;
    }
    続いて、\verb|eval_for_expression|関数を定める。
    static Value
    eval_for_expression(LocalEnvironment *env,
                        Expression *expression1, Expression *condition,
                        Expression *expression2, Expression *expression_list)
    {
        Value       condition_val;
        Value       result;
    
        result = eval_expression(env, expression1);
    
        while (1) {
            condition_val = eval_expression(env, condition);
            if (condition_val.type != INT_VALUE) {
                clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
            }
            if (!condition_val.u.int_value)
                break;
    
            result = eval_expression(env, expression_list);
            result = eval_expression(env, expression2);
        }
        return result;
    }
    
    
    となる。for文は、for(文1;条件式;文2;)実行文と定義した。まず、for文の第一引数の文を実行する。すなわち、引き数の初期化である。これが、result = eval_expression(env, expression1);の部分である。次に、無限ループで、実行文を行うのだが、その際、ループから抜ける条件として、条件式が偽だった場合のif文を記述する。これが、if (!condition_val.u.int_value)  break;である。最後に返す結果としては、文2を実行した結果を返すことにした。この他に、double型とint型での計算の際にも少々追加しておく。
    static int
    eval_binary_int(ExpressionType operator, int left, int right)
    {
      ・・・・・・
        switch (operator) {
       ・・・・・・
          case FOR_EXPRESSION:      /* FALLTHRU */
          case FUNCTION_CALL_EXPRESSION:    /* FALLTHRU */
          case EXPRESSION_TYPE_NUM: /* FALLTHRU */
          default:
            DBG_assert(0, ("bad case...%d", operator));
        }
        return result;
    }
    static void
    eval_binary_double(ExpressionType operator, double left, double right,
                       Value *result)
    {
      ・・・・・・
        switch (operator) {
         ・・・・・・・
          case FOR_EXPRESSION:      /* FALLTHRU */
          case FUNCTION_CALL_EXPRESSION:    /* FALLTHRU */
          case EXPRESSION_TYPE_NUM: /* FALLTHRU */
          default:
            DBG_assert(0, ("bad default...%d", operator));
        }
    }
    
    
    double型の演算を行わないif文やwhile文そしてfor文などの場合と記述以外の場合には動作を行わず、DBG_assert(0, ("bad default...%d", operator));関数を呼ぶ。
以上による追加変更で、for文を使用することができるようになる。以下が実行結果の例である。
>b = 11;
>>11
>b;
>>11
>for( a = 1 ; a < 5 ; a = a + 1 ) { b = b + 1 } ;
>>5
>b;
>>15
>a;
>>5

実行結果からfor文の拡張が正しく行えたといえる。

4.4  べき乗の追加

今度は、いちいち、for文の場合のようにどこを追加変更したか書くのではなく、大事なポイントだけ記述していく。べき乗の演算に関しても一番重要な変更点のみ記す。 べき乗の演算は、今まであった×÷%の演算よりも結びつきが強い。しかし、単項演算子の−よりは結びつきは弱い。よって、calc.yでの文法を以下のようにすればよい。 すなわち、
multiplicative_expression
        : unary_expression
        | multiplicative_expression MUL unary_expression
        {
            $$ = clc_create_binary_expression(MUL_EXPRESSION, $1, $3);
        }
        | multiplicative_expression DIV unary_expression
        {
            $$ = clc_create_binary_expression(DIV_EXPRESSION, $1, $3);
        }
        | multiplicative_expression MOD unary_expression
        {
            $$ = clc_create_binary_expression(MOD_EXPRESSION, $1, $3);
        }
        ;
unary_expression
        : postfix_expression
        | SUB unary_expression
        {
            $$ = clc_create_minus_expression($2);
        }
        ;

であった部分を、
multiplicative_expression
        : exp_expression
        | multiplicative_expression MUL exp_expression
        {
            $$ = clc_create_binary_expression(MUL_EXPRESSION, $1, $3);
        }
        | multiplicative_expression DIV exp_expression
        {
            $$ = clc_create_binary_expression(DIV_EXPRESSION, $1, $3);
        }
        | multiplicative_expression MOD exp_expression
        {
            $$ = clc_create_binary_expression(MOD_EXPRESSION, $1, $3);
        }
        ;
exp_expression
        : unary_expression
        | unary_expression POWOP exp_expression
        {
            $$  = clc_create_binary_expression(EXP_EXPRESSION,$1,$3);
        }
        ;
unary_expression
        : postfix_expression
        | SUB unary_expression
        {
            $$ = clc_create_minus_expression($2);
        }
        ;

とするのである。multiplicative_expression(掛け算系の式)を展開するとexp_expression(べき乗式)、そして、exp_expressionを展開するとunary_expression(単項演算系の式)そして・・・と定義することによって、×÷%^−(単項)−+の演算子があったときに、−(単項)、(べき乗)、×÷%−+(二項)の順で演算を優先させる。また、同じ演算子が複数続いたときには、加減乗除の場合は左側から順に計算するが、べき乗の場合には、右側から順に計算していくようになる。これに関しては、
exp_expression
    : unary_expression (←ココの記述と)
    | unary_expression (←コレが同じである) POWOP exp_expression

の書き方に分かるとおりである。その他にもちろん、calc.lでのtokenの切り出しやcalc.yでのtokenの返り値などの定義は追加しておく。eval.cの変更も必要である。ただ、ここでは既存の関数clc_create_binary_expression();を使用しているのでcreate.cの変更は必要ない。 実行結果は以下のようになる。
>2 ^ 3;     int型の計算
>>8
>2.5 ^ 2;    double型の計算
>>6.250000
>2^3^2;     べき乗の演算子が複数あれば右から計算を行う
>>512
>1.8 * 2 ^ 2;  複数の演算子があってもべきを優先
>>7.200000
>1 + 1.5 ^ 2;  同上
>>3.250000
>1.4 + 1.5 ^ 2 * 3; 同上
>>8.150000
>-2 ^ 2;    単項演算子−があってもOK
>>4
>2.0 ^ -2;   同上
>>0.250000

正しく拡張されていることがわかる。

4.5  sin,cos,tanの追加

calc.lでの切り出しは、簡単であるので省略する。重要なのは、calc.yである。calc.yにおいてsinなどの値を計算した結果は、整数やdouble型実数と同じ扱いをしてほしいので、文法では同じ場所に追加してやればよい。 すなわち、
primary_expression
        : ・・・・・・
        | INT_LITERAL
        | DOUBLE_LITERAL
        ;

だったところにsin等を追加して、
primary_expression
        : ・・・・・・
        | SIN LP expression  RP
        {
            $$ = clc_create_binary_expression(SIN_EXPRESSION, $3, $3);
        }
        | COS LP expression RP
        {
            $$ = clc_create_binary_expression(COS_EXPRESSION, $3, $3);
        }
        | TAN LP expression RP
        {
            $$ = clc_create_binary_expression(TAN_EXPRESSION, $3, $3);
        }
        | INT_LITERAL
        | DOUBLE_LITERAL
        ;

と追加すればよい。sin等の形式は、sin(式)とした。ここで、sin等のため関数を設計するのはめんどくさかったので、引数を二つ持つ既存のclc_create_binary_expression();をそのまま使った。この関数は演算用の関数なので引数を二つ持っているのだが、今回は実際には片方の引数は使用しない。Cに慣れてないのでこれでだいぶ時間が節約できたが、ちょっとズルイなあとは思う・・。けど動いたからよしとする。他に、tokenの追加などを行った。sinなどの計算は数学関数を使用した。(eval.cにおいてである。)ここでも、create.cの変更は必要ない。実行結果は以下のようになった。
>sin(2);   int型を引数でとってもdouble型で返す
>>0.909297
>cos(3.1415); コサインもOK
>>-1.000000
>tan(0.7856); タンジェントもOK
>>1.000404
>pi = 3.14159; 円周率を変数piとして設定すると・・
>>3.14159
>sin(pi);   変数も引数として持つことが出来る。(以下同様)
>>0.000000
>cos(pi);
>>-1.000000
>tan(pi);
>>-0.000000
>sin(2) * cos(3.1415); sin等をを含めた×などの演算も可能
>>-0.909297
>2 * cos(3.1415);
>>-2.000000
>sin(2) ^ 2;      べき乗の演算も可能
>>0.826822

正しく拡張された。拡張内容についてはしっかり理解していたつもりだった。しかし実は、変数も引数として持つことが出来るというところが何度やってもエラーになってしまっていた。単純ミスだったのだが、これを発見するのにかなり時間を費やしてしまった。 最終的に動いてとてもうれしかった。

4.6  gnuplot、appletの呼び出しで絵を描けるようにする

4.6.1  gnuplotの呼び出し

まずは、calc.lのルール部に
<INITIAL>"gnuplot"      return GNUPLOT;

を追加する。gnuplotの字句を切り出す。次に、calc.yにおいて、
%token  GNUPLOT

を追加する。GNUPLOTの返り値は必要ないのでココに追加した。そして、同様にcalc.yにおいて、
translation_unit
        : definition_or_expression
        | translation_unit definition_or_expression
        ;
definition_or_expression
        : function_definition
        | GNUPLOT SEMICOLON    ←ココに追加
        {
            system("wgnupl32");
        }
    ・・・・・・
        ;

とする。インタプリタの起動状態でgnuplot;と入力すると、これで、system呼び出しでgnuplotが起動する。cygwinではwgnupl32でgnuplotが立ち上がるため、system("wgnupl32");としたが、学校でやるならここをsystem("gnuplot");とすればよいはずである。cygwinでgnuplotを使用する設定にはいささか苦労した。実行結果は、gnuplotが新しいwindowで起動するため直接はここには記せないがそのwindow内での実行例を示す。
g99p1275@eris068[112] ./mycalc
>gnuplot;
ここで新しいwindowが立ち上がる。
gnuplot> print {1,2}**2
print {1,2}**2
{-3.0, 4.0}
gnuplot> print abs({2,4})   絶対値の計算
print abs({2,4})
4.47213595499958
gnuplot> print sin(2.0/3.0*pi) sinなども計算可能
print sin(2.0/3.0*pi)
0.866025403784439
gnuplot> f(x)=2*x+1 関数定義
gnuplot> print f(2)
5
gnuplot> plot x   グラフが表示される
gnuplot> plot x**2 グラフが表示される
gnuplot> quit
ここでgnuplotのwindowが消えてインタプリタに戻る。
>

グラフもちゃんと新しいwindowがさらに立ち上がって表示された。gnuplotを終了したあとにはまた、インタプリタとしてそのまま命令を実行することが出来る。すなわち起動状態のままであり、繰り返し実行することが可能である。

4.6.2  appletの呼び出し

javaで絵を描くということは、関数をplotすることなのかなと思ったが、それはgnuplotでできるのでappletで絵を描くことができるようにしてみた。まずは、絵を描くjavaプログラムを作成した。これは色々ホームページを参考にして書いた。ファイル名は、DrawTest.javaでレポートの最後に付加しています。このプログラムに関しては、レポートとは直接関係ないので解説は省略する。とりあえず、appletviewer example1.htmlでこのプログラムは動くようになった。絵を描きたいときには、draw;と打ち込むとこのアプレットが起動するように設定する。これに関しては、gnuplotとほぼ同じやり方である。calc.lについての拡張は省略する。calc.yについての主な変更部分は以下の通りである。
definition_or_expression
        : function_definition
        | GNUPLOT SEMICOLON  ←gnuplotの時の拡張
        {
            system("gnuplot");
        }
        | DRAW SEMICOLON   ←ココに追加
        {
            system("appletviewer example1.html");
        }
    ・・・・・・

アプレットは起動され、その後終了すると再びインタプリタの起動状態に戻る。アプレットを貼り付けるのはしないが正しく動く。この場合、example1.html、DrawTest.javaをコンパイルして作成されるDrawTest.class、DrawControls.class、DrawPanel.classの三つのクラスをこのインタプリタと同じディレクトリにおいておく必要がある。

4.7  ガベージコレクタ(Boehm GC)の使用

まずは、http://www.hpl.hp.com/personal/Hans_Boehm/gc/からgc.tar.gzをダウンロードした。それを解凍してmakeするまでは問題なかった。前橋氏のプログラムでは、memory.cでメモリ管理を行っている。Boehm GCが提供するのは、基本的にGC_malloc(),GC_malloc_atomic(),GC_realloc()という3つの関数であり、これらは,C のライブラリの malloc() と realloc() に対応している。使い方も同じであるため、memory.cの中のalloc()realloc()を使用している部分を、それぞれgcライブラリのGC_alloc()GC_ralloc()に変えればよい。ヘッダにはgc.hを指定する。(実際にはgc.hのある場所を指定)。そうして、memoryディレクトリにおいては、問題なくコンパイルが完了したのだが、この上のディレクトリでコンパイルした時には、GC_alloc()GC_ralloc()が未定義だと言われてエラーになってしまった。cygwinではこのガベージコレクタを使用する際にはなんらかの変更が必要なのかと思って、ホームページなどを調べてみたのだが、時間が無かったので解決することが出来なかった。Boehm GCの使用方法はあっているはずなので、gccのコンパイラでの設定が必要なのだと思われるので、これはこれから解決していきたい。

5  ソースコード

5.1  コード解説

bisonのみを用いて作成したmfcalcに関しては先にコードと共に触れたので省略する。ここでは、拡張した前橋氏のソースコードについて解説する。基本的な流れとしては、calc.lで字句解析を行い、それをもとに、calc.yで構文解析を行う。構文解析を行うために解析木を生成するプログラムcreate.cがあり、その解析木の評価を行うプログラムとしてeval.cがある。その他は、定義用のヘッダファイルや、メモリ管理やデバッグ用のプログラムである。また、拡張していく際に細々した部分に関しては、触れているので大まかに解説することにする。実際コンパイルの際には、以上の流れが行われている。メモリ管理等のプログラムを生成したあとで、
$ make
bison --yacc -dv calc.y
flex calc.l
gcc -c -g lex.yy.c
gcc -c -g y.tab.c
gcc -c -g -Wall create.c
gcc -c -g -Wall eval.c
gcc -c -g -Wall interface.c
gcc -c -g -Wall util.c
gcc -c -g -Wall error.c
gcc lex.yy.o y.tab.o create.o eval.o interface.o util.o error.o main.o
  ./memory/mem.o ./debug/dbg.o -o mycalc -lreadline -lfl -lm

とする。この流れでだいたい分かる。flexよりもbisonのコンパイルを先に行うのは、そのほうがエラーがないということなのでそうしたのである。次にソースの解説に移る。

5.1.1  字句解析

まず、calc.lである。calc.lは、字句解析を行うflexプログラムである。flexが読み込むflexプログラムは三つの部分から構成される。それぞれの部分は、定義部、ルール部、およびサブルーチン部からなる。これらは二つの%%によって区切られる。また、定義部とサブルーチン部は省略することが出来る。flexで重要なのはパターンである。パターンはflexプログラムの中核をなすものであり、認識すべき字句の形式を記述する。パターンは行の先頭から記述する必要がある。また、パターンを正規表現とも呼ぶ。パターン記述の際には、様々なオペレータが提供されているのでそれを使用してよい。オペレータとして使用できる文字は以下のようになっている。
  " \ [ ] ^ - ? . * + | ( ) $ / { } % < >

これらの意味は一般的な正規表現の意味とほぼ同じであるのが多い。もし、これらのオペレータを文字として使用したい場合には、"または\を使用してやればよい。プログラムを見ていくと、最初の%{ から %}は、Cのコードを記述しているが、ここのCのコードは、lexにより生成されるCのコードにもそのまま入ってくる。その後の%startは、スタート状態を宣言している。ここでは、comemntがスタート状態である。そして、その次に%% %%で区切られたのがルール部である。<INITIAL>とあるのは、デフォルトのスタート状態である。入力が左側の正規表現にマッチした時、右側に記述されているCコードが実行される。
<INITIAL>"define"       return DEFINE;
<INITIAL>"if"           return IF;
<INITIAL>"else"         return ELSE;
    ・・・・・・

ここでいえば、defineという文字にマッチすると、returnでDEFINEを返すのである。そして、このreturnする値がこのトークンの種類を表している。 トークンの種類とは、例えば「123.456」というトークンが考えられるが、 このトークンの種類は、実数値DOUBLE_LITERALとなる。また、トークンの種類は、yaccの定義ファイル側で定義されているので、Cのソースに落ちた時には、#defineによるマクロ定義になっている。そして、トークンの文字列であるが、トークンには、入力そのままの文字列表現がある。「123.456」というトークンの文字列は、"123.456"である。その文字列は、アクションの中では、 yytextというグローバル変数で参照することができる。また、トークンの値「123.456」というトークンには、「実数の123.456という値」という意味もある。これはアクションの中で解釈しなければならないので、yylvalというグローバル変数にセットする。このyylvalというグローバル変数は、 色々なトークンの値を格納しなければならないので、 共用体になっている。この共用体の定義は、yacc(bison、mfcalcで述べたとおり%union{}である個所)の定義ファイル側で行なうことになっている。
またトークンの規則であるが以下のような場合には、
<INITIAL>"="            return ASSIGN;
<INITIAL>"=="           return EQ;

"="がASSIGNなら、"=="はASSIGNふたつに解釈されるのではない。flexでは、「できるだけ長い文字列をトークンとして切り出せるように」規則を選び、「長さが同じなら、先に書いた規則が優先」というルールがある。
<INITIAL>[A-Za-z_][0-9]* {
    yylval.identifier = clc_malloc(strlen(yytext) + 1);
    strcpy(yylval.identifier, yytext);
    return IDENTIFIER;
}

これは、変数名を切り出す規則である。[A-Za-z_][0-9]*が正規表現である。 アクションとして、yylval共用体の、identifierというメンバに、変数名をコピー(strcpy関数で)している。clc_mallocは、メモリ確保ルーチンである。 そして、<INITIAL>[1-9][0-9]* でint型整数を、<INITIAL>"0" で0(int型で)を、<INITIAL>[0-9]*\.[0-9]*でdouble型実数を切り出している。INITIAL>[ \t\n] ; では、空白やタブを読み込んだときは、何もしないようにアクション無しにしている。そして、<INITIAL>^# BEGIN COMMENT;でCOMMENTの開始を宣言する。ここからは、COMMENTの規則に移る。スタート状態がCOMMENTになると、<INITIAL>で始まる規則ではなく、<COMMENT>で始まる規則が使われる。 <COMMENT>. ;の規則により、 (.は任意の1文字にマッチする)コメントの読み飛ばしが行われる。 改行が来た場合には、 <COMMENT>\n BEGIN INITIAL;により、スタート状態をINITIALに戻す。すなわち、一行だけコメントを読み込むことが出来るのである。 また、<INITIAL>. clc_compile_error(CHARACTER_INVALID_ERR, NULL);では、INITIAL状態の他の全ての規則に引っ掛からない任意の1文字(.は任意の1文字にマッチするので)が来た場合である。よって、エラーにする。
こちらの電卓では、一番最初に作成したmfcalc.exeと違い、コマンド行編集が出来る。 式の前の方を、1文字だけ直したいというのもできるわけである。コマンド行編集機能の実装には、 GNUのreadline()ライブラリを使う。 readline()ライブラリは、cygwinのgcc環境にはたいてい入っている。このライブラリの使用方法としては、fgets()等の代わりに、
  char *p;

  ...
  p = readline(">");
  ...

  free(p);

とすればよい。readline()の引数はプロンプトの文字列、戻り値が入力された文字列になる。入力された戻り値はヒープに確保されますので、後で忘れずfree()する必要がある。これがガベージコレクタを使用するとその必要は無くなるのではあるが。しかし、flexの場合、標準の入力ルーチンを持っているので、このままではだめである。flex標準の入力ルーチンは、FILE*から入力を食うようになっていて、そのFILE*は、|verb| yyin|というグローバル変数で設定するようになっている。入力ルーチンを置き換える方法は、flexやFreeBSDの標準のlexでは、YY_INPUTマクロを置き換えることで行なう。
calc.lの冒頭より:
#undef YY_INPUT
#define YY_INPUT(buf, result, max_size) (result = my_yyinput(buf, max_size))

こんな感じで自分の入力ルーチンを定義する。入力ルーチンは、バッファとバッファサイズを受け取り(fgets()流)、バッファに文字列を詰め込んで、詰め込んだ文字数を返す。バッファは'\0'で終端させる必要はない。 calc.lでは、規則部の終わりに%%の行があって、それ以後に置き換えた入力ルーチンがずらずらと書ける。 もし、 YY_INPUTの再定義をしなければ、この部分は省略できた。 ファイルから食うこともあるのでモードにより、tty_input()とfile_input()を呼び分けるようになっている。tty_input()の方では、readline()で取れた文字列を、max_sizeの分だけ、切り取って返すようにしている。readline()で取った文字列が余った場合は、
static char *st_readline_buffer;
static int  st_readline_used_len;

を使ってバッファリングする。

5.1.2  構文解析

次に、calc.yである。これは、構文解析を行うbison(yaccのGNU版)のプログラムである。まず、先頭の %{ から %} までには、flexと同様、Cのコードを記述する。ここで、YYDEBUGを1にするが、これがあると、 yydebugをデバッガとかで非ゼロにしてやることで、パースの状態を実行時に見ることができる。次の
%union {
    char		*identifier;
    ParameterList	*parameter_list;
    Expression		*expression;
}

において、トークンや非終端子の型となる共用体を定義している。トークンの値は色々な型を持つので、共用体にする必要があるのである。 そして、以下の部分で、トークンの宣言(必要であればその型も)と、型の必要な非終端子の宣言を行なっている。もちろんmfcalcで述べたとおり、非終端子は、型が不要なら、特に宣言する必要はない。
%token <expression>    INT_LITERAL
%token <expression>    DOUBLE_LITERAL
%token <identifier>    IDENTIFIER
%token DEFINE IF ELSE WHILE LP RP LC RC SEMICOLON COMMA ASSIGN
        EQ NE GT GE LT LE ADD SUB MUL DIV MOD
%type   <parameter_list> parameter_list
%type   <expression> expression expression_list
        equality_expression relational_expression
	additive_expression multiplicative_expression
	unary_expression postfix_expression primary_expression
	if_expression while_expression

ここでトークンを宣言することにより、 y.tab.hというヘッダファイルでトークンの種類を表す名前が #defineされて(-dオプションを付けた場合)、 flex側のファイルでも使えるようになる。そして、%%以降が構文規則である。 最初の規則で、受理する全体を表現している。これはtranslation_unitである。
translation_unit
        : definition_or_expression
        | translation_unit definition_or_expression
        ;

関数定義するか、式を直接入れて計算させるかの繰り返しであるから、 translation_unitは、definition_or_expressionの一個以上の繰り返しである、ということを言っている。mfcalcではここは0個以上の繰り返しとなっていた。そして、definition_or_expressionとは何かと言えば、関数定義(function_definition)または、式(expression)にセミコロンを付けたものである。
definition_or_expression
        : function_definition
        | expression SEMICOLON
        {
            clc_eval_expression($1);
        }
        ...

以下、加減乗除の演算やif文while文、gnuplotなどに展開されていく。この規則は、簡単なので文法自体の説明は省略する。
実際に実行するときであるがyaccをかける際に、-vオプションを付けると、標準の場合、yaccではy.outputというファイルが、bisonでは.yファイルのサフィックスに.outputを付けたファイルができる。このy.outputファイルには、どこでどうコンフリクトが起きているかが記述されている。実際、考えながらべき乗の追加を行っている途中では、コンパイルはできても三つもconflictが起きていた。そのせいで優先順位などが適応されなかったが、ちゃんと正しく記述してやるとconflictは無くなったのである。
次にエラーについてであるが1回のシンタックスエラーで全てを終了されてはたまらないので、エラーリカバリがある。
definition_or_expression
        : function_definition
        | expression SEMICOLON
        {
            clc_eval_expression($1);
        }
        | error
        {
            yyclearin;
        }
        ;

「error」というトークンは、前述通り予約語でありエラーにマッチする。つまり、関数定義や、式の途中で文法エラーが発生したら、破棄するのである。アクションの中のyyclearinは、先読みしてあるトークンを破棄する。1つエラーがあり、そこまでの入力を破棄した場合次のトークンが正しいという保証はほとんどない。なので、yaccでは、「最低3つのトークンをエラーなしで読み込めた時」までは、エラーの報告を抑止する。 そして、上のプログラムの、yyclearin; の下に、
  yyerrok;

を付け加えると、即座に正常状態に復帰するようになる。しかし、このインタプリタの規則からすると、式の入力ではセミコロン、関数定義の入力では'}'という、終端になる記号があるので、
        | expression error SEMICOLON

などとして、セミコロンまでを読み飛ばすようにした方が良いともいえるがここではそうしなかった。

5.1.3  構文解析木の生成

calc.yにおけるCの関数のアクションの大半は、解析木の構築を行なうためのものである。これは、create.cとcalc.hで行っている。木構造を、葉の方から順に作っていく。 clc_create_binary_expression()において、2項演算子の両側が定数だったら、その場で評価して定数のノードに畳み込むようにしている。 そして、最終的には、関数定義の場合は、最後にclc_function_define()がコールされ、インタプリタの関数リストにFunctionDefinitionを追加する。電卓感覚で式を入力している場合は、clc_eval_expression()がコールされ、式の評価が開始されるようになっている。

5.1.4  構文解析木の評価

解析木の評価は、eval.cで行っている。eval.c では、解析木に対しては、書き込みを行なわない。解析木に対しては、read onlyである。 基本的には、eval_expression()を起点として、解析木を帰りがけ順(post order)でトラバースし、評価した結果を、共用体を含む構造体Valueに詰めて、構造体の実体返しで順次返しているだけである。 構造体Valueの定義は、以下のようである。
typedef struct {
    ValueType   type;
    union {
        int     int_value;
        double  double_value;
    } u;
} Value;

この電卓で扱う型は、整数型と実数型の二つあるので、intとdoubleの共用体で返すわけである。eval_expression()から呼び出される、各種の評価関数は、eval_int_expression(),eval_double_expression()のようなごく簡単なものを除いて、第1引数に LocalEnvironment * を取る。これは、関数を実行中に、現在実行中の関数の環境を保持するための構造体である。だから最初はNULLであるが、、関数呼び出しがあった時に新しく生成される。eval_function_call_expression()を参照してみればよい。LocalEnvironmentは、ローカル変数(Variableにより連結リストで保持される)を保持するためのポインタだけを持っている。
typedef struct Variable_tag {
    char	*name;
    Value	value;
    struct Variable_tag	*next;
} Variable;

typedef struct {
    Variable	*variable;
} LocalEnvironment;

変数を評価している所(eval_identifier_expression())では、先にローカル変数を検索してから、グローバル変数を検索する。代入する所では、先にローカル変数を検索し、次にグローバル変数を検索してから、どちらにもその名前の変数が存在しなければ、関数実行中ならローカル変数に、さもなければグローバル変数に、新しい変数を生成している。すなわちこのインタプリタでは関数内では、新しくグローバル変数を作り出すことはできないのである。グローバル変数は、CLC_Interpreterが保持している。

5.1.5  メモリ管理

  1. 式の解析木
    解析木は、解析木構築前に「ストレージ」を割り当て、 解析木の各ノードはそのストレージの中に確保している。 clc_malloc()が、 解析木用ストレージにメモリを確保するようになっている。単に「式 + セミコロン」で評価された場合は、 評価終了と同時にストレージごと破棄し、 関数定義の場合は、ストレージをFunctionDefinitionで保持する。
  2. 関数定義
    関数定義は、その構成要素の大部分が関数自体の解析木なので、 その解析木のストレージに、 FunctionDefinitionの中から、解析木のストレージを保持する。
  3. グローバル変数
    グローバル変数は、インタプリタと同じ寿命を持つので、CLC_Interpreterでストレージを保持している。clc_global_malloc()が、 グローバルなストレージにメモリを確保する。
  4. ローカル変数
    ローカル変数(というか、関数のローカル環境)は、 関数の呼び出し時に確保し、呼び出し終了後に解放する。

5.2  使用方法

mfcalcについては、最初のほうで述べたとおりである。mycalcについては、まず、memoryディレクトリでmakeでコンパイルをする。次に、debugディレクトリで同様にmakeでコンパイルを行う。最後に、これら二つの親ディレクトリであるmycalcでmakeを実行するとmycalc.exeが生成する。memoryとdebugは補助関数であり、bisonやflexのファイルはmycalcにある。そして、./mycalcで実行する。実行の終了はCtrl-dである。

5.3  ソースコード

前橋氏の電卓で変更したソースコードを、変更のあったプログラム(calc.l、calc.y、calc.h、eval.c、create.c)のみレポートの最後に付加した。また、アプレットで立ち上げて使うjavaソースプログラムも付加してある。

6  実行結果

実行結果は拡張しながら結果を出していったとおりである。このインタプリタが実行できる機能に関しては、考察で一覧にまとめた。

7  考察

7.1  インタプリタの機能一覧

インタプリタの機能を一覧にしてまとめておく。基本的に命令には最後にセミコロンを打つことによって実行される。

  1. 演算
    乗除、mod演算を加減演算よりも優先、べき乗の計算はさらに優先
    また、場合に応じて、int型とdouble型の両方の演算を行える
    マイナスを単項演算子として使用可能
    またコメントも使用可能
  2. 変数
    変数を定義して使用できる。
    使用形式は、変数に最初に値を代入したときに初めてメモリが割り当てられることになっている。
  3. 制御文の使用
    if文、while文、for文の実行が可能。
    使用形式は以下のとおりである。

    1. if文
      if 条件式 { 文1 }の形式。
      if 条件式 { 文1 }else { 文2 }の形式。
    2. while文
      "while" 条件式 { 文 }の形式。
    3. for文
      "for" ( 文1 ; 条件式 ; 文2 ) { 文 }の形式。
  4. sin,cos,tan関数の利用
    "sin,cos,tan" ( 文 )の形式で使用する。
  5. ユーザが関数を定義できる
    "define"  関数名 ( 引数をコンマで区切って記述 )  { 文 }
    の形式で定義する。
  6. ファイルの読み込み
    起動時に./mycalc < ファイル名 と続けて打つ。
  7. gnuplotの使用
    gnuplot;と打つ。
  8. 絵を描くappletの使用
    draw;と打つ。
実行結果から、このインタプリタは実現しようとした機能に関しては正しく動いていることがわかる。

7.2  その他

数値計算用インタプリタとしては、blasの関数が使えたほうがいいと思うし、行列の固有値も計算できたほうが良かったと思う。また、複素数もlapackの関数内に入っているようなのでそれについても出来たほうが良かった。実際、行列や複素数をライブラリを使って扱えるようにしようと、CLAPACKをダウンロードして、いざインストールしようとしたらエラーが発生して、色々やってみたけど結局できなかった。なんとか使ってみたいので、インストールを成功させて使用してみたい。他にも課題に書いてあることには全部一応挑戦してはみたのだが、どれもこれも理論上はこうしてこうしてこうやればいいと分かっているつもりだったのだがなんかうまくいかなくて悔しかった。その他、cygwinにRubyを組み込むと色々便利らしいということが分かった。Rubyの特徴としては、シンプルな文法・普通のオブジェクト指向機能(クラス,メソッドコールなど)・特殊なオブジェクト指向機能(Mixin, 特異メソッドなど)・演算子オーバーロード・例外処理機能・イテレータとクロージャ・ガーベージコレクタ・ダイナミックローディング (アーキテクチャによる)・そして、移植性が高い.多くのUNIX上で動くだけでなく,DOSやWindows, Mac,BeOSなどの上でも動くということが挙げられる。これも次にやってみたいことの1つとなった。

8  感想

今回のレポートは、今までのレポートの中で一番時間がかかったし、難しかった気がする。bisonやflexは見るのも聴くのも触るのも初めてで、結構とまどったけど、やり方を知ってしまえば、すごいことができるんだと感動した。エラーなどがたくさんでて、おそらく課題と関係ないエラーが多かったのだろうが、cygwinやunixに関する知識も増えた気がする。また、直接レポートには関係ないが、GNUに関してもなんだろうと思ったので色々調べてみて詳しくなったと思う。GNUのソフトウェアはすべてフリーソフト(フリーという言葉は正確ではないらしいけど)なので色々な環境に移植されているし、雑誌の付録についていることも多い。今まで使用してきたソフトもかなりGNUの恩恵に預かっていたことが分かりありがたいなと思った。でも、今回のレポートでのヒットは、cygwinのインストールだったと思う。学校が閉鎖されなければ、cygwinをインストールすることなんて思わなかっただろうし、cygiwnに関わるきっかけなんて他にあったとも思えない。そんな感じで、今回は結構いっぱいいっぱいだったけど、レポートをやっていくうちに楽しくなって、機能をどんどんつけたいなと思うようになった。bisonやflexも使っていると、結構面白くてもっと使ってみたいと思う。 数値計算の授業、半年間お世話になりました。有難うございました。

References

[9]
大石先生のホームページ
http://www.oishi.info.waseda.ac.jp/~oishi/index-j.html
[9]
bison入門
http://lmj.nagaokaut.ac.jp/%7Elu/computer/gnu_manual/bison-1.28/bison-ja_toc.html
[9]
flex字句スキャナ生成プログラム
http://www.asahi-net.or.jp/%7Ewg5k-ickw/html/online/flex-2.5.4/flex_toc.html
[9]
前橋氏の電卓を作ろうのページ
http://member.nifty.ne.jp/maebashi/programmer/c_yota/calc.html
[9]
理工系ユーザーの為のUNIX入門(武藤覚著)(星雲社)
[9]
yacc/lexプログラムジェネレータ on Unix(五月女健治)(テクノプレス)
[9]
A garbage collector for C and C++
http://www.hpl.hp.com/personal/Hans_Boehm/gc/
[9]
gnuplot
www.is.titetch.ac.jp/~sassa/cs-nyumon00/gnuplot-faq-j.html
[9]
独習java(ジョゼフ・オニール著)(翔泳社)

A  インタプリタ用ソースコード

前橋氏のプログラムから変更したもののみです。

A.1  calc.l

%{
#undef YY_INPUT
#define YY_INPUT(buf, result, max_size) (result = my_yyinput(buf, max_size))
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include "DBG.h"
#include "calc.h"
#include "y.tab.h"
static int my_yyinput(char *buf, int max_size);
%}
%start COMMENT
%%
<INITIAL>"define"       return DEFINE;
<INITIAL>"if"           return IF;
<INITIAL>"else"         return ELSE;
<INITIAL>"while"        return WHILE;
<INITIAL>"for"          return FOR;
<INITIAL>"("            return LP;
<INITIAL>")"            return RP;
<INITIAL>"{"            return LC;
<INITIAL>"}"            return RC;
<INITIAL>";"            return SEMICOLON;
<INITIAL>","            return COMMA;
<INITIAL>"="            return ASSIGN;
<INITIAL>"=="           return EQ;
<INITIAL>"!="           return NE;
<INITIAL>">"            return GT;
<INITIAL>">="           return GE;
<INITIAL>"<"            return LT;
<INITIAL>"<="           return LE;
<INITIAL>"+"            return ADD;
<INITIAL>"-"            return SUB;
<INITIAL>"*"            return MUL;
<INITIAL>"/"            return DIV;
<INITIAL>"^"            return POWOP;
<INITIAL>"%"            return MOD;
<INITIAL>"sin"          return SIN;
<INITIAL>"cos"          return COS;
<INITIAL>"tan"          return TAN;
<INITIAL>"gnuplot"      return GNUPLOT;
<INITIAL>"draw"         return DRAW;
<INITIAL>[A-Za-z_][A-Za-z_0-9]* {
    yylval.identifier = clc_malloc(strlen(yytext) + 1);
    strcpy(yylval.identifier, yytext);
    return IDENTIFIER;
}
<INITIAL>[1-9][0-9]* {
    Expression  *expression = clc_alloc_expression(INT_EXPRESSION);
    sscanf(yytext, "%d", &expression->u.int_value);
    yylval.expression = expression;
    return INT_LITERAL;
}
<INITIAL>"0" {
    Expression  *expression = clc_alloc_expression(INT_EXPRESSION);
    expression->u.int_value = 0;
    yylval.expression = expression;
    return INT_LITERAL;
}
<INITIAL>[0-9]*\.[0-9]* {
    Expression  *expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    sscanf(yytext, "%lf", &expression->u.double_value);
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>[ \t\n] ;
<INITIAL>^#     BEGIN COMMENT;
<INITIAL>.      clc_compile_error(CHARACTER_INVALID_ERR, NULL);
<COMMENT>\n     BEGIN INITIAL;
<COMMENT>.      ;
%%
static char *st_readline_buffer;
static int  st_readline_used_len;
void
clc_initialize_readline_buffer(void)
{
    if (st_readline_buffer) {
        free(st_readline_buffer);
    }
    st_readline_buffer = NULL;
    st_readline_used_len = 0;
}
static int
tty_input(char *buf, int max_size)
{
    int len;
    if (st_readline_buffer == NULL) {
        st_readline_used_len = 0;
        st_readline_buffer = readline(">");
        if (st_readline_buffer == NULL) {
            return 0;
        }
    }
    len = smaller(strlen(st_readline_buffer) - st_readline_used_len,
                  max_size);
    strncpy(buf, &st_readline_buffer[st_readline_used_len], len);
    st_readline_used_len += len;
    if (st_readline_buffer[st_readline_used_len] == '\0') {
        free(st_readline_buffer);
        st_readline_buffer = NULL;
    }
    return len;
}
static int
file_input(char *buf, int max_size)
{
    int ch;
    int len;
    if (feof(clc_current_interpreter->input_fp))
        return 0;
    for (len = 0; len < max_size; len++) {
        ch = getc(clc_current_interpreter->input_fp);
        if (ch == EOF)
            break;
        buf[len] = ch;
        len++;
    }
    return len;
}
static int
my_yyinput(char *buf, int max_size)
{
    int result;
    switch (clc_current_interpreter->input_mode) {
      case CLC_TTY_INPUT_MODE:
        result = tty_input(buf, max_size);
        break;
      case CLC_FILE_INPUT_MODE:
        result = file_input(buf, max_size);
        break;
      default:
        DBG_assert(0, ("bad default(%d).\n",
                       clc_current_interpreter->input_mode));
    }
    return result;
}

A.2  calc.y

%{
#include <stdio.h>
#include "calc.h"
#define YYDEBUG 1
%}
%union {
    char                *identifier;
    ParameterList       *parameter_list;
    Expression          *expression;
}
%token <expression>     INT_LITERAL
%token <expression>     DOUBLE_LITERAL
%token <identifier>     IDENTIFIER
%token DEFINE IF ELSE WHILE FOR LP RP LC RC SEMICOLON COMMA ASSIGN
        EQ NE GT GE LT LE ADD SUB MUL DIV POWOP MOD SIN COS TAN GNUPLOT DRAW
%type   <parameter_list> parameter_list
%type   <expression> expression expression_list
        equality_expression relational_expression
        additive_expression multiplicative_expression
        unary_expression postfix_expression primary_expression
        if_expression while_expression for_expression exp_expression
%%
translation_unit
        : definition_or_expression
        | translation_unit definition_or_expression
        ;
definition_or_expression
        : function_definition
        | GNUPLOT SEMICOLON
        {
            system("wgnupl32");
        }
        | DRAW SEMICOLON
        {
            system("appletviewer example1.html");
        }
        | expression SEMICOLON
        {
            clc_eval_expression($1);
        }
        | error
        {
            yyclearin;
            clc_reopen_current_storage();
        }
        ;
function_definition
        : DEFINE IDENTIFIER LP parameter_list RP LC expression_list RC
        {
            clc_function_define($2, $4, $7);
        }
        | DEFINE IDENTIFIER LP RP LC expression_list RC
        {
            clc_function_define($2, NULL, $6);
        }
        ;
parameter_list
        : IDENTIFIER
        {
            $$ = clc_create_parameter($1);
        }
        | parameter_list COMMA IDENTIFIER
        {
            $$ = clc_chain_parameter($1, $3);
        }
        ;
expression_list
        : expression
        {
            $$ = clc_create_expression_list($1);
        }
        | expression_list COMMA expression
        {
            $$ = clc_chain_expression_list($1, $3);
        }
        ;
expression
        : equality_expression
        | IDENTIFIER ASSIGN equality_expression
        {
            $$ = clc_create_assign_expression($1, $3);
        }
        ;
equality_expression
        : relational_expression
        | equality_expression EQ relational_expression
        {
            $$ = clc_create_binary_expression(EQ_EXPRESSION, $1, $3);
        }
        | equality_expression NE relational_expression
        {
            $$ = clc_create_binary_expression(NE_EXPRESSION, $1, $3);
        }
        ;
relational_expression
        : additive_expression
        | relational_expression GT additive_expression
        {
            $$ = clc_create_binary_expression(GT_EXPRESSION, $1, $3);
        }
        | relational_expression GE additive_expression
        {
            $$ = clc_create_binary_expression(GE_EXPRESSION, $1, $3);
        }
        | relational_expression LT additive_expression
        {
            $$ = clc_create_binary_expression(LT_EXPRESSION, $1, $3);
        }
        | relational_expression LE additive_expression
        {
            $$ = clc_create_binary_expression(LE_EXPRESSION, $1, $3);
        }
        ;
additive_expression
        : multiplicative_expression
        | additive_expression ADD multiplicative_expression
        {
            $$ = clc_create_binary_expression(ADD_EXPRESSION, $1, $3);
        }
        | additive_expression SUB multiplicative_expression
        {
            $$ = clc_create_binary_expression(SUB_EXPRESSION, $1, $3);
        }
        ;
multiplicative_expression
        : exp_expression
        | multiplicative_expression MUL exp_expression
        {
            $$ = clc_create_binary_expression(MUL_EXPRESSION, $1, $3);
        }
        | multiplicative_expression DIV exp_expression
        {
            $$ = clc_create_binary_expression(DIV_EXPRESSION, $1, $3);
        }
        | multiplicative_expression MOD exp_expression
        {
            $$ = clc_create_binary_expression(MOD_EXPRESSION, $1, $3);
        }
        ;
exp_expression
        : unary_expression
        | exp_expression POWOP unary_expression
        {
            $$ = clc_create_binary_expression(EXP_EXPRESSION,$1,$3); 
        }
        ;
unary_expression
        : postfix_expression
        | SUB unary_expression
        {
            $$ = clc_create_minus_expression($2);
        }
        ;
postfix_expression
        : primary_expression
        ;
primary_expression
        : IDENTIFIER LP expression_list RP
        {
            $$ = clc_create_function_call_expression($1, $3);
        }
        | IDENTIFIER LP RP
        {
            $$ = clc_create_function_call_expression($1, NULL);
        }
        | if_expression
        | while_expression
        | for_expression
        | LP expression RP
        {
            $$ = $2;
        }
        | IDENTIFIER
        {
            $$ = clc_create_identifier_expression($1);
        }
        | SIN LP expression  RP
        {
            $$ = clc_create_binary_expression(SIN_EXPRESSION, $3, $3);
        }
        | COS LP expression RP
        {
            $$ = clc_create_binary_expression(COS_EXPRESSION, $3, $3);
        }
        | TAN LP expression RP
        {
            $$ = clc_create_binary_expression(TAN_EXPRESSION, $3, $3); 
        } 
        | INT_LITERAL
        | DOUBLE_LITERAL
        ;
if_expression
        : IF expression LC expression_list RC
        {
            $$ = clc_create_if_expression($2, $4, NULL);
        }
        | IF expression LC expression_list RC ELSE LC expression_list RC
        {
            $$ = clc_create_if_expression($2, $4, $8);
        }
        ;
while_expression
        : WHILE expression LC expression_list RC
        {
            $$ = clc_create_while_expression($2, $4);
        }
        ;
for_expression
        : FOR LP expression SEMICOLON expression SEMICOLON expression RP LC expression_list RC
        {
          $$ = clc_create_for_expression($3,$5,$7,$10);
        }
        ;
%%

A.3  calc.h

#ifndef PRIVATE_CALC_H_INCLUDED
#define PRIVATE_CALC_H_INCLUDED
#include <stdio.h>
#include <setjmp.h>
#include "MEM.h"
#include "CLC.h"
#define smaller(a, b) ((a) < (b) ? (a) : (b))
#define larger(a, b) ((a) > (b) ? (a) : (b))
typedef enum {
    PARSE_ERR = 1,
    CHARACTER_INVALID_ERR,
    FUNCTION_MULTIPLE_DEFINE_ERR,
    COMPILE_ERROR_NUM
} CompileError;
typedef enum {
    VARIABLE_NOT_FOUND_ERR = 1,
    FUNCTION_NOT_FOUND_ERR,
    BOOLEAN_EXPECTED_ERR,
    ARGUMENT_TOO_MANY_ERR,
    ARGUMENT_TOO_FEW_ERR,
    RUNTIME_ERROR_NUM
} RuntimeError;
typedef struct Expression_tag Expression;
typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EXP_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    FOR_EXPRESSION,
    SIN_EXPRESSION,
    COS_EXPRESSION,
    TAN_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;
typedef struct {
    Expression *expression;
    Expression *next;
} ExpressionList;
typedef struct {
    char        *variable;
    Expression  *operand;
} AssignExpression;
typedef struct {
    Expression  *left;
    Expression  *right;
} BinaryExpression;
typedef struct {
    Expression  *condition;
    Expression  *then_expression;
    Expression  *else_expression;
} IfExpression;
typedef struct {
    Expression  *condition;
    Expression  *expression_list;
} WhileExpression;
typedef struct {
    Expression *expression1;
    Expression *condition;
    Expression *expression2;
    Expression *expression_list;
} ForExpression;
typedef struct {
    char        *identifier;
    Expression  *argument;
} FunctionCallExpression;
struct Expression_tag {
    ExpressionType type;
    union {
        int                     int_value;
        double                  double_value;
        char                    *identifier;
        ExpressionList         expression_list;
        AssignExpression        assign_expression;
        BinaryExpression        binary_expression;
        Expression              *minus_expression;
        IfExpression            if_expression;
        FunctionCallExpression  function_call_expression;
        WhileExpression         while_expression;
        ForExpression           for_expression;
    } u;
};
typedef struct ParameterList_tag {
    char        *name;
    struct ParameterList_tag *next;
} ParameterList;
typedef struct FunctionDefinition_tag {
    char                *name;
    ParameterList       *parameter;
    Expression          *expression_list;
    MEM_Storage         storage;
    struct FunctionDefinition_tag        *next;
} FunctionDefinition;
typedef enum {
    INT_VALUE = 1,
    DOUBLE_VALUE,
    NULL_VALUE
} ValueType;
typedef struct {
    ValueType   type;
    union {
        int     int_value;
        double  double_value;
    } u;
} Value;
typedef struct Variable_tag {
    char        *name;
    Value       value;
    struct Variable_tag *next;
} Variable;
typedef struct {
    Variable    *variable;
} LocalEnvironment;
struct CLC_Interpreter_tag {
    CLC_InputMode       input_mode;
    FILE                *input_fp;
    MEM_Storage         global_storage;
    MEM_Storage         current_storage;
    Variable            *variable;
    FunctionDefinition  *function_list;
    jmp_buf             error_recovery_environment;
};
#ifdef GLOBAL_VARIABLE_DEFINE
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL CLC_Interpreter clc_current_interpreter;
/* calc.l */
void clc_initialize_readline_buffer(void);
/* create.c */
void clc_function_define(char *identifier, ParameterList *parameter_list,
                                Expression *expression_list);
ParameterList *clc_create_parameter(char *identifier);
ParameterList *clc_chain_parameter(ParameterList *list,
                                   char *identifier);
Expression *clc_alloc_expression(ExpressionType type);
Expression *clc_create_expression_list(Expression *expression);
Expression *clc_chain_expression_list(Expression *list, Expression *add);
Expression *clc_create_assign_expression(char *variable,
                                         Expression *operand);
Expression *clc_create_binary_expression(ExpressionType operator,
                                         Expression *left,
                                         Expression *right);
Expression *clc_create_minus_expression(Expression *operand);
Expression *clc_create_identifier_expression(char *identifier);
Expression *clc_create_if_expression(Expression *condition,
                                     Expression *then_expression,
                                     Expression *else_expression);
Expression *clc_create_while_expression(Expression *condition,
                                      Expression *expression);
Expression *clc_create_for_expression(Expression *expression1,
                                      Expression *condition,
                                      Expression *expression2,
                                      Expression *expression);
Expression *clc_create_function_call_expression(char *func_name,
                                                Expression *expression);
/* eval.c */
void  clc_eval_expression(Expression *expr);
Value clc_eval_binary_expression(LocalEnvironment *env,
                                 ExpressionType operator,
                                 Expression *left, Expression *right);
Value clc_eval_minus_expression(LocalEnvironment *env, Expression *operand);
/* util.c */
void clc_print_expression(Expression *expression);
void *clc_malloc(size_t size);
Value *clc_search_local_variable(LocalEnvironment *env,
                                 char *identifier);
Value *clc_search_global_variable(char *identifier);
Value *clc_search_local_variable(LocalEnvironment *env,
                                 char *identifier);
Value *clc_search_global_variable(char *identifier);
void clc_add_local_variable(LocalEnvironment *env,
                            char *identifier, Value *value);
void clc_add_global_variable(char *identifier, Value *value);
FunctionDefinition *clc_search_function(char *name);
void clc_reopen_current_storage(void);
/* error.c */
void clc_compile_error(CompileError id, char *fmt, ...);
void clc_runtime_error(RuntimeError id, char *fmt, ...);
#endif /* PRIVATE_CALC_H_INCLUDED */

A.4  create.c

#include <stdio.h>
#include "DBG.h"
#include "calc.h"
void
clc_function_define(char *identifier, ParameterList *parameter_list,
                    Expression *expression_list)
{
    FunctionDefinition *f;
    if (clc_search_function(identifier)) {
        clc_compile_error(FUNCTION_MULTIPLE_DEFINE_ERR, "(%s)\n", identifier);
        clc_reopen_current_storage();
        return;
    }
    f = clc_malloc(sizeof(FunctionDefinition));
    f->name = identifier;
    f->parameter = parameter_list;
    f->expression_list = expression_list;
    f->storage = clc_current_interpreter->current_storage;
    clc_current_interpreter->current_storage = MEM_open_storage(0);
    f->next = clc_current_interpreter->function_list;
    clc_current_interpreter->function_list = f;
}
ParameterList*
clc_create_parameter(char *identifier)
{
    ParameterList       *p;
    p = clc_malloc(sizeof(ParameterList));
    p->name = identifier;
    p->next = NULL;
    return p;
}
ParameterList *
chain_parameter(ParameterList *list, ParameterList *add)
{
    ParameterList *pos;
    for (pos = list; pos->next; pos = pos->next)
        ;
    pos->next = add;
    return list;
}
ParameterList*
clc_chain_parameter(ParameterList *list,  char *identifier)
{
    ParameterList       *add;
    add = clc_create_parameter(identifier);
    return chain_parameter(list, add);
}
Expression *
clc_alloc_expression(ExpressionType type)
{
    Expression  *exp;
    exp = clc_malloc(sizeof(Expression));
    exp->type = type;
    return exp;
}
Expression *
clc_create_expression_list(Expression *expression)
{
    Expression  *exp;
    exp = clc_alloc_expression(EXPRESSION_LIST_EXPRESSION);
    exp->u.expression_list.expression = expression;
    exp->u.expression_list.next = NULL;
    return exp;
}
Expression *
clc_chain_expression_list(Expression *list, Expression *add)
{
    Expression  *exp;
    Expression  *pos;
    exp = clc_alloc_expression(EXPRESSION_LIST_EXPRESSION);
    exp->u.expression_list.expression = add;
    exp->u.expression_list.next = NULL;
    for (pos = list; pos->u.expression_list.next;
         pos = pos->u.expression_list.next)
        ;
    pos->u.expression_list.next = exp;
    return list;
}
Expression *
clc_create_assign_expression(char *variable,
                             Expression *operand)
{
    Expression *exp;
    exp = clc_alloc_expression(ASSIGN_EXPRESSION);
    exp->u.assign_expression.variable = variable;
    exp->u.assign_expression.operand = operand;
    return exp;
}
static Expression
convert_value_to_expression(Value *v)
{
    Expression  expr;
    if (v->type == INT_VALUE) {
        expr.type = INT_EXPRESSION;
        expr.u.int_value = v->u.int_value;
    } else {
        DBG_assert(v->type == DOUBLE_VALUE, ("v->type..%d\n", v->type));
        expr.type = DOUBLE_EXPRESSION;
        expr.u.double_value = v->u.double_value;
    }
    return expr;
}
Expression *
clc_create_binary_expression(ExpressionType operator,
                             Expression *left,
                             Expression *right)
{
    if ((left->type == INT_EXPRESSION
         || left->type == DOUBLE_EXPRESSION)
        && (right->type == INT_EXPRESSION
            || right->type == DOUBLE_EXPRESSION)) {
        Value v;
        v = clc_eval_binary_expression(NULL, operator, left, right);
        /* Overwriting left hand expression. */
        *left = convert_value_to_expression(&v);

        return left;
    } else {
        Expression *exp;
        exp = clc_alloc_expression(operator);
        exp->u.binary_expression.left = left;
        exp->u.binary_expression.right = right;
        return exp;
    }
}
Expression *
clc_create_minus_expression(Expression *operand)
{
    if (operand->type == INT_EXPRESSION
        || operand->type == DOUBLE_EXPRESSION) {
        Value   v;
        v = clc_eval_minus_expression(NULL, operand);
        /* Notice! Overwriting operand expression. */
        *operand = convert_value_to_expression(&v);
        return operand;
    } else {
        Expression      *exp;
        exp = clc_alloc_expression(MINUS_EXPRESSION);
        exp->u.minus_expression = operand;
        return exp;
    }
}
Expression *
clc_create_identifier_expression(char *identifier)
{
    Expression  *exp;
    exp = clc_alloc_expression(IDENTIFIER_EXPRESSION);
    exp->u.identifier = identifier;
    return exp;
}
Expression *
clc_create_if_expression(Expression *condition,
                         Expression *then_expression,
                         Expression *else_expression)
{
    Expression  *exp;
    exp = clc_alloc_expression(IF_EXPRESSION);
    exp->u.if_expression.condition = condition;
    exp->u.if_expression.then_expression = then_expression;
    exp->u.if_expression.else_expression = else_expression;
    return exp;
}
Expression *
clc_create_while_expression(Expression *condition,
                            Expression *expression)
{
    Expression  *exp;
    exp = clc_alloc_expression(WHILE_EXPRESSION);
    exp->u.while_expression.condition = condition;
    exp->u.while_expression.expression_list = expression;
    return exp;
}
Expression *
clc_create_for_expression(Expression *expression1,
                          Expression *condition,
                          Expression *expression2,
                          Expression *expression)
{
    Expression *exp;
    exp = clc_alloc_expression(FOR_EXPRESSION);
    exp->u.for_expression.expression1 = expression1;
    exp->u.for_expression.condition = condition;
    exp->u.for_expression.expression2 = expression2;
    exp->u.for_expression.expression_list = expression;
    return exp;
}
Expression *
clc_create_function_call_expression(char *func_name,
                                    Expression *argument)
{
    Expression  *exp;
    exp = clc_alloc_expression(FUNCTION_CALL_EXPRESSION);
    exp->u.function_call_expression.identifier = func_name;
    exp->u.function_call_expression.argument = argument;
    return exp;
}

A.5  eval.c

#include <stdio.h>
#include <math.h>
#include "MEM.h"
#include "DBG.h"
#include "calc.h"
static Value
eval_int_expression(int int_value)
{
    Value       v;
    v.type = INT_VALUE;
    v.u.int_value = int_value;
    return v;
}
static Value
eval_double_expression(double double_value)
{
    Value       v;
    v.type = DOUBLE_VALUE;
    v.u.double_value = double_value;
    return v;
}
static Value
eval_identifier_expression(LocalEnvironment *env, char *identifier)
{
    Value       v;
    Value       *vp;
    vp = clc_search_local_variable(env, identifier);
    if (vp != NULL) {
        v = *vp;
        return v;
    }
    vp = clc_search_global_variable(identifier);
    if (vp != NULL) {
        v = *vp;
        return v;
    }
    clc_runtime_error(VARIABLE_NOT_FOUND_ERR, "(%s)\n", identifier);
    return v; /* dummy. make compiler happy. */
}
static Value eval_expression(LocalEnvironment *env, Expression *expr);
static Value
eval_expression_list_expression(LocalEnvironment *env,
                                Expression *expression, Expression *next)
{
    Value       v;
    v = eval_expression(env, expression);
    if (next) {
        v = eval_expression(env, next);
    }
    return v;
}
static Value
eval_assign_expression(LocalEnvironment *env,
                       char *identifier, Expression *expression)
{
    Value       v;
    Value       *left;
    v = eval_expression(env, expression);
    left = clc_search_local_variable(env, identifier);
    if (left == NULL) {
        left = clc_search_global_variable(identifier);
    }
    if (left != NULL) {
        /* Notice! Overwriting variable by pointer. */
        *left = v;
    } else {
        if (env != NULL) {
            clc_add_local_variable(env, identifier, &v);
        } else {
            clc_add_global_variable(identifier, &v);
        }
    }
    return v;
}
static int
eval_binary_int(ExpressionType operator, int left, int right)
{
    int result;
    switch (operator) {
      case INT_EXPRESSION:      /* FALLTHRU */
      case DOUBLE_EXPRESSION:   /* FALLTHRU */
      case IDENTIFIER_EXPRESSION:       /* FALLTHRU */
      case EXPRESSION_LIST_EXPRESSION:  /* FALLTHRU */
      case ASSIGN_EXPRESSION:
        DBG_assert(0, ("bad case...%d", operator));
        break;
      case ADD_EXPRESSION:
        result = left + right;
        break;
      case SUB_EXPRESSION:
        result = left - right;
        break;
      case MUL_EXPRESSION:
        result = left * right;
        break;
      case DIV_EXPRESSION:
        result = left / right;
        break;
      case MOD_EXPRESSION:
        result = left % right;
        break;
      case EXP_EXPRESSION:
    result = pow(left,right);
    break;
      case SIN_EXPRESSION:
    result = sin(left);
    break;
      case COS_EXPRESSION:
    result = cos(left);
    break;
      case EQ_EXPRESSION:
        result = left == right;
        break;
      case NE_EXPRESSION:
        result = left != right;
        break;
      case GT_EXPRESSION:
        result = left > right;
        break;
      case GE_EXPRESSION:
        result = left >= right;
        break;
      case LT_EXPRESSION:
        result = left < right;
        break;
      case LE_EXPRESSION:
        result = left <= right;
        break;
      case MINUS_EXPRESSION:    /* FALLTHRU */
      case IF_EXPRESSION:       /* FALLTHRU */
      case WHILE_EXPRESSION:    /* FALLTHRU */
      case FOR_EXPRESSION:      /* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:    /* FALLTHRU */
      case EXPRESSION_TYPE_NUM: /* FALLTHRU */
      default:
        DBG_assert(0, ("bad case...%d", operator));
    }
    return result;
}
static void
eval_binary_double(ExpressionType operator, double left, double right,
                   Value *result)
{
    if (operator == ADD_EXPRESSION || operator == SUB_EXPRESSION
        || operator == MUL_EXPRESSION || operator == DIV_EXPRESSION
        || operator == MOD_EXPRESSION || operator == EXP_EXPRESSION
        || operator == SIN_EXPRESSION || operator == COS_EXPRESSION
        || operator == TAN_EXPRESSION ) {
        result->type = DOUBLE_VALUE;
    } else {
        DBG_assert(operator == EQ_EXPRESSION || operator == NE_EXPRESSION
                   || operator == GT_EXPRESSION || operator == GE_EXPRESSION
                   || operator == LT_EXPRESSION || operator == LE_EXPRESSION,
                   ("operator..%d\n", operator));
        result->type = INT_VALUE;
    }
    switch (operator) {
      case INT_EXPRESSION:      /* FALLTHRU */
      case DOUBLE_EXPRESSION:   /* FALLTHRU */
      case IDENTIFIER_EXPRESSION:       /* FALLTHRU */
      case EXPRESSION_LIST_EXPRESSION:  /* FALLTHRU */
      case ASSIGN_EXPRESSION:
        DBG_assert(0, ("bad case...%d", operator));
        break;
      case ADD_EXPRESSION:
        result->u.double_value = left + right;
        break;
      case SUB_EXPRESSION:
        result->u.double_value = left - right;
        break;
      case MUL_EXPRESSION:
        result->u.double_value = left * right;
        break;
      case DIV_EXPRESSION:
        result->u.double_value = left / right;
        break;
      case MOD_EXPRESSION:
        result->u.double_value = fmod(left, right);
        break;
      case EXP_EXPRESSION:
        result->u.double_value = pow(left, right);
        break;
      case SIN_EXPRESSION:
        result->u.double_value = sin(left);
        break;
      case COS_EXPRESSION:
        result->u.double_value = cos(left);
        break;
      case TAN_EXPRESSION:
        result->u.double_value = tan(left);
        break;
      case EQ_EXPRESSION:
        result->u.int_value = left == right;
        break;
      case NE_EXPRESSION:
        result->u.int_value = left != right;
        break;
      case GT_EXPRESSION:
        result->u.int_value = left > right;
        break;
      case GE_EXPRESSION:
        result->u.int_value = left >= right;
        break;
      case LT_EXPRESSION:
        result->u.int_value = left < right;
        break;
      case LE_EXPRESSION:
        result->u.int_value = left <= right;
        break;
      case MINUS_EXPRESSION:    /* FALLTHRU */
      case IF_EXPRESSION:       /* FALLTHRU */
      case WHILE_EXPRESSION:    /* FALLTHRU */
      case FOR_EXPRESSION:      /* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:    /* FALLTHRU */
      case EXPRESSION_TYPE_NUM: /* FALLTHRU */
      default:
        DBG_assert(0, ("bad default...%d", operator));
    }
}
Value
clc_eval_binary_expression(LocalEnvironment *env,
                           ExpressionType operator,
                           Expression *left, Expression *right)
{
    Value       left_val;
    Value       right_val;
    Value       result;
    left_val = eval_expression(env, left);
    right_val = eval_expression(env, right);
    if (left_val.type == INT_VALUE
                && right_val.type == INT_VALUE) {

        if(operator == SIN_EXPRESSION || operator == COS_EXPRESSION
         || operator == TAN_EXPRESSION) {
           left_val.u.double_value = left_val.u.int_value;
           right_val.u.double_value = right_val.u.int_value;
           eval_binary_double(operator, left_val.u.double_value,
                              right_val.u.double_value, &result);
        }else {
          result.type = INT_VALUE;
          result.u.int_value
            = eval_binary_int(operator,
                              left_val.u.int_value, right_val.u.int_value);
        }
    } else if (left_val.type == DOUBLE_VALUE
               && right_val.type == DOUBLE_VALUE) {
        eval_binary_double(operator,
                           left_val.u.double_value, right_val.u.double_value,
                           &result);
    } else {
        /* cast int to double */
        if (left_val.type == INT_VALUE) {
            left_val.u.double_value = left_val.u.int_value;
        } else {
            right_val.u.double_value = right_val.u.int_value;
        }
        eval_binary_double(operator,
                           left_val.u.double_value, right_val.u.double_value,
                           &result);
    }
    return result;
}
Value
clc_eval_minus_expression(LocalEnvironment *env, Expression *operand)
{
    Value       operand_val;
    Value       result;
    operand_val = eval_expression(env, operand);
    if (operand_val.type == INT_VALUE) {
        result.type = INT_VALUE;
        result.u.int_value = -operand_val.u.int_value;
    } else {
        DBG_assert(operand_val.type == DOUBLE_VALUE,
                   ("operand_val.type..%d", operand_val.type));
        result.type = DOUBLE_VALUE;
        result.u.double_value = -operand_val.u.double_value;
    }
    return result;
}
static Value
eval_if_expression(LocalEnvironment *env,
                   Expression *condition, Expression *then_expression,
                   Expression *else_expression)
{
    Value       condition_val;
    Value       result;
    condition_val = eval_expression(env, condition);
    if (condition_val.type != INT_VALUE) {
        clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
    }
    if (condition_val.u.int_value) {
        result = eval_expression(env, then_expression);
    } else {
        result = eval_expression(env, else_expression);
    }
    return result;
}
static Value
eval_while_expression(LocalEnvironment *env,
                      Expression *condition, Expression *expression_list)
{
    Value       condition_val;
    Value       result;
    while (1) {
        condition_val = eval_expression(env, condition);
        if (condition_val.type != INT_VALUE) {
            clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
        }
        if (!condition_val.u.int_value)
            break;

        result = eval_expression(env, expression_list);
    }
    return result;
}
static Value
eval_for_expression(LocalEnvironment *env,
                    Expression *expression1, Expression *condition,
                    Expression *expression2, Expression *expression_list)
{
    Value       condition_val;
    Value       result;
    result = eval_expression(env, expression1);
    while (1) {
        condition_val = eval_expression(env, condition);
        if (condition_val.type != INT_VALUE) {
            clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
        }
        if (!condition_val.u.int_value)
            break;
        result = eval_expression(env, expression_list);
        result = eval_expression(env, expression2);
    }
    return result;
}
static LocalEnvironment *
alloc_local_environment()
{
    LocalEnvironment *ret;
    ret = MEM_malloc(sizeof(LocalEnvironment));
    ret->variable = NULL;
    return ret;
}
static void
dispose_local_environment(LocalEnvironment *env)
{
    while (env->variable) {
        Variable        *temp;
        temp = env->variable->next;
        MEM_free(env->variable);
        env->variable = temp;
    }
    MEM_free(env);
}
static Value
eval_function_call_expression(LocalEnvironment *env,
                              char *identifier, Expression *argument)
{
    Value       result;
    Expression          *arg_p;
    ParameterList       *param_p;
    LocalEnvironment    *local_env;
    FunctionDefinition  *func;

    func = clc_search_function(identifier);
    if (func == NULL) {
        clc_runtime_error(FUNCTION_NOT_FOUND_ERR, "name..%s\n", identifier);
    }
    local_env = alloc_local_environment();
    DBG_assert(argument->type == EXPRESSION_LIST_EXPRESSION,
               ("type..%d\n", argument->type));
    for (arg_p = argument, param_p = func->parameter;
         arg_p;
         arg_p = arg_p->u.expression_list.next,
         param_p = param_p->next) {
        Value arg_val;

        if (param_p == NULL) {
            clc_runtime_error(ARGUMENT_TOO_MANY_ERR, NULL);
        }
        arg_val = eval_expression(env,
                                  arg_p->u.expression_list.expression);
        clc_add_local_variable(local_env, param_p->name, &arg_val);
    }
    if (param_p) {
        clc_runtime_error(ARGUMENT_TOO_FEW_ERR, NULL);
    }
    result = eval_expression(local_env, func->expression_list);
    dispose_local_environment(local_env);
    return result;
}
static Value
eval_expression(LocalEnvironment *env, Expression *expr)
{
    Value       v;
    switch (expr->type) {
      case INT_EXPRESSION:
        v = eval_int_expression(expr->u.int_value);
        break;
      case DOUBLE_EXPRESSION:
        v = eval_double_expression(expr->u.double_value);
        break;
      case IDENTIFIER_EXPRESSION:
        v = eval_identifier_expression(env, expr->u.identifier);
        break;
      case EXPRESSION_LIST_EXPRESSION:
        v = eval_expression_list_expression
            (env,
             expr->u.expression_list.expression,
             expr->u.expression_list.next);
        break;
      case ASSIGN_EXPRESSION:
        v = eval_assign_expression(env,
                                   expr->u.assign_expression.variable,
                                   expr->u.assign_expression.operand);
        break;
      case ADD_EXPRESSION:      /* FALLTHRU */
      case SUB_EXPRESSION:      /* FALLTHRU */
      case MUL_EXPRESSION:      /* FALLTHRU */
      case DIV_EXPRESSION:      /* FALLTHRU */
      case MOD_EXPRESSION:      /* FALLTHRU */
      case EXP_EXPRESSION:  /* FALLTHRU */
      case EQ_EXPRESSION:       /* FALLTHRU */
      case NE_EXPRESSION:       /* FALLTHRU */
      case GT_EXPRESSION:       /* FALLTHRU */
      case GE_EXPRESSION:       /* FALLTHRU */
      case LT_EXPRESSION:       /* FALLTHRU */
      case LE_EXPRESSION:       /* FALLTHRU */
      case SIN_EXPRESSION:  /* FALLTHRU */
      case COS_EXPRESSION:  /* FALLTHRU */
      case TAN_EXPRESSION:  /* FALLTHRU */
        v = clc_eval_binary_expression(env,
                                       expr->type,
                                       expr->u.binary_expression.left,
                                       expr->u.binary_expression.right);
        break;
      case MINUS_EXPRESSION:
        v = clc_eval_minus_expression(env, expr->u.minus_expression);
        break;
      case IF_EXPRESSION:
        v = eval_if_expression(env,
                               expr->u.if_expression.condition,
                               expr->u.if_expression.then_expression,
                               expr->u.if_expression.else_expression);
        break;
      case WHILE_EXPRESSION:
        v = eval_while_expression(env,
                                  expr->u.while_expression.condition,
                                  expr->u.while_expression.expression_list);
        break;
      case FOR_EXPRESSION:
        v = eval_for_expression(env,
                                  expr->u.for_expression.expression1,
                                  expr->u.for_expression.condition,
                                  expr->u.for_expression.expression2,
                                  expr->u.for_expression.expression_list);
        break;
      case FUNCTION_CALL_EXPRESSION:
        v = eval_function_call_expression
            (env,
             expr->u.function_call_expression.identifier,
             expr->u.function_call_expression.argument);
        break;
      case EXPRESSION_TYPE_NUM: /* FALLTHRU */
      default:
        DBG_assert(0, ("bad case. type..%d\n", expr->type));
    }
    return v;
}
void
clc_eval_expression(Expression *expression)
{
    Value               v;
    RuntimeError        error_id;
    if ((error_id = (RuntimeError)setjmp(clc_current_interpreter
                                         ->error_recovery_environment)) == 0) {
        v = eval_expression(NULL, expression);
        if (clc_current_interpreter->input_mode == CLC_TTY_INPUT_MODE) {
            if (v.type == INT_VALUE) {
                printf(">>%d\n", v.u.int_value);
            } else if (v.type == DOUBLE_VALUE) {
                printf(">>%f\n", v.u.double_value);
            } else {
                printf("<void>\n");
            }
        }
    } else {
    }
    clc_reopen_current_storage();
}

B  絵描きアプレット用ソースコード

B.1  example1.html

<html>
  <head>
      <title>Draw</title>
  </head>
  <body>
      <h1>Draw</h1>
      <hr>
      <applet code=DrawTest.class width=400 height=400>
    alt="Your browser understands the &lt;APPLET&gt; tag but isn't
     running the applet, for some reason."
    Your browser is completely ignoring the &lt;APPLET&gt; tag!
      </applet>
      <hr>
      <a href="DrawTest.java">The source</a>.
  </body>
</html>

B.2  DrawTest.java

import java.awt.event.*;
import java.awt.*;
import java.applet.*;
import java.util.Vector;
public class DrawTest extends Applet{
    DrawPanel panel;
    DrawControls controls;
    public void init() {
    panel = new DrawPanel();
        controls = new DrawControls(panel);
    add("Center", panel);
    add("South",controls);
    }
    public void destroy() {
        remove(panel);
        remove(controls);
    }
    public static void main(String args[]) {
    Frame f = new Frame("DrawTest");
    DrawTest drawTest = new DrawTest();
    drawTest.init();
    drawTest.start();
    f.add("Center", drawTest);
    f.setSize(300, 300);
    f.show();
    }
    public String getAppletInfo() {
        return "A simple drawing program.";
    }
}
class DrawPanel extends Panel implements MouseListener, MouseMotionListener {
    public static final int LINES = 0;
    public static final int POINTS = 1;
    int    mode = LINES;
    Vector lines = new Vector();
    Vector colors = new Vector();
    int x1,y1;
    int x2,y2;
    int xl, yl;
    public DrawPanel() {
    setBackground(Color.white);
    addMouseMotionListener(this);
    addMouseListener(this);
    }
    public void setDrawMode(int mode) {
    switch (mode) {
      case LINES:
      case POINTS:
        this.mode = mode;
        break;
       default:
        throw new IllegalArgumentException();
    }
    }
    public void mouseDragged(MouseEvent e) {
        e.consume();
        switch (mode) {
            case LINES:
                xl = x2;
                yl = y2;
                x2 = e.getX();
                y2 = e.getY();
                break;
            case POINTS:
            default:
                colors.addElement(getForeground());
                lines.addElement(new Rectangle(x1, y1, e.getX(), e.getY()));
                x1 = e.getX();
                y1 = e.getY();
                break;
        }
        repaint();
    }
    public void mouseMoved(MouseEvent e) {}
    public void mousePressed(MouseEvent e) {
        e.consume();
        switch (mode) {
            case LINES:
                x1 = e.getX();
                y1 = e.getY();
                x2 = -1;
                break;
            case POINTS:
            default:
                colors.addElement(getForeground());
                lines.addElement(new Rectangle(e.getX(), e.getY(), -1, -1));
                x1 = e.getX();
                y1 = e.getY();
                repaint();
                break;
        }
    }
    public void mouseReleased(MouseEvent e) {
        e.consume();
        switch (mode) {
            case LINES:
                colors.addElement(getForeground());
                lines.addElement(new Rectangle(x1, y1, e.getX(), e.getY()));
                x2 = xl = -1;
                break;
            case POINTS:
            default:
                break;
        }
        repaint();
    }
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    public void paint(Graphics g) {
    int np = lines.size();
    /* draw the current lines */
    g.setColor(getForeground());
    g.setPaintMode();
    for (int i=0; i < np; i++) {
        Rectangle p = (Rectangle)lines.elementAt(i);
        g.setColor((Color)colors.elementAt(i));
        if (p.width != -1) {
        g.drawLine(p.x, p.y, p.width, p.height);
        } else {
        g.drawLine(p.x, p.y, p.x, p.y);
        }
    }
    if (mode == LINES) {
        g.setXORMode(getBackground());
        if (xl != -1) {
        /* erase the last line. */
        g.drawLine(x1, y1, xl, yl);
        }
        g.setColor(getForeground());
        g.setPaintMode();
        if (x2 != -1) {
        g.drawLine(x1, y1, x2, y2);
        }
    }
    }
}
class DrawControls extends Panel implements ItemListener {
    DrawPanel target;
    public DrawControls(DrawPanel target) {
    this.target = target;
    setLayout(new FlowLayout());
    setBackground(Color.lightGray);
    target.setForeground(Color.red);
    CheckboxGroup group = new CheckboxGroup();
    Checkbox b;
    add(b = new Checkbox(null, group, false));
    b.addItemListener(this);
    b.setForeground(Color.red);
    add(b = new Checkbox(null, group, false));
    b.addItemListener(this);
    b.setForeground(Color.green);
    add(b = new Checkbox(null, group, false));
    b.addItemListener(this);
    b.setForeground(Color.blue);
    add(b = new Checkbox(null, group, false));
    b.addItemListener(this);
    b.setForeground(Color.pink);
    add(b = new Checkbox(null, group, false));
    b.addItemListener(this);
    b.setForeground(Color.orange);
    add(b = new Checkbox(null, group, true));
    b.addItemListener(this);
    b.setForeground(Color.black);
    target.setForeground(b.getForeground());
    Choice shapes = new Choice();
    shapes.addItemListener(this);
    shapes.addItem("Lines");
    shapes.addItem("Points");
    shapes.setBackground(Color.lightGray);
    add(shapes);
    }
    public void paint(Graphics g) {
    Rectangle r = getBounds();
    g.setColor(Color.lightGray);
    g.draw3DRect(0, 0, r.width, r.height, false);
        int n = getComponentCount();
        for(int i=0; i<n; i++) {
            Component comp = getComponent(i);
            if (comp instanceof Checkbox) {
                Point loc = comp.getLocation();
                Dimension d = comp.getSize();
                g.setColor(comp.getForeground());
                g.drawRect(loc.x-1, loc.y-1, d.width+1, d.height+1);
            }
        }
    }
  public void itemStateChanged(ItemEvent e) {
    if (e.getSource() instanceof Checkbox) {
      target.setForeground(((Component)e.getSource()).getForeground());
    } else if (e.getSource() instanceof Choice) {
      String choice = (String) e.getItem();
      if (choice.equals("Lines")) {
    target.setDrawMode(DrawPanel.LINES);
      } else if (choice.equals("Points")) {
    target.setDrawMode(DrawPanel.POINTS);
      }
    }
  }
}




File translated from TEX by TTH, version 2.80.
On 18 Aug 2001, 13:36.