四則演算計算機のエラー処理について
インタプリタの場合のエラー処理としては字句解析上のエラーと構文解析上のエラー、0で割った場合のランタイムエラーなどがあります。
字句解析時と構文解析時のエラーは一まとめにシンタックス(構文)エラーとして出力するのが一般的なようです。エラーが発生した場合はエラーのメッセージに加えて、どこでエラーが発生したかが分かるように行番号(場合によっては行内の位置)を出力するのが一般的です。また、エラー出力は1つだけではなく、複数出すことが出来るとベターです。エラーを複数出力する場合は、エラーが発生した場合はエラーの復帰を試みてさらに、エラーを探します。
さて、今回はエラーは1つしか出していません。また、1行のみの計算なので行番号は出力せずに、位置をposという変数に保存しています。字句解析を必要になったときに行うことにすることで、位置情報は複数保存することなく、pos,pposという2つの変数に格納するだけですむような構成になっています。
位置情報はC言語へのトランスレータを書く場合などは#lineディレクティブをつかうことで位置情報を保存できます。javaバイトコードへのコンパイラの場合はライン情報を格納するバイトコードを埋め込むことで実行時のエラーの箇所も特定できる仕組みになっています。オリジナルの中間コードを使った場合に参考になります。インタプリタの場合はトークン情報に位置情報を関連付けてあるとよいでしょう。
エラー処理の考え方はまず、エラーを思い浮かぶだけ羅列します。
今回の四則演算では以下のようなケースを羅列しました。
- 数字か演算子以外を入れた場合。
- 数字が予想されるところで数字以外があった。
- 数字の範囲を超えていた場合はどうか?
そしてそのエラー処理をどこに書けばよいかを考えて埋め込んでいきます。テスト駆動で考えるのであれば、まずエラーの出るケースを考えて、そのテストを書き、実装してテストがとおるようにすればOKです。
出来上がった後、考えもれがないか再確認しましょう。
- 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>