0.0.4遅延評価したり、しなかったりする。

とりあえず、DのlazyのようなのをJSOPで作ってみました。処理系は60行以下です。
ちゃんと、遅延評価したり、しなかったりできます。

[{
	myif : {
		args : ["c", ["a"], ["b"]],
		body : ["if", "c", ["a"], ["b"]]
	},
	body : ["myif", 0, ["error"], 3]
}]

これで、結果として3が返ります。


JavaScriptで書くと、こんな感じです。lazyなんて命令はないのでうごきませんが。

function() {
  var myif = function(c,lazy a,lazy b) {
    return if(c) a(); else b();
  }
  return myif(0, error(), 3);
}();

動くようにするには下のようにするとよいと思う。

function() {
  var myif = function(c,a,b) {
    return if(c) a(); else b();
  }
  return myif(0, error, function(){return 3;});
}();


動作なのですが、JSOP0.0.3と比べると、{}でくくられる連想配列があっても、すぐには実行されず、
実行するには、[]でくくって関数適用してやる必要があるという点が異なっています。

1

は1で、

{body:1}

は、1を返す関数。

[{body:1}]

が、1を返す関数を実行して1が返る式になってると。
遅延評価をする場合は、引数リストargsに名前をリストで与えたらよいと["a"]といった具合に。
そうすると、関数が渡されてくるので、["a"]と実行してあげたら、値が取り出せるという按配です。
リストというか、配列や、文字列の処理は何も考えてないから出来るってのもあるんですが、
原理的にはこんなんでできちゃうんだなと。


まともな、説明になってませんが、まぁ、こんなかんじです。あとは、ソースみて、アレしてコレすればOKってことで。

<script>
var dbg;
function eval(p, env) {
	if(dbg)alert(p+"\n---\n"+env);
	function getEnv(p, env) {
		while (env != null) {
			if (env[p] != null)
				return [env[p], env];
			env = env["parent"];
		}
	}
	if (!isNaN(p)          ) return p;
	if (p instanceof Array ) {// 関数適用
		switch (p[0]) {
		// 組み込み関数
		case "+": return eval(p[1], env) +  eval(p[2], env);
		case "-": return eval(p[1], env) -  eval(p[2], env);
		case "*": return eval(p[1], env) *  eval(p[2], env);
		case "/": return eval(p[1], env) /  eval(p[2], env);
		case "=": return eval(p[1], env) == eval(p[2], env);
		case "if": return eval(p[1], env) ? eval(p[2], env) : eval(p[3], env);
		case "cond": 
				for (var i = 1; i < p.length; i++) 
					if (eval(p[i][0], env))
						return eval(p[i][1], env);
		// 関数
		default:
			var e, prm, body;
			if (p[0] instanceof Object) {
				// 無名関数
				prm = p[0]["args"];
				body = p[0]["body"];
				p.parent = env;
				e = {parent: p[0]};
			} else {
				// 関数
				var envs = getEnv(p[0], env);
				prm = envs[0]["args"];
				body = envs[0]["body"];
				p.parent = envs[1];
				e = {parent: envs[1]};
			}
			// 引数の計算
			if (prm)
				for(var i = 0; i < prm.length; i++)
					if (prm[i] instanceof Array)
						e[ prm[i][0] ] = {body:p[i + 1]}; // 遅延評価
					else
						e[ prm[i] ] = eval(p[i + 1], env); // 正格評価
			return eval(body, e);
		}
	}
	if (p instanceof Object) return p;
	var envs = getEnv(p, env);
	return eval(envs[0], envs[1]);
}
// Arrayの出力
Array.prototype.toString = function() {
	return "[" + this.join(",") + "]";
}
// Objectの出力
Object.prototype.toString = function() {
	var str = "";
	var p = "";
	for(var i in this) {
		str += p + i + ":" + this[i];
		p = ",";
	}
	return "{" + str + "}";
}

// テスト用
function test(f, a) {
	var r = eval(f);
	if(r != a) {
		alert("error(" + f + "\n---\n" + a + " != "+ r);
		throw "error(" + f + "\n---\n" + a + " != "+ r;
	}
}

// 値の評価
test(1, 1);

// 式の評価
test(["+",1,2], 3);

// 式の評価
test(["+",1,["*", 3,2]], 7);

// 無名関数として実行する。
test([{body:["+",1,2]}], 3);

// 関数定義と関数呼び出し
test([{
	f: {body: 3},
	body: ["f"]
}], 3);

// 引数付きの関数
test([{
	add: {
		args: ["a", "b"],
		body: ["+", "a", "b"]
	},
	body: ["add", 1, 2]
}], 3);

// 関数を引数に渡す
test([{
	c: {
		args: ["f"],
		body: ["f", 1, 2]
	},
	add: {
		args: ["a", "b"],
		body: ["+", "a", "b"]
	},
	body: ["c", "add"]
}], 3);

// 無名関数を引数に渡す
test([{
	c: {
		args: ["f"],
		body: ["f", 3, 2]
	},
	body: ["c", {args: ["a", "b"], body: ["*", "a", "b"]}]
}], 6);

// 再帰呼び出し
test([{
	body : ["fact", 10],
	fact : {
		args : ["n"],
		body : ["cond",
			[["=","n",1], 1],
			[1          , ["*", ["fact", ["-", "n", 1]], "n"]]
		]
	}
}], 3628800);

// 遅延評価
// dbg = true;
test([{
	myif : {
		args : ["c", ["a"], ["b"]],
		body : ["if", "c", ["a"], ["b"]]
	},
	body : ["myif", 0, ["error"], 3]
}], 3);

</script>