前橋和弥氏のページ ここの「電卓を作ってみよう」がある。 (http://member.nifty.ne.jp/maebashi/programmer/c_yota/calc.html)参照
(M+) 前橋氏の電卓にsin,cos,べき乗などの関数を組み込んでみよう。また,ユーザ定義関数の返す値を指定できるようにしよう。(これぐらいできると100点)。 (M++) 前橋氏の電卓に行列ができるようにしてみよう。もちろん,加減乗算はBLASの関数を使う。また,ついでだから, Ax=b を > x=A\b でできるようにしてみよう。ここまでできたら200点。複素数も扱えれば+20で,複素行列も扱えれば+30とする。複素行列も扱えるようになったら,固有値問題も解けるようにしてみたい。 > [P D] = eig(A) で行列Aの固有値(を対角要素に並べた行列)Dと対応する固有ベクトルを並べた行列Pが計算できるようにしたい(これができたら+30点)。 (M+++) 前橋氏の電卓でgcでメモリの確保をしたらどうなるだろう(これができたら+30点)。
flex [-bcdfinpstvFILT8] [-C[efmF]] [-Sskeleton] [file ...]一般的には、単に「flex」に続けて処理すべきファイル名を入力すればよい。ファイル名の末尾は「.l」とし、FlexもしくはLexの記述ファイルであることを示唆する。ファイル(filename.l)を読み込み、そこに記述されたパターンを認識するスキャナ機能を持つ「lex.yy.c」という名前のC言語ファイルを生成し、記述情報の中になんらかのエラーがあれば、 対応するエラー・メッセージをstderrに出力する。
bison filename.y単に「bison」に続けて処理すべきファイル名を入力すればよい。オプションは、伝統的な一文字のオプションと、記憶しやすいオプション名の両方を受け付けるため、長いオプション名は「-」の代わりに、「--」で指定する。ファイル名の末尾は「.y」とし、YaccもしくはBisonの記述ファイルであることを示唆する慣例を使う。生成される構文解析器ファイルは、文法ファイル名の.yを「.tab.c」に変えたものである。従って、「filename.y」からは、「filename.tab.c」を得る。
<第四端末室> OS Red Hat Linux release 6.2 (Zoot) CPU Pentium III クロック 800MHz メモリ 256MB <自宅> OS windows98+cygwin CPU Intel Celeron クロック 500MHz メモリ 64MB学校が閉鎖されてしまったので、途中からは家のcygwinで実行した。
<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を生成したということも体感できた。
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.これで取りあえず、動く電卓はできた。以下が実行結果である。
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
>a = 3; /* 変数aに3を定義する */ >>3 >50*a; /* 変数aを値として使用できる */ >>150 >b = -10.5; /* double型の変数も可能 */ >>-10.500000 >c = a * b; /* 新しい変数に、変数での計算値を代入可能 */ >>-31.500000 >c; >>-31.500000
>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
>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ここでは、関数内の変数と、関数外の変数を区別することができる。グローバル変数とローカル変数の区別ができるのである。
<実行例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 > となる。改行を入力しなければ複数の命令もこなす。 つまり改行コードを認識するまで命令の入力を待っている。
<INITIAL>"for" return FOR;を追加した。これによって、文字"for"を認識し、FORというコードをアクションとして返す。
%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文の内容について実装する。(命令を生成する。)
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; }ここで、ヘッダファイルの定義を変えておく。
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文のためのヘッダファイルの定義は整った。次は解析木を生成するプログラムの編集である。
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));関数を呼ぶ。
>b = 11; >>11 >b; >>11 >for( a = 1 ; a < 5 ; a = a + 1 ) { b = b + 1 } ; >>5 >b; >>15 >a; >>5実行結果からfor文の拡張が正しく行えたといえる。
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正しく拡張されていることがわかる。
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正しく拡張された。拡張内容についてはしっかり理解していたつもりだった。しかし実は、変数も引数として持つことが出来るというところが何度やってもエラーになってしまっていた。単純ミスだったのだが、これを発見するのにかなり時間を費やしてしまった。 最終的に動いてとてもうれしかった。
<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を終了したあとにはまた、インタプリタとしてそのまま命令を実行することが出来る。すなわち起動状態のままであり、繰り返し実行することが可能である。
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の三つのクラスをこのインタプリタと同じディレクトリにおいておく必要がある。
$ 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のコンパイルを先に行うのは、そのほうがエラーがないということなのでそうしたのである。次にソースの解説に移る。
" \ [ ] ^ - ? . * + | ( ) $ / { } % < >これらの意味は一般的な正規表現の意味とほぼ同じであるのが多い。もし、これらのオペレータを文字として使用したい場合には、"または\を使用してやればよい。プログラムを見ていくと、最初の%{ から %}は、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文字にマッチするので)が来た場合である。よって、エラーにする。
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;を使ってバッファリングする。
%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などに展開されていく。この規則は、簡単なので文法自体の説明は省略する。
definition_or_expression : function_definition | expression SEMICOLON { clc_eval_expression($1); } | error { yyclearin; } ;「error」というトークンは、前述通り予約語でありエラーにマッチする。つまり、関数定義や、式の途中で文法エラーが発生したら、破棄するのである。アクションの中のyyclearinは、先読みしてあるトークンを破棄する。1つエラーがあり、そこまでの入力を破棄した場合次のトークンが正しいという保証はほとんどない。なので、yaccでは、「最低3つのトークンをエラーなしで読み込めた時」までは、エラーの報告を抑止する。 そして、上のプログラムの、yyclearin; の下に、
yyerrok;を付け加えると、即座に正常状態に復帰するようになる。しかし、このインタプリタの規則からすると、式の入力ではセミコロン、関数定義の入力では'}'という、終端になる記号があるので、
| expression error SEMICOLONなどとして、セミコロンまでを読み飛ばすようにした方が良いともいえるがここではそうしなかった。
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が保持している。
数値計算用インタプリタとしては、blasの関数が使えたほうがいいと思うし、行列の固有値も計算できたほうが良かったと思う。また、複素数もlapackの関数内に入っているようなのでそれについても出来たほうが良かった。実際、行列や複素数をライブラリを使って扱えるようにしようと、CLAPACKをダウンロードして、いざインストールしようとしたらエラーが発生して、色々やってみたけど結局できなかった。なんとか使ってみたいので、インストールを成功させて使用してみたい。他にも課題に書いてあることには全部一応挑戦してはみたのだが、どれもこれも理論上はこうしてこうしてこうやればいいと分かっているつもりだったのだがなんかうまくいかなくて悔しかった。その他、cygwinにRubyを組み込むと色々便利らしいということが分かった。Rubyの特徴としては、シンプルな文法・普通のオブジェクト指向機能(クラス,メソッドコールなど)・特殊なオブジェクト指向機能(Mixin, 特異メソッドなど)・演算子オーバーロード・例外処理機能・イテレータとクロージャ・ガーベージコレクタ・ダイナミックローディング (アーキテクチャによる)・そして、移植性が高い.多くのUNIX上で動くだけでなく,DOSやWindows, Mac,BeOSなどの上でも動くということが挙げられる。これも次にやってみたいことの1つとなった。
%{ #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; }
%{ #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); } ; %%
#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 */
#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; }
#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(); }
<html> <head> <title>Draw</title> </head> <body> <h1>Draw</h1> <hr> <applet code=DrawTest.class width=400 height=400> alt="Your browser understands the <APPLET> tag but isn't running the applet, for some reason." Your browser is completely ignoring the <APPLET> tag! </applet> <hr> <a href="DrawTest.java">The source</a>. </body> </html>
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); } } } }