TDCのライトニングトークで使ったソース+α
再帰下降型の構文解析をして計算します。
高速に書き加える為にevalを使ってみてます。
advanceが字句解析器で1トークン取り出してtokenに値を入れ前回のtokenを返します。
eatは予測されたトークンのチェックをします。kinabaさんのソースとかからいただきました。
fがボタンが押されたときに動く関数です。
exp,term,factが拡張BNFで書かれた構文規則を解析する関数です。
まだまだ、バグもちですが、5分でってなるとこれが限界って感じです。
以下ソースです。
<html> <style> input {font-size:50px;} </style> <script> // ボタンを押したときに動く function f() { // id="in"の値をsrcに入れる src = document.getElementById("in").value; // 1トークン先読みする advance(); var rc; try { // 計算する rc = exp(); // srcが残ってたら式が終わっていない if(src.length > 0) { rc = "式終わってないよエラー"; } } catch(e) { // 例外を返す rc = e; } // id="out"の値に結果を入れる document.getElementById("out").value = rc; } var token;// トークン var src;// ソースコード var ptoken;//前回のトークン // 字句解析 // リターン値が前回のトークンにすることで以下のコードが綺麗になる。 function advance() { // 前回のトークンを保存する ptoken = token; // 空白を削除しつつ0〜9の並びもしくは+,-,*,/,(,)を取り出す m = src.match(/^[ \r\n\t]*([0-9]+|[\+\-\*\/\)\(])/); if(m) { // マッチした token = m[1];// カッコの中身をトークンとして返す src = src.substring(m[0].length);// マッチした全体をソースから削除する } else { token = null;// マッチしなかったらnull } return ptoken;// 前回のトークンを返す。 } // 予想したトークンを食べる // トークンがsと予測される場合に使う function eat(s) { if(s != advance()) throw "expect error"; } // exp := term { (+|-) term } function exp() { var c = term();// とりあえずtermだ。 while(token=="+"||token=="-")// +か-なら // evalで"+"と"-"どちらでも動くように c = eval( c + advance() + term()); return c; } // term := fact { (*|/) fact } function term() { var c = fact(); while(token=="*"||token=="/") // evalで"*"と"/"どちらでも動くように c = eval( c + advance() + fact()); return c; } // fact := ( exp ) | number function fact() { // カッコ if("(" == advance()) { // カッコ内をもう一度計算 var c = exp(); // 閉じカッコを食べる eat(")"); return c;// 結果を返す。 } // カッコでなければ、数字としてしまう。 // 正しく処理するならば、ptoken.match(/^[0-9]+$/)のときにすべき return Number(ptoken); } </script> <body> <h1>1曲のうちに作る<br/>構文解析器<br/>リベンジ</h1> <h1></h1> <input id = "in" value="1+2*3"> <input type = "button" value="実行" onclick="f()"><br/> <input id = "out"> </body> </html>