四則演算計算機のエラー処理について

インタプリタの場合のエラー処理としては字句解析上のエラーと構文解析上のエラー、0で割った場合のランタイムエラーなどがあります。
字句解析時と構文解析時のエラーは一まとめにシンタックス(構文)エラーとして出力するのが一般的なようです。エラーが発生した場合はエラーのメッセージに加えて、どこでエラーが発生したかが分かるように行番号(場合によっては行内の位置)を出力するのが一般的です。また、エラー出力は1つだけではなく、複数出すことが出来るとベターです。エラーを複数出力する場合は、エラーが発生した場合はエラーの復帰を試みてさらに、エラーを探します。


さて、今回はエラーは1つしか出していません。また、1行のみの計算なので行番号は出力せずに、位置をposという変数に保存しています。字句解析を必要になったときに行うことにすることで、位置情報は複数保存することなく、pos,pposという2つの変数に格納するだけですむような構成になっています。
位置情報はC言語へのトランスレータを書く場合などは#lineディレクティブをつかうことで位置情報を保存できます。javaバイトコードへのコンパイラの場合はライン情報を格納するバイトコードを埋め込むことで実行時のエラーの箇所も特定できる仕組みになっています。オリジナルの中間コードを使った場合に参考になります。インタプリタの場合はトークン情報に位置情報を関連付けてあるとよいでしょう。


エラー処理の考え方はまず、エラーを思い浮かぶだけ羅列します。
今回の四則演算では以下のようなケースを羅列しました。

  1. 数字か演算子以外を入れた場合。
  2. 数字が予想されるところで数字以外があった。
  3. 数字の範囲を超えていた場合はどうか?


そしてそのエラー処理をどこに書けばよいかを考えて埋め込んでいきます。テスト駆動で考えるのであれば、まずエラーの出るケースを考えて、そのテストを書き、実装してテストがとおるようにすればOKです。


出来上がった後、考えもれがないか再確認しましょう。

  1. 0で割った場合どうなるのだろう?

という疑問が思い浮かびました。この場合、chromeの場合はInfinityという計算結果が出ました。JavaScript処理系によっては例外が飛ぶかもしれないし、JavaScriptはInfinityでもほかの言語では例外が発生したり、例外のない言語ではどうなるのか?考える必要があります。

今回の四則演算も自分はエラー処理苦手ということもあり、完璧ではないかもしれません。しかし、今まで書いていた四則演算プログラムよりはよりエラーに強いプログラムになったと思います。
以下ソースです。

<html>
<script>
function run() {
	var src = document.getElementById("in").value;
	document.getElementById("out").value = eval(src);
}

function eval(src) {
	var token;
	var ptoken;
	var pos = 0;
	var ppos = 0;
	function lex() {
		ppos = pos;
		ptoken = token;
		var m = src.match(/^[\r\n\t ]*([0-9]+|[+\-\/*])/);
		if (m == null) {
			token = "";
		} else {
			token = m[1];
			src = src.substring(m[0].length);
			pos += m[0].length;
		}
		return ptoken;
	}
	function exp() {
		var t = term();
		while (token == "+" || token == "-") {
			switch(lex()) {
			case "+": t = t + term(); break;
			case "-": t = t - term(); break;
			}
		}
		return t;
	}
	function term() {
		var t = fact();
		while (token == "*" || token == "/") {
			switch(lex()) {
			case "*": t = t * fact(); break;
			case "/": t = t / fact(); break;
			}
		}
		return t;
	}
	function fact() {
		var t = lex();
		if (t.match(/[0-9]+/)==null) throw "error expected number but found '"+t+"'.pos("+ppos+")";
		return Number(t);
	}
	try {
		lex();
		var t = exp();
		if (src != "") {
			return "error '"+src.substring(0, 1)+"'. pos("+pos+")";
		}
		return t;
	} catch (e) {
		return e;
	}
}
</script>
<body>

<input id="in" value="1+2*3"><input type="button" onclick="run()"><br/>
<input id="out"><br/>
<li>数字か演算子以外を入れた場合。
<li>数字が予想されるところで数字以外があった。
<li>数字の範囲を超えていた場合はどうか?
<li>0で割った場合はjavascriptの処理系に依存する。
</body>
</html>