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>