数値計算レポート
担当:大石教授

G99P0985 中島求

提出日: 2001-08-13
提出期限: 2001-08-15

1  開発環境

CPU: PentiumII 400MHz
OS: Kondara MNU/Linux 2.0
KERNEL: Linux 2.4.4-18ksmp
CC: gcc-2.96-80k
LIBC: glibc-2.2.2-12k
LIBREADLINE: readline-4.1-14k → readline-2.2.1

2  mycalcのコンパイル

まずは、 [] にある前橋氏の電卓「mycalc」をコンパイル してみる。

手順通りにコンパイルしていくと、いきなりコンパイルエラー。

[q@krb mycalc]$ make
cc 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 -lfl -lm -lreadline
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tgetnum'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tgoto'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tgetflag'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `BC'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tputs'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `PC'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tgetent'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `UP'
/usr/lib/gcc-lib/i586-redhat-linux/2.96/../../../libreadline.so: undefined reference to `tgetstr'
collect2: ld returned 1 exit status
make: *** [mycalc] エラー 1

最後のリンカがエラーを吐いているようだ。まずは疑ったのがgcc。Kondara-devel-ja メーリングリストに入っているのでいろいろな情報が流れてくるが、その中には gcc2.9.6だと上手くコンパイルできないものがある、というものがあったのを 思いだした。早速、gcc-2.95.3で実験してみると、同じエラーを吐いて落ちた。 そこで次に疑ったのが、 [] にある以下の記述である。

入力ルーチンを置き換える方法は、処理系によって違ったりするようなんですが、 flexやFreeBSDの標準のlexでは、YY_INPUTマクロを置き換えることで行なうようです。
Linuxでは異っているかもしれない、と思ったが、
$ man lex

を抜粋してみると、問題ないことがわかった。

By  default  (and for purposes of efficiency), the scanner
uses block-reads rather than simple getc() calls  to  read
characters from yyin.  The nature of how it gets its input
can  be  controlled  by  defining  the   YY_INPUT   macro.
YY_INPUT's           calling          sequence          is
"YY_INPUT(buf,result,max_size)".  Its action is  to  place
up  to  max_size characters in the character array buf and
return in the integer variable result either the number of
characters  read  or  the  constant  YY_NULL  (0  on  Unix
systems) to indicate EOF.  The default YY_INPUT reads from
the global file-pointer "yyin".

ここで一旦投げ出しかけたが、readlineのChangelogにreadline4系は readline2系とは互換性があまりない、ということが書いてあったので、 readline-2.2.1をインストールし、Makefileを修正してreadline2系ライブラリ を参照するようにしてgcc-2.9.6でコンパイルしてみたところ、問題なく コンパイルできた。

[root@krb lib]# rpm -qi readline2.2.1
Name        : readline2.2.1                Relocations: /usr 
Distribution: Kondara MNU/Linux 2.0(Mary)       Vendor: (none)
Version     : 2.2.1                             Option: (none)
Release     : 4k                            Build Date: Sat Mar 17 00:01:57 2001
Install date: Sat Jul 28 17:10:53 2001      Build Host: mukiri.kondara.org
Group       : System Environment/Libraries   Source RPM: readline2.2.1-2.2.1-4k.nosrc.rpm
Size        : 204081                           License: GPL
Summary     : A library for reading and returning lines from a terminal.
Description :
The readline library reads a line from the terminal and returns it,
allowing the user to edit the line with standard Emacs editing keys.
The readline library allows programmers to provide an easy to use and
more intuitive interface for users.

This version of the readline package provides backwards compatibility
for programs built against Red Hat Linux 6.2.

とあるので、第4,5端末室やVineLinux2.1などRedHat6.2系Linuxでは 問題なくコンパイルできるはずである。

3  mycalcの仕様拡張

3.1  前橋氏の電卓にsin,cos,べき乗などの関数を組み込んでみよう

まず、sinを計算する関数を組み込んでみる。

前橋氏の電卓では、演算は全て「式」の形式で実装されている。 これをそのまま採用し、「sin式」を定義して実装することにした。

もちろん、tokenに''sin''を加えるなど、字句解析部も拡張する。 (これより、ソースコードを掲載するときは、前橋氏のmycalcから 変更があった部分のみを掲載する)

sin式の構文規則はこのようにする。

sin式=‘sin’‘(’式‘)’

例示すると、sin(3.1415)は有効、sin(10)は有効、そして、 sin(1+3)も有効である。

sin式=‘sin’‘(’実数値‘)’

にしようかどうか迷ったが、これにすると、sin(1+3)がエラーになってしまう。 しかし、上記の規則では、sin(if(…))と書けてしまう。 迷ったが、結局上記のものにした。

これを実装すると、このようになる。 (cacl.yに追加)

sin_expression
	: SIN LP expression RP 
	{
	  $$ = clc_create_sin_expression($3);
	}
	;

なお、下記の部分は、sin式を評価する際は、渡された式のみが 必要なので、3番目の引数、すなわち$3を渡せばよいことになる。

	  $$ = clc_create_sin_expression($3);

これを実装することにより芋蔓式に変更が必要になる。 clc_create_sin_expression(expression)の実装は 一旦置いておくとしてまずは字句解析部と構文解析部で必要な変更を 加える。

まず字句解析部だが、SINというtokenを定義する必要がある。 calc.lに以下を加える。

<INITIAL>"sin"          return SIN;

calc.yを以下のように変更する。
%token SIN 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 sin_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
	| sin_expression
	| LP expression RP
	{
	    $$ = $2;
	}
	| IDENTIFIER
	{
	    $$ = clc_create_identifier_expression($1);
	}
	| INT_LITERAL
	| DOUBLE_LITERAL
	;

次に実際の処理をするのだが、これはclc_create_sin_expression(expression)を 実装すればよい。

まずは定義する必要がある。calc.hファイルをいじる。

typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    SIN_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;

typedef struct {
  Expression    *inside_bracket ;
} SinExpression;


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;
      SinExpression           sin_expression;
    } u;
};

/* 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_sin_expression(Expression *inside_bracket);
Expression *clc_create_function_call_expression(char *func_name,
						Expression *expression);


そして、上記ソースコード抜粋の中でcreate.cをいじれ、と書いてあるのでそうする。 前橋氏の電卓の仕様では、EXPRESSIONの構築にはそのtypeが必要なので、それも 上記の定義を用いる。

Expression *
clc_create_sin_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(SIN_EXPRESSION);

  exp->u.sin_expression_inside_bracket = inside_bracket;

  return exp;
}

実際の処理はeval.cで定義されているので、そこにsin関数の処理を追加する。 Cのsin()関数はdoubleしか受け付けないので、doubleではないときは 括弧の中身は式であると見なして処理をする。 なお、eval.c内で式の種類を判別して処理内容を分けているので、 その判別ルーチンにsin式も追加する。

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 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 SIN_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) {
	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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}

static Value
eval_sin_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
	if (!inside_bracket.u.double_value){
	  result = sin(inside_bracket);
	} else {
	  result = eval_expression(env, inside_bracket);
	}
    }
    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 EQ_EXPRESSION:	/* FALLTHRU */
      case NE_EXPRESSION:	/* FALLTHRU */
      case GT_EXPRESSION:	/* FALLTHRU */
      case GE_EXPRESSION:	/* FALLTHRU */
      case LT_EXPRESSION:	/* FALLTHRU */
      case LE_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 SIN_EXPRESSION:
      v = eval_sin_expression(env, expr->u.sin_expression_inside_bracket);
      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;
}

これでsin式の処理が追加されたと思うので、コンパイルしてみる。 …いきなりエラー吐いて落ちた。

$ make
bison --yacc -dv calc.y
flex calc.l
cc -c -g lex.yy.c 
In file included from calc.l:7:
DBG.h:54:8: warning: extra tokens at end of #endif directive
In file included from calc.h:6,
                 from calc.l:8:
CLC.h:17:8: warning: extra tokens at end of #endif directive
cc -c -g y.tab.c 
In file included from calc.h:6,
                 from calc.y:3:
CLC.h:17:8: warning: extra tokens at end of #endif directive
cc -c -g -Wall create.c 
In file included from create.c:2:
DBG.h:54:8: warning: extra tokens at end of #endif directive
In file included from calc.h:6,
                 from create.c:3:
CLC.h:17:8: warning: extra tokens at end of #endif directive
create.c: In function `clc_create_sin_expression':
create.c:218: union has no member named `sin_expression_inside_bracket'
make: *** [create.o] エラー 1
$ 

create.cに追加した関数がエラーの原因と言っているので、それを修正する。 _を.に一箇所直す。

Expression *
clc_create_sin_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(SIN_EXPRESSION);

  exp->u.sin_expression.inside_bracket = inside_bracket;

  return exp;
}

これでコンパイルしてみる。 …またエラー吐いて落ちた。

$ make
cc -c -g -Wall eval.c 
In file included from eval.c:4:
DBG.h:54:8: warning: extra tokens at end of #endif directive
In file included from calc.h:6,
                 from eval.c:5:
CLC.h:17:8: warning: extra tokens at end of #endif directive
eval.c: In function `eval_sin_expression':
eval.c:319: request for member `u' in something not a structure or union
eval.c:320: incompatible type for argument 1 of `sin'
eval.c:320: incompatible types in assignment
eval.c:315: warning: unused variable `inside_bracket_val'
eval.c: In function `eval_expression':
eval.c:450: union has no member named `sin_expression_inside_bracket'
make: *** [eval.o] エラー 1
$ 

今度はeval.cが文句言っている。create.cでいじったところをこっちにも 反映しろ、と言っているのでそうする。_を.に一箇所直す。 さらに、eval_sin_expression()でもいろいろ文句を言っているので直す。 こちらは、返り値の型が合っていなかった。

static Value
eval_sin_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sin(inside_bracket_val.u.double_value);
      }
    }
    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 EQ_EXPRESSION:	/* FALLTHRU */
      case NE_EXPRESSION:	/* FALLTHRU */
      case GT_EXPRESSION:	/* FALLTHRU */
      case GE_EXPRESSION:	/* FALLTHRU */
      case LT_EXPRESSION:	/* FALLTHRU */
      case LE_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 SIN_EXPRESSION:
      v = eval_sin_expression(env, expr->u.sin_expression.inside_bracket);
      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;
}

これでコンパイルしてみる。 …通った!

$ make
cc -c -g -Wall eval.c 
In file included from eval.c:4:
DBG.h:54:8: warning: extra tokens at end of #endif directive
In file included from calc.h:6,
                 from eval.c:5:
CLC.h:17:8: warning: extra tokens at end of #endif directive
cc -c -g -Wall interface.c 
In file included from calc.h:6,
                 from interface.c:3:
CLC.h:17:8: warning: extra tokens at end of #endif directive
cc -c -g -Wall util.c 
In file included from calc.h:6,
                 from util.c:1:
CLC.h:17:8: warning: extra tokens at end of #endif directive
util.c: In function `clc_search_local_variable':
util.c:31: warning: implicit declaration of function `strcmp'
util.c: In function `clc_add_global_variable':
util.c:77: warning: implicit declaration of function `strlen'
util.c:78: warning: implicit declaration of function `strcpy'
cc -c -g -Wall error.c 
In file included from error.c:4:
DBG.h:54:8: warning: extra tokens at end of #endif directive
In file included from calc.h:6,
                 from error.c:5:
CLC.h:17:8: warning: extra tokens at end of #endif directive
cc 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 -lreadline2 -lfl -lm
$ 

動作実験をする。 まずは簡単な例から入れてみる。

[q@krb mycalc]$ ./mycalc 
>sin(3.1415);

…暴走した。Ctrl-Cで止めた。以下はtopコマンドの出力の抜粋である。

  PID USER     PRI  NI  SIZE  RSS SHARE STAT %CPU %MEM   TIME COMMAND
 1260 q         14   0   812  812   652 R    94.0  0.2   0:53 mycalc

なんでだろう…と思ってeval.c内のeval_sin_expression()を見たら、 あたり前だった。無限forループ内でbreakし忘れていた(爆)。 ついでに、intが渡された場合でも大丈夫なようにする。

static Value
eval_sin_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sin(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sin(inside_bracket_val.u.int_value);
	break;
      }
    }
    return result;
}

これでコンパイルしてみる。通ったので、メッセージは省略する。

動作確認をしてみる。 まずは簡単な例から、複雑な例へと進む。

$ ./mycalc 
>sin(3.1415);
>>0.000093
>sin(0.0);
>>0.000000
>sin(3.1415/2);
>>1.000000
>sin(1+3*(4+5));
>>0.270906
>sin(28);
>>0.270906
>sin(28.0);
>>0.270906
>
$ 

動いた!感動(涙)。ただ、上記使用だと、入力された式がintかdoubleを返さない場合、 どうなるかわからない。予想としては、forループからbreakする条件を永久に 満たさないので無限ループすると思う。というわけで、エラーを吐いて終了するように する。while文の処理(eval.c内のeval_while_expression())を見ると、 clc_runtime_error()という関数を呼びだせばエラー終了することがわかる。 この関数に、「引数をevalした際にdoubleかintが返ってこない」という エラーを認識させるように改造することにした。

error.cを見ると、エラーの種類の定義はcalc.hでなされているようなので、 calc.hもいじる必要がある。while文のエラー処理では、コンパイルエラーでは なく、ランタイムエラーとして処理している。これにならう。

calc.hは以下の部分を変更した。

typedef enum {
    VARIABLE_NOT_FOUND_ERR = 1,
    FUNCTION_NOT_FOUND_ERR,
    BOOLEAN_EXPECTED_ERR,
    ARGUMENT_TOO_MANY_ERR,
    ARGUMENT_TOO_FEW_ERR,
    INT_OR_DOUBLE_EXPECTED_ERR
    RUNTIME_ERROR_NUM,
} RuntimeError;

error.cは以下の部分を変更した。

static char *runtime_error_message[] = {
    "internal error: bad error message(0)",
    "variable not found. ",
    "function not found error. ",
    "boolean expected error. ",
    "argument too many error. ",
    "argument too few error. ",
    "int or double expected error.",
    "internal error: bad error message(MAX)"
};

当然、eval.cも変更する必要がある。以下の部分を変更した。

static Value
eval_sin_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sin(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sin(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    return result;

}

コンパイルする。エラーなく通ったので、メッセージは省略する。

動作実験をする。

$ ./mycalc 
>sin(3.00);
>>0.141120
>sin(3);
>>0.141120
>sin(3fdjfkdlfsl);
parse error. near token fdjfkdlfsl
>sin(1=1);
parse error. near token =
>sin(true);
variable not found.  (true)
>

なんとかintかdoubleにならない返り値を持つ式をsin()の中に入れようとしたが、 判定式では=でparse errorになってしまう…。というわけで例が見つからなかった ので、これでよしとした(爆)。

次にcos()を実装する。sin()と同じように実装することにする。 つまり、「cos式」なるものを定義して、それを処理する、という方式である。

cos式の構文規則はこのようにする。

cos式=‘cos’‘(’式‘)’

これをcalc.yに実装する。ついでにcosというtokenを定義する。

%token COS SIN 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 sin_expression cos_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
	| sin_expression
        | cos_expression
	| LP expression RP
	{
	    $$ = $2;
	}
	| IDENTIFIER
	{
	    $$ = clc_create_identifier_expression($1);
	}
	| INT_LITERAL
	| DOUBLE_LITERAL
	;

cos_expression
	: COS LP expression RP 
	{
	  $$ = clc_create_cos_expression($3);
	}
	;

ここまでで、字句解析部と構文解析部の実装を一気に行なった。 次に、clc_create_cos_expression(expression)を実装する。

まずは付随する定義などをする。

calc.hをいじる。

typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    SIN_EXPRESSION,
    COS_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;

typedef struct {
  Expression    *inside_bracket ;
} CosExpression;

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;
      SinExpression           sin_expression;
      CosExpression           cos_expression;
    } u;
};

/* 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_sin_expression(Expression *inside_bracket);
Expression *clc_create_cos_expression(Expression *inside_bracket);
Expression *clc_create_function_call_expression(char *func_name,
						Expression *expression);


clc_create_cos_expression()を実装する。 create.cをいじる。

Expression *
clc_create_cos_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(COS_EXPRESSION);

  exp->u.cos_expression.inside_bracket = inside_bracket;

  return exp;
}

eval_cos_expression()を実装する。 eval.cをいじる。

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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_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) {
	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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}

static Value
eval_cos_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = cos(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = cos(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    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 EQ_EXPRESSION:	/* FALLTHRU */
      case NE_EXPRESSION:	/* FALLTHRU */
      case GT_EXPRESSION:	/* FALLTHRU */
      case GE_EXPRESSION:	/* FALLTHRU */
      case LT_EXPRESSION:	/* FALLTHRU */
      case LE_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 SIN_EXPRESSION:
      v = eval_sin_expression(env, expr->u.sin_expression.inside_bracket);
      break;

    case COS_EXPRESSION:
      v = eval_cos_expression(env, expr->u.cos_expression.inside_bracket);
      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;
}

エラー処理もsin()と同じようにした。

コンパイルする。エラーなく通ったので、メッセージは省略する。 動作実験をする。

$ ./mycalc 
>cos(0);
function not found error.  name..cos
>cos(0.0);
function not found error.  name..cos
>sin(0.0);
>>0.000000
>sin(0); 
>>0.000000
>

動かない(涙)。

一回 make clean を実行した後にコンパイルしても駄目なので、 よくわからない。

…3日後、calc.lにtokenを追加するのを忘れていることが 判明。急いでcalc.lに追加する。なお、これは、羃乗を実装しようと して判明したことなので、羃乗のtokenも実装されている。

%%
<INITIAL>"pow"          return POW;
<INITIAL>"cos"          return COS;
<INITIAL>"sin"          return SIN;
<INITIAL>"define"	return DEFINE;
<INITIAL>"if"		return IF;
<INITIAL>"else"		return ELSE;
<INITIAL>"while"	return WHILE;
<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 MOD;
<INITIAL>[A-Za-z_][A-Za-z_0-9]* {
    yylval.identifier = clc_malloc(strlen(yytext) + 1);
    strcpy(yylval.identifier, yytext);
    return IDENTIFIER;
}

コンパイルをかける。エラーなく通ったので、メッセージは省略する。 動作実験をする。

$ ./mycalc 
>cos(0);
>>1.000000
>cos(0.0);
>>1.000000
>

動いた!

次に、羃乗を実装する。これも式にする。関数名などは、c言語と同じにした。 構文規則は以下のようになる。

pow式=‘pow’‘(’double値‘,’double値‘)’

しかし、pow(式, 式)などの表記もしたい人がいるかもしれないので、 以下のようにした。

pow式=‘pow’‘(’式‘,’式‘)’

powというtokenと上記の構文規則をcalc.yに実装する。

%token POW COS SIN 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 sin_expression cos_expression
        power_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
	| sin_expression
        | cos_expression
        | power_expression
	| LP expression RP
	{
	    $$ = $2;
	}
	| IDENTIFIER
	{
	    $$ = clc_create_identifier_expression($1);
	}
	| INT_LITERAL
	| DOUBLE_LITERAL
	;
power_expression
	: POW LP expression expression RP 
	{
	  $$ = clc_create_power_expression($3, $4);
	}
	;

次に、calc.hをいじる。

typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    SIN_EXPRESSION,
    COS_EXPRESSION,
    POWER_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;


typedef struct {
  Expression    *atai;
  Expression    *jyosu;
} PowerExpression;

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;
      SinExpression           sin_expression;
      CosExpression           cos_expression;
      PowerExpression         power_expression;
    } u;
};

/* 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_sin_expression(Expression *inside_bracket);
Expression *clc_create_cos_expression(Expression *inside_bracket);
Expression *clc_create_power_expression(Expression *atai, Expression *jyosu);
Expression *clc_create_function_call_expression(char *func_name,
						Expression *expression);

そして、IDENTIFIERとしてのPOWトークンをcalc.lに実装する。 (ここで、COSトークンの実装を忘れていたことを発見する。)

<INITIAL>"pow"          return POW;
<INITIAL>"cos"          return COS;
<INITIAL>"sin"          return SIN;
<INITIAL>"define"	return DEFINE;
<INITIAL>"if"		return IF;
<INITIAL>"else"		return ELSE;
<INITIAL>"while"	return WHILE;
<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 MOD;
<INITIAL>[A-Za-z_][A-Za-z_0-9]* {
    yylval.identifier = clc_malloc(strlen(yytext) + 1);
    strcpy(yylval.identifier, yytext);
    return IDENTIFIER;
}

次に、create.cをいじる。

Expression *
clc_create_power_expression(Expression *atai, Expression *jyosu)
{
  Expression *exp;
  exp = clc_alloc_expression(POWER_EXPRESSION);

  exp->u.power_expression.atai = atai;
  exp->u.power_expression.jyosu = jyosu;

  return exp;
}

さらに、eval.cをいじる。

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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_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) {
	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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}

static Value
eval_power_expression(LocalEnvironment *env, Expression *atai, Expression *jyosu)
{
  Value       atai_val;
  Value       jyosu_val;
    Value	result;

    for (;;) {
      atai_val = eval_expression(env, atai);
      if (atai_val.type == DOUBLE_VALUE) {
	jyosu_val = eval_expression(env, jyosu);
	if (jyosu_val.type == DOUBLE_VALUE) {
	  result.type = DOUBLE_VALUE;
	  result.u.double_value = pow(atai_val.u.double_value, jyosu_val.u.double_value);
	  break;
	} else if(jyosu_val.type == INT_VALUE) {
	  result.type = DOUBLE_VALUE;
	  result.u.double_value = pow(atai_val.u.double_value, jyosu_val.u.int_value);
	  break;
	} else {
	  clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
	}
      } else if(atai_val.type == INT_VALUE) {
	jyosu_val = eval_expression(env, jyosu);
	if (jyosu_val.type == DOUBLE_VALUE) {
	  result.type = DOUBLE_VALUE;
	  result.u.double_value = pow(atai_val.u.int_value, jyosu_val.u.double_value);
	  break;
	} else if(jyosu_val.type == INT_VALUE) {
	  result.type = DOUBLE_VALUE;
	  result.u.double_value = pow(atai_val.u.int_value, jyosu_val.u.int_value);
	  break;
	} else {
	  clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
	}
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    return result;
}

条件分岐が複雑だが、他にいい方法が思いつかなかった。

コンパイルをする。エラーが出力されなかったのでメッセージは省略する。

動作実験をする。

$ ./mycalc 
>pow(2,2);
>>4.000000
>pow(2,10);
>>1024.000000
>pow(3.14, 45);
>>23005631724245846327296.000000
>pow(2,-2);
>>0.250000
>pow(2,-5);
>>0.031250
>pow(2,0);
>>1.000000
>

動いた!

3.2  その他の独自拡張

こうなると、いちいち3.1415…と入力するのがバカらしくなるので、 例えばpiで補完できるようにしたくなる。Linux版glibc2.2.2のmath.h には、以下の定数が定義されている(math.hより抜粋)。

/* Some useful constants.  */
#if defined __USE_BSD || defined __USE_XOPEN
# define M_E            2.7182818284590452354   /* e */
# define M_LOG2E        1.4426950408889634074   /* log_2 e */
# define M_LOG10E       0.43429448190325182765  /* log_10 e */
# define M_LN2          0.69314718055994530942  /* log_e 2 */
# define M_LN10         2.30258509299404568402  /* log_e 10 */
# define M_PI           3.14159265358979323846  /* pi */
# define M_PI_2         1.57079632679489661923  /* pi/2 */
# define M_PI_4         0.78539816339744830962  /* pi/4 */
# define M_1_PI         0.31830988618379067154  /* 1/pi */
# define M_2_PI         0.63661977236758134308  /* 2/pi */
# define M_2_SQRTPI     1.12837916709551257390  /* 2/sqrt(pi) */
# define M_SQRT2        1.41421356237309504880  /* sqrt(2) */
# define M_SQRT1_2      0.70710678118654752440  /* 1/sqrt(2) */
#endif

まずは、三角関数を定義したので、使用頻度が高いと思われる pから定義することにした。

これは単純で、PIなるトークンを定義し、それを字句解析部で置きかえて しまえばよい。

calc.lをいじる。

#include <math.h>

<INITIAL>"pi" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_PI;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}

ちなみに、上記規則は

<INITIAL>[A-Za-z_][A-Za-z_0-9]* {
    yylval.identifier = clc_malloc(strlen(yytext) + 1);
    strcpy(yylval.identifier, yytext);
    return IDENTIFIER;
}

の前に入れないと、この規則の正規表現にひっかかってしまい、 コンパイルエラーとなるので注意する必要がある。

コンパイルは、エラーなく終了したのでメッセージは省略する。

動作実験をする。

$ ./mycalc 
>pi;
>>3.141593
>sin(pi);
>>0.000000
>cos(pi);
>>-1.000000
>sin(pi/2);
>>1.000000
>cos(pi/2);
>>0.00
>

動いた!

次に、math.hにある残りのものも実装してしまうことにした。

定数名は、それぞれC言語のものを流用することにした。 そのため、pの定数名も変更することにした。

calc.lを変更する。

<INITIAL>"M_PI" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_PI;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_E" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_E;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_LOG2E" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_LOG2E;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_LOG10E" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_LOG10E;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_LN2" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_LN2;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_LN10" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_LN10;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_PI_2" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_PI_2;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_PI_4" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_PI_4;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_1_PI" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_1_PI;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_2_PI" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_2_PI;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_2_SQRTPI" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_2_SQRTPI;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_SQRT2" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_SQRT2;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}
<INITIAL>"M_SQRT1_2" {
    Expression	*expression = clc_alloc_expression(DOUBLE_EXPRESSION);
    expression->u.double_value = M_SQRT1_2;
    yylval.expression = expression;
    return DOUBLE_LITERAL;
}

コンパイルは、エラーなく終了したのでメッセージは省略する。

動作実験をする。

$ ./mycalc 
>M_PI;
>>3.141593
>M_E;
>>2.718282
>M_LOG2E;
>>1.442695
>M_LN2;
>>0.693147
>M_LN10;
>>2.302585
>M_PI_2;
>>1.570796
>M_PI_4;
>>0.785398
>M_1_PI;
>>0.318310
>M_2_PI;
>>0.636620
>M_SQRT2;
>>1.414214
>M_SQRT1_2;
>>0.707107
>

動いた!

そして、よくよく考えると、例えばlog2eが定義されているのに、対数関数が 定義されていないのは意味がないので、指数関数と対数関数を定義することにした。 平方根に関しては、pow(羃乗)で代用できるので、いらないような気もしたが、 いちいち

> pow(値, 1/2);

と入力するのもバカバカしい話なので実装することにした。

まずは平方根関数から実装することにした。 これは、式として定義することにした。sin式と同じ方式である。

構文規則はこのようにする。

sqrt式=‘sqrt’‘(’式‘)’

まずはcalc.lをいじる。

<INITIAL>"sqrt"          return SQRT;

次にcalc.hをいじる。

typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    SIN_EXPRESSION,
    COS_EXPRESSION,
    POWER_EXPRESSION,
    SQRT_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;

typedef struct {
  Expression    *inside_bracket ;
} SqrtExpression;

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;
      SinExpression           sin_expression;
      CosExpression           cos_expression;
      PowerExpression         power_expression;
      SqrtExpression         sqrt_expression;
    } u;
};

/* 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_sin_expression(Expression *inside_bracket);
Expression *clc_create_cos_expression(Expression *inside_bracket);
Expression *clc_create_power_expression(Expression *atai, Expression *jyosu);
Expression *clc_create_sqrt_expression(Expression *inside_bracket);
Expression *clc_create_function_call_expression(char *func_name,
						Expression *expression);

次にcalc.yをいじる。

%token SQRT POW COS SIN 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 sin_expression cos_expression
        power_expression sqrt_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
	| sin_expression
        | cos_expression
        | power_expression
        | sqrt_expression
	| LP expression RP
	{
	    $$ = $2;
	}
	| IDENTIFIER
	{
	    $$ = clc_create_identifier_expression($1);
	}
	| INT_LITERAL
	| DOUBLE_LITERAL
	;

sqrt_expression
        : SQRT LP expression RP 
	{
	  $$ = clc_create_sqrt_expression($3);
	}
	;

次に、create.cをいじる。

Expression *
clc_create_sqrt_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(SQRT_EXPRESSION);

  exp->u.sqrt_expression.inside_bracket = inside_bracket;

  return exp;
}

次に、eval.cをいじる。処理の方法は、sinやcosとまったく同じで、 括弧内の式を評価した後、その値がdoubleかintかそれ以外で 条件分岐をする方法である。

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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_EXPRESSION:	/* FALLTHRU */
      case SQRT_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) {
	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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_EXPRESSION:	/* FALLTHRU */
      case SQRT_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}

static Value
eval_sqrt_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sqrt(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = sqrt(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    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 EQ_EXPRESSION:	/* FALLTHRU */
      case NE_EXPRESSION:	/* FALLTHRU */
      case GT_EXPRESSION:	/* FALLTHRU */
      case GE_EXPRESSION:	/* FALLTHRU */
      case LT_EXPRESSION:	/* FALLTHRU */
      case LE_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 SIN_EXPRESSION:
      v = eval_sin_expression(env, expr->u.sin_expression.inside_bracket);
      break;
    case COS_EXPRESSION:
      v = eval_cos_expression(env, expr->u.cos_expression.inside_bracket);
      break;
    case POWER_EXPRESSION:
      v = eval_power_expression(env, expr->u.power_expression.atai,
                                     expr->u.power_expression.jyosu);
      break;
    case SQRT_EXPRESSION:
      v = eval_sqrt_expression(env, expr->u.sqrt_expression.inside_bracket);
      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;
}

コンパイルする。エラーなく通ったので、メッセージは省略する。

動作実験をする。

$ ./mycalc 
>sqrt(4);
>>2.000000
>M_SQRT2;
>>1.414214
>sqrt(2);
>>1.414214
>

きちんと動いている。

ここまでくると流れ作業と化しているので、指数関数、対数関数は 一度に実装してしまう。

ユーザインタフェースは、C言語のものを採用する。つまり、 exp() 関数は自然対数の底(e)のx乗の値を返し、log() 関数はxの自然対数を返し、 log10() 関数は x の常用対数を返すようにする。

構文規則はそれぞれこのようにする。

exp式=‘exp’‘(’式‘)’
log式=‘log’‘(’式‘)’
log10式=‘log10’‘(’式‘)’

まずはcalc.lをいじる。

<INITIAL>"exp"          return EXP;
<INITIAL>"log"          return LOG;
<INITIAL>"log10"          return LOG10;

次にcalc.yをいじる。

%token EXP LOG LOG10 SQRT POW COS SIN 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 sin_expression cos_expression
        power_expression sqrt_expression exp_expression log_expression
        log10_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
	| sin_expression
        | cos_expression
        | power_expression
        | sqrt_expression
        | exp_expression
        | log_expression
        | log10_expression
	| LP expression RP
	{
	    $$ = $2;
	}
	| IDENTIFIER
	{
	    $$ = clc_create_identifier_expression($1);
	}
	| INT_LITERAL
	| DOUBLE_LITERAL
	;
exp_expression
        : EXP LP expression RP 
	{
	  $$ = clc_create_exp_expression($3);
	}
	;
log_expression
        : LOG LP expression RP 
	{
	  $$ = clc_create_log_expression($3);
	}
	;
log10_expression
        : LOG10 LP expression RP 
	{
	  $$ = clc_create_log10_expression($3);
	}
	;

次にcalc.hをいじる。

typedef enum {
    INT_EXPRESSION = 1,
    DOUBLE_EXPRESSION,
    IDENTIFIER_EXPRESSION,
    EXPRESSION_LIST_EXPRESSION,
    ASSIGN_EXPRESSION,
    ADD_EXPRESSION,
    SUB_EXPRESSION,
    MUL_EXPRESSION,
    DIV_EXPRESSION,
    MOD_EXPRESSION,
    EQ_EXPRESSION,
    NE_EXPRESSION,
    GT_EXPRESSION,
    GE_EXPRESSION,
    LT_EXPRESSION,
    LE_EXPRESSION,
    MINUS_EXPRESSION,
    IF_EXPRESSION,
    WHILE_EXPRESSION,
    SIN_EXPRESSION,
    COS_EXPRESSION,
    POWER_EXPRESSION,
    SQRT_EXPRESSION,
    EXP_EXPRESSION,
    LOG_EXPRESSION,
    LOG10_EXPRESSION,
    FUNCTION_CALL_EXPRESSION,
    EXPRESSION_TYPE_NUM
} ExpressionType;

typedef struct {
  Expression    *inside_bracket ;
} ExpExpression;

typedef struct {
  Expression    *inside_bracket ;
} LogExpression;

typedef struct {
  Expression    *inside_bracket ;
} Log10Expression;

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;
        SinExpression           sin_expression;
        CosExpression           cos_expression;
        PowerExpression         power_expression;
        SqrtExpression          sqrt_expression;
        ExpExpression           exp_expression;
        LogExpression           log_expression;
        Log10Expression         log10_expression;
    } u;
};
/* 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_sin_expression(Expression *inside_bracket);
Expression *clc_create_cos_expression(Expression *inside_bracket);
Expression *clc_create_power_expression(Expression *atai, Expression *jyosu);
Expression *clc_create_sqrt_expression(Expression *inside_bracket);
Expression *clc_create_exp_expression(Expression *inside_bracket);
Expression *clc_create_log_expression(Expression *inside_bracket);
Expression *clc_create_log10_expression(Expression *inside_bracket);
Expression *clc_create_function_call_expression(char *func_name,
						Expression *expression);

次にcreate.cをいじる。

Expression *
clc_create_exp_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(EXP_EXPRESSION);

  exp->u.exp_expression.inside_bracket = inside_bracket;

  return exp;
}

Expression *
clc_create_log_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(LOG_EXPRESSION);

  exp->u.log_expression.inside_bracket = inside_bracket;

  return exp;
}

Expression *
clc_create_log10_expression(Expression *inside_bracket)
{
  Expression *exp;
  exp = clc_alloc_expression(LOG10_EXPRESSION);

  exp->u.log10_expression.inside_bracket = inside_bracket;

  return exp;
}

次にeval.cをいじる。

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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_EXPRESSION:	/* FALLTHRU */
      case SQRT_EXPRESSION:	/* FALLTHRU */
      case EXP_EXPRESSION:	/* FALLTHRU */
      case LOG_EXPRESSION:	/* FALLTHRU */
      case LOG10_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) {
	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 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 SIN_EXPRESSION:	/* FALLTHRU */
      case COS_EXPRESSION:	/* FALLTHRU */
      case POWER_EXPRESSION:	/* FALLTHRU */
      case SQRT_EXPRESSION:	/* FALLTHRU */
      case EXP_EXPRESSION:	/* FALLTHRU */
      case LOG_EXPRESSION:	/* FALLTHRU */
      case LOG10_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}
static Value
eval_exp_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = exp(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = exp(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    return result;
}
static Value
eval_log_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = log(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = log(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    return result;
}
static Value
eval_log10_expression(LocalEnvironment *env, Expression *inside_bracket)
{
    Value	inside_bracket_val;
    Value	result;

    for (;;) {
      inside_bracket_val = eval_expression(env, inside_bracket);
      if (inside_bracket_val.type == DOUBLE_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = log10(inside_bracket_val.u.double_value);
	break;
      } else if (inside_bracket_val.type == INT_VALUE) {
	result.type = DOUBLE_VALUE;
	result.u.double_value = log10(inside_bracket_val.u.int_value);
	break;
      } else {
	clc_runtime_error(INT_OR_DOUBLE_EXPECTED_ERR, NULL);
      }
    }
    return result;
}

コンパイルをする。エラーが出力されなかったのでメッセージは省略する。

動作実験をする。

$ ./mycalc 
>exp(1);
>>2.718282
>M_E;
>>2.718282
>log(M_E);
>>1.000000
>log10(10);
>>1.000000
>log10(1);
>>0.000000
>

動いた!

4  感想

図らずもテストで著しく悪い点(部分点が無いとすると自己採点0点)を 取ってしまったので、頑張らざるを得なくなってしまった…。 特に、柏木先生の分はレポート点が解らないので、単位がくるかどうか…。

mycalcのコンパイルが通るまで6時間を費やしてしまった。不覚。

前橋氏の解説ページでは、「詳しいことはソースを読め」という説明(?)が 多く、慣れないCの文法を読むのに苦労した。

8/12以降、理工UNIXシステムが停止し、DNSまでもが引けなくなってしまった。 ウェブは見られない、メールは出せない、のでどうやって提出しようかパニックに 陥った。

References

[]
前橋和弥. その4 「電卓を作ってみよう」. 2000-06-15.

http://member.nifty.ne.jp/maebashi/programmer/c_yota/calc.html




File translated from TEX by TTH, version 2.80.
On 23 Aug 2001, 13:49.