/*
 * Copyright (c) 2008-2011 Hasso Plattner Institute
 *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

module('lively.AST.Tests').requires('lively.AST.Parser', 'lively.AST.StackReification', 'lively.TestFramework').toRun(function() {

TestCase.subclass('lively.AST.Tests.ParserTest',
'running', {
	setUp: function($super) {
		$super()
	},
},
'helper', {
	parseJS: function(src, rule) {
		// return lively.AST.Parser.parse(src, rule)
		return OMetaSupport.matchAllWithGrammar(LivelyJSParser, rule || 'topLevel', src)
	},
},
'testing', {
	test01ParseRegex: function() {
		var src = '/aaa/gi',
			expected = ['regex', [0, 7], 'aaa', 'gi'],
			result = this.parseJS(src, 'expr');
		this.assertMatches(expected, result);
	},

});


TestCase.subclass('lively.AST.Tests.JSToAstTest',
'helper', {
	parseJS: function(src, rule) { return lively.AST.Parser.parse(src, rule) },
},
'testing', {
	test01SimpleExpression: function() {
		var src = '1+2;',
			r = this.parseJS(src),
			expected = {
				isSequence: true,
				children: [{
					isBinaryOp: true,
					name: '+',
					left: {isNumber: true, value: 1},
					right: {isNumber: true, value: 2},
				}],
			};
		this.assertMatches(expected, r);
	},
	test02SimpleFunction: function() {
		var src = 'function(a) { return a++ }',
			r = this.parseJS(src),
			expected = {
				isFunction: true,
				args: ['a'],
				body: {
					isSequence: true,
					children: [{
						isReturn: true,
						expr: {
							isPostOp: true,
							name: '++',
							expr: {isVariable: true, name: 'a'}
						}
					}]
				},
			};
		this.assertMatches(expected, r.children[0]);
	},
	test03TryCatch: function() {
		var src = 'try { this.foo(1) } catch(e) { log(e) }',
			r = this.parseJS(src),
			expected = {
				isTryCatchFinally: true,
				tryExpr: {},
				catchExpr: {},
				finallyExpr: {},
			};
		this.assertMatches(expected, r.children[0]);
	},
	test04GetParentFunction: function() {
		var funcAst = function() { if (true) return 1 + m(); foo() }.ast();
		this.assertIdentity(funcAst, funcAst.body.children[0].trueExpr.children[0].expr.right.parentFunction());
	},

	test05aEnumerateASTNodes: function() {
		var funcAst = function() { if (true) return 1 + m(); foo() }.ast();
		// funcAst.printTree(true) gives a tree in post order, just enumerate it
		// 0    Variable(condExpr)
		// 1       Number(left)
		// 2        Variable(fn)
		// 3       Call(right)
		// 4      BinaryOp(expr)
		// 5     Return(children)
		// 6    Sequence(trueExpr)
		// 7     Variable(children)
		// 8    Sequence(falseExpr)
		// 9   If(children)
		// 10    Variable(fn)
		// 11   Call(children)
		// 12  Sequence(body)
		// 13 Function(undefined)
		this.assertEquals(0, funcAst.body.children[0].condExpr.astIndex());
		this.assertEquals(1, funcAst.body.children[0].trueExpr.children[0].expr.left.astIndex());
		this.assertEquals(2, funcAst.body.children[0].trueExpr.children[0].expr.right.fn.astIndex());
		this.assertEquals(3, funcAst.body.children[0].trueExpr.children[0].expr.right.astIndex());
		this.assertEquals(4, funcAst.body.children[0].trueExpr.children[0].expr.astIndex());
		this.assertEquals(5, funcAst.body.children[0].trueExpr.children[0].astIndex());
		this.assertEquals(6, funcAst.body.children[0].trueExpr.astIndex());
		this.assertEquals(7, funcAst.body.children[0].falseExpr.astIndex());
		this.assertEquals(8, funcAst.body.children[0].astIndex());
		this.assertEquals(9, funcAst.body.children[1].fn.astIndex());
		this.assertEquals(10, funcAst.body.children[1].astIndex());
		this.assertEquals(11, funcAst.body.astIndex());
		this.assertEquals(12, funcAst.astIndex());

	},
	test05bEnumerateASTNodesButNotNestedFunctions: function() {
		var funcAst = function() { (function() { return 3 }); foo() }.ast();
		// funcAst.printTree(true) gives a tree in post order, just enumerate it
		// x      Number(expr)
		// x     Return(children)
		// x    Sequence(body)
		// x   Function(children)
		// 0    Variable(fn)
		// 1   Call(children)
		// 2  Sequence(body)
		// 3 Function(undefined)
		this.assertEquals(3, funcAst.astIndex());
		this.assertEquals(2, funcAst.body.astIndex());
		// this.assertEquals(0, funcAst.body.children[0].astIndex());
		// this.assertEquals(1, funcAst.body.children[0].body.astIndex());
		// this.assertEquals(2, funcAst.body.children[0].body.children[0].astIndex());
		// this.assertEquals(3, funcAst.body.children[0].body.children[0].expr.astIndex());
		this.assertEquals(1, funcAst.body.children[1].astIndex());
		this.assertEquals(0, funcAst.body.children[1].fn.astIndex());
	},



})


TestCase.subclass('lively.AST.Tests.ReplaceTest',
'helper', {
	parseJS: function(src, rule) { return lively.AST.Parser.parse(src, rule) },
},
'testing', {
	test01ReplaceWith: function() {
		var ast = this.parseJS('this.baz(this.foo()) + this.foo()', 'expr'),
			nodes = ast.nodesMatching(function(node) { return node.isSend && node.name == 'foo' }),
			node = nodes[0],
			replacement = this.parseJS('this.bar()', 'expr'),
			expected = {
				left: {name: 'baz', args: [{name: 'bar'}]},
				right: {name: 'foo'}
			};

		this.assertEquals(2, nodes.length);
		node.replaceWith(replacement)
		this.assertMatches(expected, ast);
	},
	test02ReplaceNodesMatching: function() {
		var astToReplace = this.parseJS('this.baz(this.foo()) + this.foo()', 'stmt'),
			expected = {
				left: {name: 'baz', args: [{name: 'bar'}]},
				right: {name: 'bar'}
			};
		astToReplace.replaceNodesMatching(
			function(node) { return node.isSend && node.name == 'foo' },
			function() { return this.parseJS('this.bar()', 'expr') }.bind(this));

		this.assertMatches(expected, astToReplace);
	},



});


TestCase.subclass('lively.AST.Tests.InterpreterTest',
'helper', {
	parseJS: function(src, rule) { return lively.AST.Parser.parse(src, rule) },
},
'testing', {
	test01Number: function() {
		var node = this.parseJS('1', 'expr');
		this.assertEquals(1, node.startInterpretation());
	},
	test02AddNumbers: function() {
		var node = this.parseJS('1 + 2', 'expr');
		this.assertEquals(3, node.startInterpretation());
	},
	test03LookupVar: function() {
		var node = this.parseJS('a + 2', 'expr');
		this.assertEquals(3, node.startInterpretation({a: 1}));
	},
	test04If: function() {
		var node = this.parseJS('if (false) { 1 } else { 2 }', 'stmt');
		this.assertEquals(2, node.startInterpretation());
	},
	test05FunctionInvocation: function() {
		var node = this.parseJS('(function() { return 1 })()', 'expr');
		this.assertEquals(1, node.startInterpretation());
	},
	test06FunctionInvocationWithArgs: function() {
		var node = this.parseJS('(function(a) { return a + 1 })(2)', 'expr');
		this.assertEquals(3, node.startInterpretation());
	},
	test07Closue: function() {
		var node = this.parseJS('var a = 6; (function(b) { return a / b })(3)');
		this.assertEquals(2, node.startInterpretation());
	},
	test08RealClosue: function() {
		var node = this.parseJS('var foo = function(){var a = 1; return function() {return a}}; foo()()');
		this.assertEquals(1, node.startInterpretation());
	},


	test09aEarlyReturn: function() {
		var node = this.parseJS('return 1; 2');
		this.assertEquals(1, node.startInterpretation());
	},
	test09bEarlyReturnInFor: function() {
		var node = this.parseJS('for (var i=0;i<10;i++) if (i==5) return i');
		this.assertEquals(5, node.startInterpretation());
	},
	test09cEarlyReturnInWhile: function() {
		var node = this.parseJS('var i = 0; while (i<10) { i++; if (i==5) return i }');
		this.assertEquals(5, node.startInterpretation());
	},


	test10Recursion: function() {
		var node = this.parseJS('function foo(n) { return n == 1 ? 1 : foo(n - 1)}; foo(10)');
		this.assertEquals(1, node.startInterpretation());
	},
	test11MethodCall: function() {
		var node = this.parseJS('var obj = {foo: function() {return 3}}; obj.foo()');
		this.assertEquals(3, node.startInterpretation());
	},
	test12UsingThis: function() {
		var node = this.parseJS('var obj = {foo: function() {this.x=3}}; obj.foo(); obj.x');
		this.assertEquals(3, node.startInterpretation());
	},
	test13ModifyingVar: function() {
		var node = this.parseJS('var x = 1; x = 3; x');
		this.assertEquals(3, node.startInterpretation());
	},
	test14NoDynamicScop: function() {
		var ast = this.parseJS('var a = 1; ' +
			'function bar () { return a }; ' +
			'function foo() { var a = 2; return bar() }; ' +
			'foo()')
			result  = ast.startInterpretation();
		this.assertIdentity(1, result, 'function barr can access dynamic scope of foo');
	},
	test15ForLoop: function() {
		var ast = this.parseJS('var arr = []; for (var i = 0; i < 5; i++) arr[i] = i; arr'),
			result  = ast.startInterpretation();
		this.assertEqualState([0, 1, 2, 3, 4], result);
	},
	test16aWhile: function() {
		var ast = this.parseJS('var i = 0; while(i < 3) i++; i'),
			result  = ast.startInterpretation();
		this.assertEqualState(3, result);
	},
	test16bWhileReturnValue: function() {
		// actually a test for pre/post op
		var exprStr = 'var i = 0; while(i<3) {++i}',
			ast = this.parseJS(exprStr),
			result  = ast.startInterpretation();
		this.assertEqualState(eval(exprStr), result);

		var exprStr = 'var i = 0; while(i<3) {i++}',
			ast = this.parseJS(exprStr),
			result  = ast.startInterpretation();
		this.assertEqualState(eval(exprStr), result);
	},
	test17DoWhile: function() {
		var ast = this.parseJS('var i = 0; do { ++i } while (i == 0); i'),
			result  = ast.startInterpretation();
		this.assertEqualState(1, result);
	},
	test18ForIn: function() {
		var ast = this.parseJS('var obj = {a: 1, b:2}, result = []; ' +
				'for (var name in obj) result.push(name); result'),
			result  = ast.startInterpretation();
		this.assertEqualState(['a', 'b'], result);
	},
	test19ModifyingSet: function() {
		var ast = this.parseJS('a += 2'),
			mapping = {a: 3},
			result  = ast.startInterpretation(mapping);
		this.assertEquals(5, result);
		this.assertEquals(5, mapping.a);
	},
	test20UnaryOp: function() {
		var ast = this.parseJS('var a = 4; -a'),
			result  = ast.startInterpretation();
		this.assertEquals(-4, result);
	},
	test20aBreakInFor: function() {
		var ast = this.parseJS('for (var i = 0; i < 10; i++) { if (i == 2) break }; i'),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test20bBreakInCase: function() {
		var ast = this.parseJS('var a = 2, b; switch(a) { case 1: b=1; case 2: b=2; break; case 3: b=3 }; b'),
			result = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test21aSwitch: function() {
		var ast = this.parseJS('switch(2) { case 1: a++; case 2: a++; case 3: a++; break; case 4: a++ }; a'),
			result = ast.startInterpretation({a: 0});
		this.assertEquals(2, result);
	},
	test21bSwitchDefault: function() {
		var ast = this.parseJS('switch(3) { case 1: a=1; case 2: a=2; default: a=3 }; a'),
			result = ast.startInterpretation({a: 0});
		this.assertEquals(3, result);
	},
	test22aContinueInFor: function() {
		var ast = this.parseJS('for (var i = 0; i < 10; i++) { if (i > 2) continue; a=i }; a'),
			result  = ast.startInterpretation({a: 0});
		this.assertEquals(2, result);
	},
	test23aSimpleTryCatch: function() {
		var ast = this.parseJS('try { throw {a: 1} } catch(e) { e.a }'),
			result  = ast.startInterpretation();
		this.assertEquals(1, result, 'wrong catch result');
	},
	test23bSimpleTryCatchFinally: function() {
		var ast = this.parseJS('try { throw {a: 1} } catch(e) { e.a } finally { 2 }'),
			result  = ast.startInterpretation();
		this.assertEquals(2, result, 'wrong finally result');
	},
	test23cTryCatchMultipleLevels: function() {
		var src = 'function m1() { for (var i = 0; i < 10; i++) if (i == 3) throw i }; ' +
				'function m2() { m1(); return 2 }; try { m2() } catch(e) { e }',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(3, result, 'wrong result');
	},
	test23dTryFinally: function() {
		var src = 'try { 1 } finally { 2 }',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},

	test24aNewWithFunc: function() {
		var src = 'function m() { this.a = 2 }; var obj = new m(); obj.a',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test24bNewThenObjAccess: function() {
		var src = 'function m() { this.a = 2 }; new m().a',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test24cNewPrototypeInheritence: function() {
		var src = 'function m() { this.a = 1 }; m.prototype.b = 2; new m().b',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test24dFunctionPrototypeNotChanged: function() {
		var src = 'function m() { this.a = 1 }; m.prototype.a = 2; new m(); m.prototype.a',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(2, result);
	},
	test24eObjReallyInherits: function() {
		var src = 'function m() {}; m.prototype.a = 2; var obj = new m(); m.prototype.a = 1; obj.a',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(1, result);
	},
	test24eFuncCallInNewExpr: function() {
		var src = 'function m() { this.a = (function() { return 1 })() }; new m().a',
			ast = this.parseJS(src),
			result  = ast.startInterpretation();
		this.assertEquals(1, result);
	},
	test25InstantiateClass: function() {
		Config.deepInterpretation = true
		var className = 'Dummy_test25InstantiateClass';
		try {
		var klass = Object.subclass(className, { a: 1 }),
			src = Strings.format('var obj = new %s(); obj.a', className),
			ast = this.parseJS(src),
			mapping = {Dummy_test25InstantiateClass: klass},
			result  = ast.startInterpretation(mapping);
		this.assertEquals(1, result);
		this.assert(Class.isClass(Global[className]), 'Class changed!')
		} finally {
			delete Global[className];
		}
	},
	test26ArgumentsOfConstructorAreUsed: function() {
		Config.deepInterpretation = true
		try {
			Object.subclass('Dummy_test26ArgumentsOfConstructorAreUsed', { initialize: function(n) { this.n = n }})
			var src = 'var obj = new Dummy_test26ArgumentsOfConstructorAreUsed(1); obj.n',
				ast = this.parseJS(src),
				result  = ast.startInterpretation(Global.asMapping());
			this.assertEquals(1, result);
		} finally {
			delete Global.Dummy_test26ArgumentsOfConstructorAreUsed
		}
	},
	test27SpecialVarArguments: function() {
		var src = 'function x() { return arguments[0] }; x(1)',
			ast = this.parseJS(src),
			result  = ast.startInterpretation(Global.asMapping());
		this.assertEquals(1, result);
	},
	test27NullisNull: function() {
		var src = 'null',
			ast = this.parseJS(src, 'expr'),
			result  = ast.startInterpretation();
		this.assertIdentity(null, result);
	},
	test28SimpleRegex: function() {
		var src = '/aaa/.match("aaa")',
			ast = this.parseJS(src, 'expr'),
			result  = ast.startInterpretation();
		this.assertIdentity(true, result);
	},
	test29FunctionHasRealFunctionAttached: function() {
		var src = 'function m() {}',
			ast = this.parseJS(src, 'expr'),
			result  = ast.startInterpretation();
		this.assert(Object.isFunction(result.getRealFunction()), 'cannot get/creater realFunction');
	},
	test30InstanceOf: function() {
		var src = 'pt(0,0) instanceof Point',
			ast = this.parseJS(src, 'expr'),
			result  = ast.startInterpretation(Global.asMapping());
		this.assert(result, 'instanceof not working');
	},








});
TestCase.subclass('lively.AST.Tests.InterpreterContinuationTest',
'testing', {
	test01ContinueSimpleFunction: function() {
		var funcAst = function() { a++; b++ }.ast(), mapping = {a: 0, b: 0};
		// funcAst.printTree(true)
		funcAst.resumeAt(3/*pc -> b++*/, mapping);
		this.assertEquals(0, mapping.a, 'a');
		this.assertEquals(1, mapping.b, 'b');
	},
	test02ContinueMoreComplex: function() {
		var funcAst = function() { a++; if (true) { b++; c++; d++; }; e++}.ast(),
			mapping = {a: 0, b: 0, c: 0, d: 0, e: 0};
		// funcAst.printTree(true) 0    Variable(expr)
		funcAst.resumeAt(6 /*b++*/, mapping);		
		this.assertEquals(0, mapping.a, 'a');
		this.assertEquals(0, mapping.b, 'b');
		this.assertEquals(1, mapping.c, 'c');
		this.assertEquals(1, mapping.d, 'd');
		this.assertEquals(1, mapping.e, 'e');
	},
	test03ContinueLoop: function() {
		var funcAst = function() { for (var i=0; i<10; i++) howManyCalls++ }.ast(),
			mapping = {i: 5, howManyCalls: 0};
		// funcAst.printTree(true)
		funcAst.resumeAt(8 /*howManyCalls++*/, mapping);
		this.assertEquals(5, mapping.howManyCalls)
	},


});
TestCase.subclass('lively.AST.Tests.FrameTest',
'testing', {
	test01CopyFrames: function() {
		var frame1 = lively.AST.Interpreter.Frame.create(),
			frame2 = frame1.newScope({a: 1}),
			frame3 = frame2.newScope({b: 2}),
			copy = frame3.copy();
		frame3.addToMapping('b', 3);
		this.assertEquals(2, copy.lookup('b'));
		frame2.addToMapping('a', 3);
		this.assertEquals(1, copy.lookup('a'));
	},
	test02JumpToNextStatement: function() {
		var func = function() { var a = 1; b(a) },
			funcAst = func.ast(),
			firstPc = funcAst.statements()[0].astIndex(),
			nextPc = funcAst.statements()[1].astIndex(),
			frame = new lively.AST.Interpreter.Frame({}, firstPc, func);
		frame.jumpToNextStatement();
		this.assertEquals(nextPc, frame.pc);
	},

});

TestCase.subclass('lively.AST.Tests.ExecutionStateReifierTest',
'running', {
	setUp: function($super) {
		this.sut = new lively.AST.StackReification.Rewriter();
	},
},
'helper', {
	funcAst: function(funcSrc) {
		var func = eval('(' + funcSrc + ')');
		return func.ast();
	},
	catsch: function(funcName, idx, optRecv, varNames) {
		varNames = varNames || [];
		var call = Strings.format('var result%s_%s = %s%s();',
			idx, funcName, optRecv ? optRecv + '.' : '', funcName),
			varRecorder = varNames.collect(function(name) { return name + ':' + name }).join(','),
			src = Strings.format('try {\n%s\n} catch(e) {\n' +
				'if (e.isUnwindException) {\n' +
				'	e.lastFrame = e.lastFrame.addCallingFrame({%s}, %s, arguments.callee.livelyClosure.getOriginalFunc())\n' +
				'}\nthrow e\n};', call, varRecorder, idx);
		return src;
	},
},
'testing', {
	test01RewriteSimpleFunction: function() {
		var func = function() { return bar() },
			expectedSrc = Strings.format('function(){ %s return result1_bar; }', this.catsch('bar', 1)),
			expectedAST = this.funcAst(expectedSrc),
			result = this.sut.rewrite(func.ast(), 'isolateAndCatchCall');
		this.assertEquals(expectedAST.asJS(), result.asJS());
	},
	test02RewriteTwoCalls: function() {
		var func = function() { return this.foo() + bar() },
			expectedSrc = Strings.format('function(){ %s %s return result1_foo + result3_bar; }',
				this.catsch('foo', 1, 'this'), this.catsch('bar', 3, null, ['result1_foo'])),
			expectedAST = this.funcAst(expectedSrc),
			result = this.sut.rewrite(func.ast(), 'isolateAndCatchCall');
		this.assertEquals(expectedAST.asJS(), result.asJS());
	},
	test03RewriteCallsInIf: function() {
		if (!Config.suppressRobertsWarnings)
			alert('TODO for Robert: test03RewriteCallsInIf: lazy evaluation of or expressions and cond exprs')
		var func = function() { if (this.foo() || bar()) { 1 } },
			expectedSrc = Strings.format('function(){ %s %s if (result1_foo || result3_bar) { 1 } }',
				this.catsch('foo', 1, 'this'), this.catsch('bar', 3, null, ['result1_foo'])),
			expectedAST = this.funcAst(expectedSrc),
			result = this.sut.rewrite(func.ast(), 'isolateAndCatchCall');

		this.assertEquals(expectedAST.asJS(), result.asJS());
	},
	test04aRewriteCallsInIfBody: function() {
		var func = function() { if (true) { return this.foo() } },
			expectedSrc = Strings.format('function(){ if (true) { %s return result2_foo } }',
				this.catsch('foo', 2, 'this')),
			expectedAST = this.funcAst(expectedSrc),
			result = this.sut.rewrite(func.ast(), 'isolateAndCatchCall');

		this.assertEquals(expectedAST.asJS(), result.asJS());
	},
	test04bRewriteCallsInIfBody: function() {
		var func = function() { if (true) foo() },
			expectedSrc = Strings.format('function(){ if (true) { %s result2_foo } }',
				this.catsch('foo', 2)),
			expectedAST = this.funcAst(expectedSrc),
			result = this.sut.rewrite(func.ast(), 'isolateAndCatchCall');

		this.assertEquals(expectedAST.asJS(), result.asJS());
	},

	test05aFindAllVarsAndArgsInScope: function() {
		var func = function(a) { var b = 2; if (a) { var c = b + 1 }; return bar() },
			expected = ['a', 'b', 'c'],
			result = this.sut.findVarAndArgNamesInScope(func.ast());
		this.assertEqualState(expected, result);
	},
	test05bFindAllVarsAndArgsInScope: function() {
		var func = function() { var a; function foo() { var b = 3 } },
			expected = ['a', 'foo'],
			result = this.sut.findVarAndArgNamesInScope(func.ast());
		this.assertEqualState(expected, result);
	},
	testCaptureSimpleStack: function() {
		var unwindException = null,
			halt = lively.AST.StackReification.halt,
			func = function() { var b = 2; halt() }.stackCaptureMode();
		try { func() } catch(e) { if (e.isUnwindException) { unwindException = e } else { throw e }}
		this.assert(unwindException, 'no unwindException')
		this.assertEquals(2, unwindException.lastFrame.lookup('b'));
	},



});
TestCase.subclass('lively.AST.Tests.ContinuationTest',
'running', {
	shouldRun: true,
	setUp: function($super) {
		this.rewriter = new lively.AST.StackReification.Rewriter();
		Global.currentTest = this;
	},
	tearDown: function($super) {
		$super();
		delete Global.currentTest;
	},

},
'testing', {
	test01RestartSimpleFunction: function() {
		var state = {before: 0, after: 0},
			func = function() {
				state.before++;
				lively.AST.StackReification.halt();
				state.after++;
			}.stackCaptureMode({state: state}),
			continuation = lively.AST.StackReification.run(func);
		this.assertEquals(1, state.before, 'before not run');
		this.assertEquals(0, state.after, 'after run');
		continuation.resume();
		this.assertEquals(1, state.before, 'before run again');
		this.assertEquals(1, state.after, 'after not run');
	},
	test02RestartFunctions: function() {
		var t = Global.currentTest;
		Object.extend(t, {
			a_before: 0, a_after: 0, b_before: 0, b_after: 0,
			b: function(arg) {
				arg++
				Global.currentTest.b_before++;
				halt();
				Global.currentTest.b_after++;
			}.stackCaptureMode(),
			a: function() {
				var x = 3;
				Global.currentTest.a_before++
				Global.currentTest.b(x)
				Global.currentTest.a_after++
			}.stackCaptureMode(),
		})
		var continuation = lively.AST.StackReification.run(t.a);
		this.assertEquals(1, t.a_before, 'a_before not run');
		this.assertEquals(1, t.b_before, 'b_before not run');
		this.assertEquals(0, t.a_after, 'a_after did run');
		this.assertEquals(0, t.b_after, 'b_after did run');
		continuation.resume();
		this.assertEquals(1, t.a_before, 'a_before run again');
		this.assertEquals(1, t.b_before, 'b_before run again');
		this.assertEquals(1, t.a_after, 'a_after not run');
		this.assertEquals(1, t.b_after, 'b_after not run');
	},
	test03ResumedFunctionHasNoNextStatement: function() {
		var func = function() {
				lively.AST.StackReification.halt();
			}.stackCaptureMode(),
			continuation = lively.AST.StackReification.run(func);
		continuation.resume();
	},


});
TestCase.subclass('lively.AST.Tests.ClosureTest',
'running', {
	setUp: function($super) {
		$super();
		// this.sut = new lively.AST.ClosureAnalyzer();
	},
},
'testing', {
	test01FindFreeVariable: function() {
		var f = function() { var x = 3; return x + y },
			result = new lively.AST.ClosureAnalyzer().findUnboundVariableNames(f);
		this.assertEqualState(['y'], result);
	},
	test02RecreateClosure: function() {
		var f = function() { var x = 3; return x + y },
			closure = lively.Closure.fromFunction(f, {y: 2}),
			f2 = closure.recreateFunc();
		this.assertEquals(f.toString(), f2.toString());
		this.assertEquals(5, f2());
	},
	test03ClosureCanBindThis: function() {
		var obj = {},
			closure = lively.Closure.fromFunction(function() { this.testCalled = true }, {'this': obj});
		obj.foo = closure.recreateFunc();
		obj.foo();
		this.assert(obj.testCalled, 'this not bound');
	},
	test04LateBoundThis: function() {
		var obj = {name: 'obj1'},
			closure = lively.Closure.fromFunction(function() { return this.name }, {'this': obj});
		obj.foo = closure.recreateFunc();
		this.assertEqual('obj1', obj.foo());
		var obj2 = Object.inherit(obj);
		obj2.name = 'obj2';
		this.assertEqual('obj2', obj2.foo());
	},
	test05ThisBoundInSuper: function() {
		var obj1 = {bar: function bar() { this.foo = 1 }.asScript()},
			obj2 = Object.inherit(obj1);
		obj2.bar = function bar() { $super() }.asScriptOf(obj2);
		obj2.bar();
		this.assertEquals(1, obj2.foo);
		this.assert(!obj1.foo, 'foo was set in obj1');
		this.assert(obj2.hasOwnProperty('foo'), 'foo not set in obj2')
	},
	test06SuperBoundStatically: function() {
		var obj1 = {bar: function bar() { this.foo = 1 }.asScript()},
			obj2 = Object.inherit(obj1),
			obj3 = Object.inherit(obj2);
		obj2.bar = function bar() { $super() }.asScriptOf(obj2);
		obj3.bar();
		this.assertEquals(1, obj3.foo);
		this.assert(!obj1.foo, 'foo was set in obj1');
		this.assert(!obj2.foo, 'foo was set in obj2');
	},
	test07StoreFunctionProperties: function() {
		var func = function() { return 99 };
		func.someProperty = 'foo';
		var closure = lively.Closure.fromFunction(func),
			recreated = closure.recreateFunc();
		this.assertEquals('foo', recreated.someProperty);
	},





});

}) // end of module