前回は四則演算を行う簡単な電卓プログラムを作りました。今回は電卓プログラムに変数と関数の機能を追加してみましょう。
前回作成した電卓は、計算結果を表示したあとそれを保持していないので、計算結果を再利用することができません。一般の電卓のように、計算結果を記憶しておくメモリ機能があると便利です。この機能を「変数 (variable)」として実現することにします。プログラミング言語で言えば、大域変数 (グローバル変数) と同じ機能になります。
変数を実装するのであれば、変数に値を代入する操作が必要になります。文法に「文」を定義する、つまり「代入文」を追加する方法もありますが、今回は簡単な電卓プログラムなので、代入演算子 "=" を用意して式の中で処理することにしましょう。代入演算子は右辺の式の値を左辺の変数に代入するので、文法は次のように表すことができます。
[EBNF] 式 = 代入式 | 式1. 代入式 = 変数, "=", 式. 式1 = 項, { ("+" | "-"), 項 }. 項 = 因子, { ("*" | "/"), 因子 }. 因子 = 数値 | ("+" | "-"), 因子 | "(", 式, ")" | 変数. 変数 = 識別子 [注意] 数値と識別子の定義は省略
演算子 = は他の演算子と違って右結合になることに注意してください。このため、他の演算子よりも優先順位を低くし、右辺の式の評価を優先して行います。そして、その結果を変数にセットします。文法では、式を 代入式 | 式1 に変更し、代入式で演算子 = の処理を行います。式1は今までの式の定義と同じです。これで演算子 = の優先順位を低くすることができます。あとは代入式の処理で、右辺の式を先に評価して、その結果を変数にセットすればいいわけです。
それから、因子に「変数」を追加します。変数の定義は「識別子」とし、識別子はアルファベットで構成されるものとします。電卓プログラムではそれをシンボルに変換し、シンボルを変数として扱います。なお、変数の値は因子の処理で求めると、代入式で左辺の変数が数値に変換されるため、右辺の値をセットすることができなくなります。そこで、因子の処理は変数を表すシンボルをそのまま返すことにします。変数の値は実際に演算を行うときに求めることにしましょう。
次は文法に関数を追加しましょう。関数の処理は「因子」に追加します。
[EBNF] 式 = 代入式 | 式1. 代入式 = 変数, "=", 式. 式1 = 項, { ("+" | "-"), 項 }. 項 = 因子, { ("*" | "/"), 因子 }. 因子 = 数値 | ("+" | "-"), 因子 | "(", 式, ")" | 変数 | 関数, "(", 引数リスト, ")". 変数 = 識別子 関数 = 識別子 引数リスト = 式, { ",", 式 }. [注意] 数値と識別子の定義は省略
関数の名前は識別子とし、そのあとに引数をカッコで囲んで渡します。カッコの中は「引数リスト」として定義します。これは「式」をカンマで区切って並べたもので、一般的な手続き型言語の関数呼び出しと同じ形式になります。
ただし、変数と関数は同じ識別子なので、このままでは区別することができません。この場合、簡単な方法が 2 つあります。ひとつは関数として登録されている識別子を関数とする方法、もうひとつは次のトークンが左カッコ (lpar) であれば関数とする方法です。今回は前者の方法を採用することにしましょう。
それではプログラムを作ります。最初に、変数と関数を格納する大域変数とアクセス関数を定義します。次のリストを見てください。
リスト : 関数と変数のアクセス関数 ;;; 組み込み関数 (defvar *primitive-function* `((exp . ,#'exp) (log . ,#'log) (sin . ,#'sin) (cos . ,#'cos) (tan . ,#'tan) (asin . ,#'asin) (acos . ,#'acos) (atan . ,#'atan) (sqrt . ,#'sqrt) (expt . ,#'expt))) ;;; 関数を求める (defun lookup-function (name) (assoc name *primitive-function*)) ;;; 変数を格納する (defvar *variable* '()) ;;; 変数の値を求める (defun lookup-variable (var) (let ((cp (assoc var *variable*))) (if cp (cdr cp) (error "unbound variable ~a" var)))) ;;; 変数の値を更新する (defun update-variable (var val) (let ((cp (assoc var *variable*))) (if cp (rplacd cp val) (push (cons var val) *variable*))))
組み込み関数はスペシャル変数 *PRIMITIVE-FUNCTION* に連想リストの形式で格納します。関数 lookup-function は名前が name の組み込み関数があるか *PRIMITIVE-FUNCTION* から探します。
変数はスペシャル変数 *VARIABLE* に連想リストの形式で格納します。関数 lookup-variable は *VARIABLE* から変数 VAR を探索します。見つからない場合はエラーを通知します。関数 update-variable は変数の値を更新します。変数 VAR が既に存在する場合は、replacd でその CDR 部の値を VAL に書き換えます。VAR が存在しない場合は、push で (cons var val) を *VARIABLE* に追加します。
次は関数 get-token を修正します。
リスト : 字句解析の修正 ;;; 識別子 (defun get-ident () (let (buff) (loop while (alpha-char-p (getch)) do (push (getch) buff) (nextch)) (read-from-string (concatenate 'string (reverse buff))))) ;;; トークンの切り分け (defun get-token () ;; 空白文字の読み飛ばし (loop while (white-space-p (getch)) do (nextch)) (cond ((digit-char-p (getch)) (setq *token* 'number *value* (get-number))) ((alpha-char-p (getch)) (setq *token* 'ident *value* (get-ident))) (t (case (getch) ((#\=) (setq *token* '=) (nextch)) ((#\+) (setq *token* '+) (nextch)) ((#\-) (setq *token* '-) (nextch)) ((#\*) (setq *token* '*) (nextch)) ((#\/) (setq *token* '/) (nextch)) ((#\() (setq *token* 'lpar) (nextch)) ((#\)) (setq *token* 'rpar) (nextch)) ((#\,) (setq *token* 'comma) (nextch)) ((#\;) (setq *token* 'semic) (nextch)) (t (setq *token* 'others))))))
記号 (getch) がアルファベットの場合は関数 get-ident で識別子を切り分けます。*TOKEN* にはトークン IDENT をセットし、get-ident の返り値を *VALUE* にセットします。get-ident はアルファベットを変数 BUFF に格納し、それを文字列に変換したあと、read-from-string でシンボルに変換して返します。あとは、代入演算子 '=' とカンマ ',' が入力された場合、それを表すトークン = と COMMA を *TOKEN* にセットするだけです。
次は構文解析を修正します。まず最初に、代入演算子の処理を expression に追加します。次のリストを見てください。
リスト : expression の修正 ;;; 変数の評価 (defun eval-var (var) (if (numberp var) var (lookup-variable var))) ;;; 演算子の評価 (defun eval-op (op var1 var2) (funcall op (eval-var var1) (eval-var var2))) ;;; 式 (加減算の処理) (defun expr1 (&aux (val (term))) (loop (case *token* (+ (get-token) (setq val (eval-op #'+ val (term)))) (- (get-token) (setq val (eval-op #'- val (term)))) (t (return val))))) ;;; 式 (defun expression (&aux (val (expr1))) (case *token* ((=) ;; 代入演算子の処理 (unless (symbolp val) (error "invalid assignment form")) (get-token) (let ((val1 (eval-var (expression)))) (update-variable val val1) val1)) (t val)))
演算子 +, - の処理は関数 expr1 で行い、演算子 = の処理を expression で行います。expression は最初に expr1 を評価して、その返り値を変数 VAL にセットします。*TOKEN* が = の場合は代入式の処理を行います。
まず VAL の値をチェックして、シンボルでなければエラーを送出します。次に、expression を呼び出して右辺の式を評価して、返り値を関数 eval-var に渡して値を取得します。eval-var は引数が数値ならばそのまま返し、そうでなければ lookup-variable で変数の値を求めます。あとは、update-variable で変数の値を VAL1 に更新してから VAL1 を返します。
関数 expr1 は今までの expression と同じです。expr1 と term は演算子を評価するとき関数 eval-op を呼び出すように修正します。eval-op は引数を eval-var で評価してから演算子 OP を評価します。あとの処理は今までと同じです。関数 term も同じように修正します。
次は関数 factor を修正します。
リスト : 因子の修正 ;;; 実引数の取得 (defun get-argument () (get-token) (let ((a '())) (loop (let ((val (eval-var (expression)))) (case *token* ((rpar) (get-token) (return (reverse (cons val a)))) ((comma) (get-token) (push val a)) (else (error "unexpected token in argument list ~a" *token*))))))) ;;; 因子 (defun factor () (case *token* (lpar (get-token) (let ((val (expression))) (if (eq *token* 'rpar) (get-token) (error "')' expected")) val)) (number (prog1 *value* (get-token))) (+ ;; 単項演算子 (get-token) (factor)) (- ;; 単項演算子 (get-token) (- (factor))) (ident (let ((func (lookup-function *value*))) (cond (func ;; 関数呼び出し (get-token) (if (eq *token* 'lpar) (apply (cdr func) (get-argument)) (error "'(' expected"))) (t ;; 変数 (prog1 *value* (get-token)))))) (t (error "unexpected token ~a" *token*))))
*TOKEN* が IDENT の場合、変数または関数呼び出しの処理を行います。最初に lookup-function を呼び出し、識別子 *VALUE* が組み込み関数かチェックします。そうであれば、組み込み関数を呼び出します。引数の評価は関数 get-argument で行います。その前に、get-token を呼び出して次のトークンを求め、それが左カッコ (LPAR) であることを確認します。そうでなければエラーを通知します。組み込み関数でなければ変数なので、*VALUE* をそのまま返します。
get-argument はカンマで区切られた式を expression で評価し、その結果をリストに格納して返します。expression を評価したあと、case で *TOKEN* をチェックします。右カッコ (LPAR) であれば、式の値 VAL を累積変数 A に追加して reverse で反転して返します。カンマ (COMMA) であれば、まだ引数があるので次の式を評価します。そうでなければ、式に誤りがあるのでエラーを通知します。
あとは関数 toplevel で、expression の返り値を eval-var で評価する処理を追加するだけです。詳細はプログラムリスト1をお読みください。
それでは実行してみましょう。
* (calc) Calc> a = 10; => 10 Calc> a; => 10 Calc> a * 10 + 20; => 120 Calc> b = a * 10 + 20; => 120 Calc> a + b; => 130 Calc> 1 + (c = 2 + 3 + 4) + 5 + 6; => 21 Calc> c; => 9 Calc> x = y = z = 0; => 0 Calc> x; => 0 Calc> y; => 0 Calc> z; => 0 Calc> p = p + 1; ERROR: unbound variable P Calc> q = 10; => 10 Calc> q; => 10 Calc> q = q + 1; => 11
変数に値を代入すると、その値を使って式を評価することができます。また、式の中に演算子 = が入っていても、その式を評価することができます。X = Y = Z = 0; のように、多重代入することも可能です。ただし、新しい変数 P で P = P + 1; のようなことはできません。Q = 10; を評価したあとならば、既に変数 Q は定義されているので、Q = Q + 1; は評価することができます。
次は組み込み関数を実行してみましょう。
Calc> sqrt(2); => 1.4142135 Calc> sqrt(2d0); => 1.4142135623730951d0 Calc> expt(2, 32); => 4294967296 Calc> PI = asin(0.5d0) * 6; => 3.1415926535897936d0 Calc> sin(0); => 0.0 Calc> sin(pi / 2); => 1.0d0 Calc> sin(pi); => -3.216245299353273d-16
正常に動作していますね。
変数や関数呼び出しは、構文木を構築してから処理することもできます。最初に、代入演算子と関数呼び出しに対応する構文木を定義します。
リスト : 構文木の定義 (defstruct op1 op rexpr) ; 単項演算子 (defstruct op2 op lexpr rexpr) ; 二項演算子 (defstruct agn var expr) ; 代入演算子 (defstruct cal fn args) ; 関数呼び出し
構造体 AGN は代入演算子を表します。スロット VAR が左辺の変数、EXPR が右辺式です。構造体 CAL は関数呼び出しを表します。スロット FN が呼び出す関数、ARGS が FN に渡す引数です。なお、構文木には変数も含まれることに注意してください。
構文解析のプログラムは次のようになります。
リスト : 構文解析 ;;; 因子 (defun factor () (case *token* (lpar (get-token) (let ((val (expression))) (if (eq *token* 'rpar) (get-token) (error "')' expected")) val)) (number (prog1 *value* (get-token))) (+ ;; 単項演算子 (get-token) (make-op1 :op #'+ :rexpr (factor))) (- ;; 単項演算子 (get-token) (make-op1 :op #'- :rexpr (factor))) (ident (let ((func (lookup-function *value*))) (cond (func ;; 関数呼び出し (get-token) (if (eq *token* 'lpar) (make-cal :fn (cdr func) :args (get-argument)) (error "'(' expected"))) (t ;; 変数 (prog1 *value* (get-token)))))) (t (error "unexpected token ~a" *token*)))) ;;; 式 (defun expression (&aux (val (expr1))) (case *token* (= ;; 代入演算子の処理 (unless (symbolp val) (error "invalid assignment form")) (get-token) (make-agn :var val :expr (expression))) (t val)))
関数 factor では、関数呼び出しを処理するところで、関数 make-cal で構文木を生成して返します。関数 get-argument は構文木を格納したリストを返すように修正します。関数 expression では、代入演算子を処理するところで、関数 make-agn で構文木を生成して返します。
最後に、構文木を評価する関数 eval-expr を修正します。
リスト : 構文木の評価 (defun eval-expr (expr) (cond ((numberp expr) expr) ((symbolp expr) (lookup-variable expr)) ((op2-p expr) (funcall (op2-op expr) (eval-expr (op2-lexpr expr)) (eval-expr (op2-rexpr expr)))) ((op1-p expr) (funcall (op1-op expr) (eval-expr (op1-rexpr expr)))) ((agn-p expr) (let ((val (eval-expr (agn-expr expr)))) (update-variable (agn-var expr) val) val)) ((cal-p expr) (apply (cal-fn expr) (mapcar (lambda (e) (eval-expr e)) (cal-args expr)))) (t (error "invalid expression type ~a" expr))))
(symbolp expr) が真ならば、変数の値を関数 lookup-variable で求めます。(agn-p expr) が真ならば変数に値を代入します。eval-expr を再帰呼び出しして右辺式 (ang-expr expr) を評価し、その結果を変数 VAL にセットします。そして、関数 update-variable で変数の値を更新してから VAL を返します。
(cal-p expr) が真ならば関数 (cal-fn expr) を呼び出します。関数に渡す引数 (cal-args expr) は構文木なので、eval-expr を再帰呼び出しして値を求めます。この処理は mapcar を使えば簡単ですね。それから、apply で関数 (cal-fn expr) を呼び出して、その結果を返します。
あとの修正は簡単なので説明は割愛させていただきます。詳細はプログラムリスト2をお読みください。興味のある方は実際にプログラムを動かして、いろいろ試してみてください。
;;; ;;; calc1.lisp : 電卓プログラム ;;; ;;; Copyright (C) 2020 Makoto Hiroi ;;; ;;; スペシャル変数 (defvar *ch*) (defvar *token*) (defvar *value*) ;;; 記号の読み込み (defun nextch () (setq *ch* (read-char))) ;;; 先読み記号の取得 (defun getch () *ch*) ;;; 組み込み関数 (defvar *primitive-function* `((exp . ,#'exp) (log . ,#'log) (sin . ,#'sin) (cos . ,#'cos) (tan . ,#'tan) (asin . ,#'asin) (acos . ,#'acos) (atan . ,#'atan) (sqrt . ,#'sqrt) (expt . ,#'expt))) ;;; 関数を求める (defun lookup-function (name) (assoc name *primitive-function*)) ;;; 変数を格納する (defvar *variable* '()) ;;; 変数の値を求める (defun lookup-variable (var) (let ((cp (assoc var *variable*))) (if cp (cdr cp) (error "unbound variable ~a" var)))) ;;; 変数の値を更新する (defun update-variable (var val) (let ((cp (assoc var *variable*))) (if cp (rplacd cp val) (push (cons var val) *variable*)))) ;;; 数値用の文字か? (defun number-char-p (c) (or (digit-char-p c) (find c '(#\. #\+ #\- #\d #\D)))) ;;; 数値を求める (defun get-number () (let (buff) (loop while (number-char-p (getch)) do (push (getch) buff) (nextch)) (setq buff (concatenate 'string (reverse buff))) (multiple-value-bind (num len) (read-from-string buff) (if (and (numberp num) (= (length buff) len)) num (error "invalid number ~a~%" buff))))) ;;; 識別子 (defun get-ident () (let (buff) (loop while (alpha-char-p (getch)) do (push (getch) buff) (nextch)) (read-from-string (concatenate 'string (reverse buff))))) ;;; 空白文字の判定 (defun white-space-p (c) (or (char= c #\ ) (not (graphic-char-p c)))) ;;; トークンの切り分け (defun get-token () ;; 空白文字の読み飛ばし (loop while (white-space-p (getch)) do (nextch)) (cond ((digit-char-p (getch)) (setq *token* 'number *value* (get-number))) ((alpha-char-p (getch)) (setq *token* 'ident *value* (get-ident))) (t (case (getch) ((#\=) (setq *token* '=) (nextch)) ((#\+) (setq *token* '+) (nextch)) ((#\-) (setq *token* '-) (nextch)) ((#\*) (setq *token* '*) (nextch)) ((#\/) (setq *token* '/) (nextch)) ((#\() (setq *token* 'lpar) (nextch)) ((#\)) (setq *token* 'rpar) (nextch)) ((#\,) (setq *token* 'comma) (nextch)) ((#\;) (setq *token* 'semic) (nextch)) (t (setq *token* 'others)))))) ;;; ;;; 式の評価 ;;; (declaim (ftype (function () t) expression)) ;;; 変数の評価 (defun eval-var (var) (if (numberp var) var (lookup-variable var))) ;;; 演算子の評価 (defun eval-op (op var1 var2) (funcall op (eval-var var1) (eval-var var2))) ;;; 実引数の取得 (defun get-argument () (get-token) (let ((a '())) (loop (let ((val (eval-var (expression)))) (case *token* ((rpar) (get-token) (return (reverse (cons val a)))) ((comma) (get-token) (push val a)) (else (error "unexpected token in argument list ~a" *token*))))))) ;;; 因子 (defun factor () (case *token* (lpar (get-token) (let ((val (expression))) (if (eq *token* 'rpar) (get-token) (error "')' expected")) val)) (number (prog1 *value* (get-token))) (+ ;; 単項演算子 (get-token) (factor)) (- ;; 単項演算子 (get-token) (- (factor))) (ident (let ((func (lookup-function *value*))) (cond (func ;; 関数呼び出し (get-token) (if (eq *token* 'lpar) (apply (cdr func) (get-argument)) (error "'(' expected"))) (t ;; 変数 (prog1 *value* (get-token)))))) (t (error "unexpected token ~a" *token*)))) ;;; 項 (defun term (&aux (val (factor))) (loop (case *token* (* (get-token) (setq val (eval-op #'* val (factor)))) (/ (get-token) (setq val (eval-op #'/ val (factor)))) (t (return val))))) ;;; 式 (加減算の処理) (defun expr1 (&aux (val (term))) (loop (case *token* (+ (get-token) (setq val (eval-op #'+ val (term)))) (- (get-token) (setq val (eval-op #'- val (term)))) (t (return val))))) ;;; 式 (defun expression (&aux (val (expr1))) (case *token* ((=) ;; 代入演算子の処理 (unless (symbolp val) (error "invalid assignment form")) (get-token) (let ((val1 (eval-var (expression)))) (update-variable val val1) val1)) (t val))) ;;; プロンプトの出力 (defun prompt () (format t "Calc> ") (force-output)) ;;; 入力された式を評価する (defun toplevel () (let ((val (eval-var (expression)))) (cond ((eq *token* 'semic) (format t "=> ~a~%" val) (prompt)) (t (error "invalid token ~a" *token*))))) ;;; 入力をクリアする (defun clear-input-data () (loop while (not (eql *ch* #\Newline)) do (nextch)) (prompt)) ;;; 電卓の実行 (defun calc () (prompt) (nextch) (loop (handler-case (progn (get-token) (toplevel)) ((or simple-error arithmetic-error program-error) (c) (format t "ERROR: ~a~%" c) (clear-input-data)))))
;;; ;;; calct1.lisp : 電卓プログラム (構文木) ;;; ;;; Copyright (C) 2020 Makoto Hiroi ;;; ;;; 構文木 (defstruct op1 op rexpr) ; 単項演算子 (defstruct op2 op lexpr rexpr) ; 二項演算子 (defstruct agn var expr) ; 代入演算子 (defstruct cal fn args) ; 関数呼び出し ;;; スペシャル変数 (defvar *ch*) (defvar *token*) (defvar *value*) ;;; 記号の読み込み (defun nextch () (setq *ch* (read-char))) ;;; 先読み記号の取得 (defun getch () *ch*) ;;; 組み込み関数 (defvar *primitive-function* `((exp . ,#'exp) (log . ,#'log) (sin . ,#'sin) (cos . ,#'cos) (tan . ,#'tan) (asin . ,#'asin) (acos . ,#'acos) (atan . ,#'atan) (sqrt . ,#'sqrt) (expt . ,#'expt))) ;;; 関数を求める (defun lookup-function (name) (assoc name *primitive-function*)) ;;; 変数を格納する (defvar *variable* '()) ;;; 変数の値を求める (defun lookup-variable (var) (let ((cp (assoc var *variable*))) (if cp (cdr cp) (error "unbound variable ~a" var)))) ;;; 変数の値を更新する (defun update-variable (var val) (let ((cp (assoc var *variable*))) (if cp (rplacd cp val) (push (cons var val) *variable*)))) ;;; 数値用の文字か? (defun number-char-p (c) (or (digit-char-p c) (find c '(#\. #\+ #\- #\d #\D)))) ;;; 数値を求める (defun get-number () (let (buff) (loop while (number-char-p (getch)) do (push (getch) buff) (nextch)) (setq buff (concatenate 'string (reverse buff))) (multiple-value-bind (num len) (read-from-string buff) (if (and (numberp num) (= (length buff) len)) num (error "invalid number ~a~%" buff))))) ;;; 識別子 (defun get-ident () (let (buff) (loop while (alpha-char-p (getch)) do (push (getch) buff) (nextch)) (read-from-string (concatenate 'string (reverse buff))))) ;;; 空白文字の判定 (defun white-space-p (c) (or (char= c #\ ) (not (graphic-char-p c)))) ;;; トークンの切り分け (defun get-token () ;; 空白文字の読み飛ばし (loop while (white-space-p (getch)) do (nextch)) (cond ((digit-char-p (getch)) (setq *token* 'number *value* (get-number))) ((alpha-char-p (getch)) (setq *token* 'ident *value* (get-ident))) (t (case (getch) ((#\=) (setq *token* '=) (nextch)) ((#\+) (setq *token* '+) (nextch)) ((#\-) (setq *token* '-) (nextch)) ((#\*) (setq *token* '*) (nextch)) ((#\/) (setq *token* '/) (nextch)) ((#\() (setq *token* 'lpar) (nextch)) ((#\)) (setq *token* 'rpar) (nextch)) ((#\,) (setq *token* 'comma) (nextch)) ((#\;) (setq *token* 'semic) (nextch)) (t (setq *token* 'others)))))) ;;; ;;; 構文木の構築 ;;; (declaim (ftype (function () t) expression)) ;;; 実引数の取得 (defun get-argument () (get-token) (let ((a '())) (loop (let ((val (expression))) (case *token* ((rpar) (get-token) (return (reverse (cons val a)))) ((comma) (get-token) (push val a)) (else (error "unexpected token in argument list ~a" *token*))))))) ;;; 因子 (defun factor () (case *token* (lpar (get-token) (let ((val (expression))) (if (eq *token* 'rpar) (get-token) (error "')' expected")) val)) (number (prog1 *value* (get-token))) (+ ;; 単項演算子 (get-token) (make-op1 :op #'+ :rexpr (factor))) (- ;; 単項演算子 (get-token) (make-op1 :op #'- :rexpr (factor))) (ident (let ((func (lookup-function *value*))) (cond (func ;; 関数呼び出し (get-token) (if (eq *token* 'lpar) (make-cal :fn (cdr func) :args (get-argument)) (error "'(' expected"))) (t ;; 変数 (prog1 *value* (get-token)))))) (t (error "unexpected token ~a" *token*)))) ;;; 項 (defun term (&aux (val (factor))) (loop (case *token* (* (get-token) (setq val (make-op2 :op #'* :lexpr val :rexpr (factor)))) (/ (get-token) (setq val (make-op2 :op #'/ :lexpr val :rexpr (factor)))) (t (return val))))) ;;; 式 (加減算の処理) (defun expr1 (&aux (val (term))) (loop (case *token* (+ (get-token) (setq val (make-op2 :op #'+ :lexpr val :rexpr (term)))) (- (get-token) (setq val (make-op2 :op #'- :lexpr val :rexpr (term)))) (t (return val))))) ;;; 式 (defun expression (&aux (val (expr1))) (case *token* (= ;; 代入演算子の処理 (unless (symbolp val) (error "invalid assignment form")) (get-token) (make-agn :var val :expr (expression))) (t val))) ;;; 構文木の評価 (defun eval-expr (expr) (cond ((numberp expr) expr) ((symbolp expr) (lookup-variable expr)) ((op2-p expr) (funcall (op2-op expr) (eval-expr (op2-lexpr expr)) (eval-expr (op2-rexpr expr)))) ((op1-p expr) (funcall (op1-op expr) (eval-expr (op1-rexpr expr)))) ((agn-p expr) (let ((val (eval-expr (agn-expr expr)))) (update-variable (agn-var expr) val) val)) ((cal-p expr) (apply (cal-fn expr) (mapcar (lambda (e) (eval-expr e)) (cal-args expr)))) (t (error "invalid expression type ~a" expr)))) ;;; プロンプトの出力 (defun prompt () (format t "Calc> ") (force-output)) ;;; 入力された式を評価する (defun toplevel () (let ((expr (expression))) (cond ((eq *token* 'semic) (format t "=> ~a~%" (eval-expr expr)) (prompt)) (t (error "invalid token ~a" *token*))))) ;;; 入力をクリアする (defun clear-input-data () (loop while (not (eql *ch* #\Newline)) do (nextch)) (prompt)) ;;; 電卓の実行 (defun calc () (prompt) (nextch) (loop (handler-case (progn (get-token) (toplevel)) ((or simple-error arithmetic-error program-error) (c) (format t "ERROR: ~a~%" c) (clear-input-data)))))