メッセンジャー用電卓スクリプト2

電卓ってさあ・・・
コンパイラでいうHello, World!だよね・・・。
ほんと何やってんだろ・・・。
caper
http://naoyuki.hirayama.googlepages.com/caper.html
っていうJavaScriptコードも吐けるLALR(1)パーザジェネレータも見つけたしこれ使ってもいいんじゃねーの?逆に面倒かね。
id:yappy_t:20090208の改良でございます(これってもっときれいな書き方ないのか?)。


文法(表現方法と演算子優先順位はフィーリングで)
剰余演算子%と累乗演算子^と2つ以上の引数を持つ関数に対応しました。小数に%を使ってどうなるかは知りません。
atan2トークンの定義がいい加減だったことを思い出し、[a-zA-Z]+→[a-zA-Z][a-zA-Z0-9]*に修正しました(\wとか使え?調べるのがめんどくさかった)。

Program := "=" Expr
Expr := ["+""-"]Expr | Expr ["+""-""*""/""%""^"] Expr | Term
Term :=  |  ( "(" Expr ("," Expr)* ")" )?

BNF正規表現JavaCCの混ざった気持ち悪い表現だこと!!
そういえばこれfunc()っていう書き方に対応してないね・・・。○pi×pi()。どうでもいいや。


複数引数対応もあり、使用できる関数が増えました。
min/max関数は任意の個数の引数をとれます。
logは底を省略するとeになります。
引数の個数はあんまりチェックしてません。めんどい。

e
max(x1, x2, x3, ...)
min(x1, x2, x3, ...)
log(x)
log(a, b)
atan2(y, x)

その他もろもろ
ソースを見ればいいと思うよ!!

function OnEvent_Initialize(MessengerStart){
	mt = new MersenneTwister();
	Debug.ClearDebuggingWindow();
	Debug.Trace("Init");
	/*
	try{
		createTokenList("=atan2(1,1)");
		Debug.Trace("> " + Program());
	}catch(e){
		Debug.Trace(e);
	}
	*/
}

function OnEvent_Uninitialize(MessengerExit){
	Debug.Trace("Uninit");
}

function OnEvent_ChatWndReceiveMessage(ChatWnd, Origin, Message, MsgKind){
	try{
		createTokenList(Message);
		ChatWnd.SendMessage("> " + Program());
	}catch(e){
		Debug.Trace(e);
	}
	return Message;
}

var mt;
var ind;
var tokenList;

function nextToken(){
	if(ind >= tokenList.length)
		throw "No token";
	return tokenList[ind++];
}

function ensureNextToken(image){
	var token = nextToken();
	if(token.image != image)
		throw image + " requred but " + token.image;
}

function peekToken(){
	if(ind >= tokenList.length)
		return {};
	return tokenList[ind];
}

var SKIP = /\s+/;

var TOKEN = [
	{type: "NUMBER", expression: /\d+(?:\.\d+)?/},
	{type: "IDENT", expression: /[a-zA-Z][a-zA-z0-9]*/},
	{type: "EQUAL", expression: /=/},
	{type: "OP1", expression: /[\+\-]/},
	{type: "OP2", expression: /[\*\/\%]/},
	{type: "OP3", expression: /\^/},
	{type: "BRACE", expression: /[\(\)]/},
	{type: "COMMA", expression: /\,/}
];

var FUNCTION = {
	min : function(args){
		var result = args[0];
		for(var i=1; i<args.length; i++){
			result = Math.min(result, args[i]);
		}
		return result;
	},
	max : function(args){
		var result = args[0];
		for(var i=1; i<args.length; i++){
			result = Math.max(result, args[i]);
		}
		return result;
	},
	pi : function(){
		return Math.PI;
	},
	e: function(){
		return Math.E;
	},
	rand : function(args){
		if(args.length >= 1){
			return mt.nextInt(args[0]);
		}
		else{
			return mt.next();
		}
	},
	abs : function(args){
		return Math.abs(args[0]);
	},
	exp : function(args){
		return Math.exp(args[0]);
	},
	log : function(args){
		if(args.length >= 2)
			return Math.log(args[1]) / Math.log(args[0]);
		else
			return Math.log(args[0]);
	},
	sqrt : function(args){
		return Math.sqrt(args[0]);
	},
	rad : function(args){
		return args[0] * Math.PI / 180;
	},
	deg : function(args){
		return args[0] * 180 / Math.PI;
	},
	sin : function(args){
		return Math.sin(args[0]);
	},
	cos  : function(args){
		return Math.cos(args[0]);
	},
	tan : function(args){
		return Math.tan(args[0]);
	},
	asin: function(args){
		return Math.asin(args[0]);
	},
	acos: function(args){
		return Math.acos(args[0]);
	},
	atan: function(args){
		return Math.atan(args[0]);
	},
	atan2: function(args){
		return Math.atan2(args[0], args[1]);
	},
	sinh: function(args){
		return (Math.exp(args[0]) - Math.exp(-args[0])) / 2;
	},
	cosh: function(args){
		return (Math.exp(args[0]) + Math.exp(-args[0])) / 2;
	},
	tanh: function(args){
		return (Math.exp(args[0]) - Math.exp(-args[0])) / (Math.exp(args[0]) + Math.exp(-args[0]));
	}
};

function createTokenList(str){
	ind = 0;
	tokenList = [];
	LOOP: while(str.length != 0){
		//Debug.Trace(str);
		{
			var mstr = str.match(SKIP);
			if(mstr && RegExp.index==0){
				str = str.substr(mstr[0].length);
			}
			//Debug.Trace("skip: " + mstr);
		}
		for(var i=0; i<TOKEN.length; i++){
			var mstr = str.match(TOKEN[i].expression);
			if(mstr && RegExp.index==0){
				str = str.substring(mstr[0].length);
				var token = {};
				token.type = TOKEN[i].type;
				token.image = mstr[0];
				tokenList.push(token);
				//Debug.Trace("token: " + mstr);
				continue LOOP;
			}
		}
		throw "Did not match";
	}
}

// =Expression
function Program(){
	ensureNextToken("=");
	return Expression();
}

function Expression(){
	return AddSub();
}

function AddSub(){
	var val = MulDiv();
	while(peekToken().type == "OP1"){
		var op = nextToken();
		var right = MulDiv();
		switch(op.image){
		case "+":
			val += right;
			break;
		case "-":
			val -= right;
			break;
		default:
			throw "Internal error: " + op.image;
		}
	}
	return val;
}

function MulDiv(){
	var val = Pow();
	while(peekToken().type == "OP2"){
		var op = nextToken();
		var right = Pow();
		switch(op.image){
		case "*":
			val *= right;
			break;
		case "/":
			val /= right;
			break;
		case "%":
			val %= right;
			break;
		default:
			throw "Internal error: " + op.image;
		}
	}
	return val;
}

function Pow(){
	var val = Unary();
	while(peekToken().type == "OP3"){
		var op = nextToken();
		var right = Unary();
		switch(op.image){
		case "^":
			val = Math.pow(val, right);
			break;
		default:
			throw "Internal error: " + op.image;
		}
	}
	return val;
}

function Unary(){
	var val = 1;
	while(peekToken().type == "OP1"){
		var op = nextToken();
		switch(op.image){
		case "+":
			break;
		case "-":
			val *= -1;
			break;
		default:
			throw "Internal error: " + op.image;
		}
	}
	return val * Factor();
}

// (Expression) | Term
function Factor(){
	if(peekToken().image == "("){
		ensureNextToken("(");
		var value = Expression();
		ensureNextToken(")");
		return value;
	}
	else{
		return Term();
	}
}

function Term(){
	var token = nextToken();
	// Number Literal
	if(token.type == "NUMBER"){
		return parseFloat(token.image);
	}
	// Function
	// IDENT ("(" Expr ("," Expr)* ")")?
	else if(token.type == "IDENT"){
		var func = FUNCTION[token.image];
		if(!func){
			throw "fucntion " + token.image + " not found";
		}
		var args = [];
		if(peekToken().image == "("){
			ensureNextToken("(");
			args.push(Expression());
			while(peekToken().image == ","){
				ensureNextToken(",");
				args.push(Expression());
			}
			ensureNextToken(")");
		}
		return func(args);
	}
	else{
		throw token.image;
	}
}