Home Reference Source

src/EvaluatorHandlers.js

'use strict';

const Value = require('./Value');
const CompletionRecord = require('./CompletionRecord');
const ClosureValue = require('./values/ClosureValue');
const ObjectValue = require('./values/ObjectValue');
const FutureValue = require('./values/FutureValue');
const RegExpValue = require('./values/RegExpValue');
const PropertyDescriptor = require('./values/PropertyDescriptor');
const ErrorValue = require('./values/ErrorValue');
const ArrayValue = require('./values/ArrayValue');
const EvaluatorInstruction = require('./EvaluatorInstruction');



function *evaluateArrayExpression(e, n, s) {
	//let result = new ObjectValue();
	let result = new Array(n.elements.length);
	for ( let i = 0; i < n.elements.length; ++i ) {
		if ( n.elements[i] ) {
			result[i] = yield * e.branch(n.elements[i], s);
		}
	}
	if ( e.yieldPower >= 3 ) yield EvaluatorInstruction.stepMinor;
	return ArrayValue.make(result, e.realm);
}

function *evaluateAssignmentExpression(e, n, s) {
	//TODO: Account for not-strict mode
	var realm = s.realm;
	let ref = yield * e.resolveRef(n.left, s, n.operator === '=');

	if ( !ref && s.strict ) {
		return CompletionRecord.makeReferenceError(s.realm, `Invalid refrence in assignment.`);
	}

	let argument = yield * e.branch(n.right, s);
	let value;
	let cur;
	if ( e.yieldPower >= 3 ) yield EvaluatorInstruction.stepMinor;
	switch ( n.operator ) {
		case '=':
			value = argument;
			break;
		case '+=':
			cur = yield * ref.getValue();
			value = yield * cur.add(argument, realm);
			break;
		case '-=':
			cur = yield * ref.getValue();
			value = yield * cur.subtract(argument, realm);
			break;
		case '*=':
			cur = yield * ref.getValue();
			value = yield * cur.multiply(argument, realm);
			break;
		case '/=':
			cur = yield * ref.getValue();
			value = yield * cur.divide(argument, realm);
			break;
		case '%=':
			cur = yield * ref.getValue();
			value = yield * cur.mod(argument, realm);
			break;
		case '<<=':
			cur = yield * ref.getValue();
			value = yield * cur.shiftLeft(argument, realm);
			break;
		case '>>=':
			cur = yield * ref.getValue();
			value = yield * cur.shiftRight(argument, realm);
			break;
		case '>>>=':
			cur = yield * ref.getValue();
			value = yield * cur.shiftRightZF(argument, realm);
			break;
		case '|=':
			cur = yield * ref.getValue();
			value = yield * cur.bitOr(argument, realm);
			break;
		case '&=':
			cur = yield * ref.getValue();
			value = yield * cur.bitAnd(argument, realm);
			break;
		case '^=':
			cur = yield * ref.getValue();
			value = yield * cur.bitXor(argument, realm);
			break;
		case '**=':
			cur = yield * ref.getValue();
			value = yield * cur.pow(argument, realm);
			break;
		default:
			throw new Error('Unknown assignment operator: ' + n.operator);
	}

	if ( ref ) {
		yield * ref.setValue(value, s);
	} else {
		yield * s.put(n.left.name, value, s);
	}

	return value;
}

function *evaluateBinaryExpression(e, n, s) {
	if ( n.operator == '&&' || n.operator == '||' ) {
		return yield* evaluateLogicalExpression(e, n, s);
	}
	let left = yield * e.branch(n.left, s);
	let right = yield * e.branch(n.right, s);
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMinor;
	return yield * e.doBinaryEvaluation(n.operator, left, right, s);
}

function *evaluateBlockStatement(e, n, s) {
	let result = Value.undef;
	let ss = s.createBlockChild();
	for ( let statement of n.body ) {
		if ( statement.type != "FunctionDeclaration" ) continue;
		result = yield * e.branch(statement, ss);
	}

	for ( let statement of n.body ) {
		if ( statement.type == "FunctionDeclaration" ) continue;
		result = yield * e.branch(statement, ss);
	}
	return result;
}


function *evaluateBreakStatement(e, n, s) {
	let label = n.label ? n.label.name : undefined;
	if ( e.yieldPower >= 1 ) yield EvaluatorInstruction.stepMinor;
	return new CompletionRecord(CompletionRecord.BREAK, Value.undef, label);
}


function *evaluateCallExpression(e, n, s) {
	return yield * doCall(e, n, n.callee, s, function*() {
		let args = new Array(n.arguments.length);
		for ( let i = 0; i < n.arguments.length; ++i ) {
			args[i] = yield * e.branch(n.arguments[i], s);
		}
		return args;
	});
}

function *doCall(e, n, c, s, argProvider) {
	let thiz = s.strict ? Value.undef : s.global.thiz;

	let callee, base;

	if ( c.type == 'Super') {
		callee = yield * e.branch(c, s);
		thiz = s.thiz;
	} else if ( c.type === 'MemberExpression' ) {
		thiz = base = yield * e.branch(c.object, s);
		callee = yield * e.partialMemberExpression(thiz, c, s);
		if ( c.object.type == "Super" ) thiz = s.thiz;
		if ( callee instanceof CompletionRecord ) {
			if ( callee.type == CompletionRecord.THROW ) return callee;
			callee = callee.value;
		}
	} else {
		callee = yield * e.branch(c, s);
	}

	if ( n.type === 'NewExpression' ) {
		thiz = yield * callee.makeThisForNew(s.realm);
		if ( thiz instanceof CompletionRecord ) {
			if ( thiz.type == CompletionRecord.THROW ) return thiz;
			thiz = thiz.value;
		}
	}

	if ( typeof callee.rawCall === 'function' ) {
		return yield * callee.rawCall(n, e, s);
	}

	//console.log("Calling", callee, callee.call);

	let args = yield * argProvider();

	let name = c.srcName || c.source() || callee.jsTypeName;

	if ( e.yieldPower >= 1 ) yield EvaluatorInstruction.stepMajor;

	if ( !callee.isCallable ) {
		let err = CompletionRecord.makeTypeError(e.realm, '' + name + ' is not a function');
		yield * err.addExtra({
			code: 'CallNonFunction',
			target: callee,
			targetAst: c,
			targetName: name,
			base: base
		});
		return err;
	}

	if ( e.debug ) {
		e.incrCtr('fxInvocationCount', c.srcName);
	}

	let callResult = callee.call(thiz, args, s, {
		asConstructor: n.type === 'NewExpression',
		callNode: n,
		evaluator: e,
		callee: callee
	});

	if ( callResult instanceof CompletionRecord ) return callResult;

	if ( typeof callResult.next !== 'function' ) {
		console.log('Generator Failure', callResult);
		return CompletionRecord.makeTypeError(e.realm, '' + name + ' didnt make a generator');
	}

	let result = yield * callResult;
	if ( n.type === 'NewExpression' ) {
		//TODO: If a constructor returns, you actually use that value
		if ( result instanceof Value ) {
			if ( result.specTypeName === 'undefined' ) return thiz;
			return result;
		}
		return thiz;
	} else {
		return result;
	}
}

let classFeatures = {};
function* addMethodFnToClass(fx, clazz, proto, e, m, s) {
	if ( m.kind == 'constructor' ) {
		//Special handling for this below.
	} else {
		let ks;
		fx.funcSourceAST = m;
		if ( m.computed ) {
			let k = yield * e.branch(m.key, s);
			ks = yield * k.toStringNative(e.realm);
		} else {
			ks = m.key.name;
		}

		let pd;

		if ( m.static ) {
			fx.superTarget = clazz.getPrototype();
			if ( Object.prototype.hasOwnProperty.call(clazz.properties, ks) ) {
				pd = clazz.properties[ks];
			} else {
				pd = new PropertyDescriptor(Value.undef);
				clazz.rawSetProperty(ks, pd);
			}
		} else {
			fx.superTarget = proto.getPrototype();
			if ( Object.prototype.hasOwnProperty.call(proto.properties, ks) ) {
				pd = proto.properties[ks];
			} else {
				pd = new PropertyDescriptor(Value.undef);
				proto.rawSetProperty(ks, pd);
			}
		}
		switch (m.kind) {
			case 'set':
				pd.setter = fx;
				break;
			case 'get':
				pd.getter = fx;
				break;
			case 'method':
				pd.value = fx;
				break;
		}
	}
	return Value.undef;
}
classFeatures.MethodDefinition = function*(clazz, proto, e, m, s) {
	yield * addMethodFnToClass(yield * e.branch(m.value, s), clazz, proto, e, m, s);
};
classFeatures.ClassMethod = function*(clazz, proto, e, m, s) {
	let fx = yield * evaluateFunctionExpression(e, m, s);
	return yield * addMethodFnToClass(fx, clazz, proto, e, {
		kind: m.kind,
		static: m.static,
		key: m.key,
	}, s);

};
classFeatures.EmptyStatement = function*() { return Value.undef; }

function *evaluateClassExpression(e, n, s) {
	let clazz = undefined;
	for ( let m of n.body.body ) {
		if ( m.type == "MethodDefinition" && m.kind == "constructor") {
			clazz = yield * e.branch(m.value, s);
			clazz.superTarget = clazz;
			clazz.funcSourceAST = n;
			break;
		}
	}

	let sc;
	if ( n.superClass ) {
		sc = yield * e.branch(n.superClass, s);
	}

	if ( !clazz ) {
		clazz = new ObjectValue(e.realm);
		if ( sc ) {
			clazz.call = function*(thiz, args, scope, extra) {
				yield * sc.call(thiz, args, scope, extra);
				return Value.undef;
			}
		} else {
			clazz.call = function*() { return Value.undef; };
		}
	}
	

	let proto = new ObjectValue(e.realm);
	yield * clazz.set('prototype', proto);
	yield * clazz.set('name', Value.fromNative(n.id.name));
	yield * proto.set('constructor', clazz);

	if ( sc ) {
		clazz.setPrototype(sc);
		proto.setPrototype(sc.getPrototypeProperty());
		clazz.parentClassInstance = sc;
	}
	clazz.superTarget = clazz.getPrototype();

	s.add(n.id.name, clazz);

	if ( e.yieldPower >= 3 ) yield EvaluatorInstruction.stepMinor;
	for ( let m of n.body.body ) {
		if ( ! module.exports.classFeatures[m.type] ) throw new Error("Unsuported Class Feature " + m.type)
		yield * module.exports.classFeatures[m.type](clazz, proto, e, m, s);
		//TODO: Support getters and setters
	}
	return clazz;
}


function *evaluateClassDeclaration(e, n, s) {
	let clazz = yield * evaluateClassExpression(e, n, s);
	yield * s.put(n.id.name, clazz);
	return clazz;
}

function *evaluateConditionalExpression(e, n, s) {
	let test = yield * e.branch(n.test, s);
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMinor;
	if ( test.truthy ) {
		return yield * e.branch(n.consequent, s);
	} else {
		if ( n.alternate ) {
			return yield * e.branch(n.alternate, s);
		}
	}
	return Value.undef;
}


function *evaluateContinueStatement(e, n, s) {
	let label = n.label ? n.label.name : undefined;
	let val = new CompletionRecord(CompletionRecord.CONTINUE, Value.undef, label);
	if ( e.yieldPower >= 1 ) yield EvaluatorInstruction.stepMinor;
	return val;
}

function *evaluateDoWhileStatement(e, n, s) {
	let last = Value.undef;
	let that = e;
	var gen = function*() {
		do {
			last = yield that.branchFrame('continue', n.body, s, {label: n.label});
		} while ( (yield * that.branch(n.test, s)).truthy );
	};
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMinor;
	e.pushFrame({generator: gen(), type: 'loop', label: n.label, ast: n});


	let finished = yield EvaluatorInstruction.waitForFramePop;
	return Value.undef;
}

function *evaluateEmptyStatement(e, n, s) {
	if ( e.yieldPower >= 5 ) yield EvaluatorInstruction.stepMinor;
	return Value.undef;
}

function *evaluateExpressionStatement(e, n, s) {
	if ( e.yieldPower > 4 ) yield EvaluatorInstruction.stepMinor;
	return yield * e.branch(n.expression, s);
}

function *evaluateIdentifier(e, n, s) {
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMinor;
	if ( n.name === 'undefined' ) return Value.undef;
	if ( !s.has(n.name) ) {
		// Allow undeclared varibles to be null?
		if ( false ) return Value.undef;
		let err = CompletionRecord.makeReferenceError(e.realm, `${n.name} is not defined`);
		yield * err.addExtra({code: 'UndefinedVariable', when: 'read', ident: n.name, strict: s.strict});
		return yield err;
	}
	return s.get(n.name);
}

function *evaluateIfStatement(e, n, s) {
	if ( e.yieldPower >= 2 ) yield EvaluatorInstruction.stepStatement;
	let test = yield * e.branch(n.test, s);
	if ( test.truthy ) {
		return yield * e.branch(n.consequent, s);
	} else {
		if ( n.alternate ) {
			return yield * e.branch(n.alternate, s);
		}
	}
	return Value.undef;
}

function *evaluateImportDeclaration(e, n, s ) {
	return Value.undef;
}

function* genForLoop(e, n, s) {
	let test = Value.true;
	
	let createPerIterationEnvironment = (n.init && n.init.kind == 'let') ? (p) => {
		let is = s.createChild();
		for ( let dec of n.init.declarations ) {
			is.addBlock(dec.id.name, p.get(dec.id.name));
		}
		return is;
	} : (p) => p;

	let is = createPerIterationEnvironment(s);
	if ( n.test ) test = yield * e.branch(n.test, s);
	let last = Value.undef;
	while ( test.truthy ) {
		e.topFrame.ast = n;
		if ( e.yieldPower > 0 ) yield EvaluatorInstruction.eventLoopBodyStart;
		last = yield e.branchFrame('continue', n.body, is, {label: n.label});
		is = createPerIterationEnvironment(is);
		if ( n.update ) yield * e.branch(n.update, is);
		if ( n.test ) test = yield * e.branch(n.test, is);
	}
};

function *evaluateForStatement(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepStatement;
	if ( n.init ) yield * e.branch(n.init, s);

	e.pushFrame({generator: genForLoop(e, n, s), type: 'loop', label: n.label, ast: n});


	let finished = yield EvaluatorInstruction.waitForFramePop;
	return Value.undef;
}

function *evaluateForInStatement(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepStatement;
	let last = Value.undef;
	let object = yield * e.branch(n.right, s);
	let names = object.observableProperties(s.realm);
	let that = e;
	let ref;
	s = s.createBlockChild();

	if ( n.left.type === 'VariableDeclaration' ) {
		let decl = n.left.declarations[0];
		if ( decl.kind == 'var') s.add(decl.id.name, Value.undef);
		else s.addBlock(decl.id.name, Value.undef);
		ref = s.ref(decl.id.name, s);
	} else {
		ref = s.ref(n.left.name, s);
	}
	if ( !ref ) {
		if ( s.strict ) return CompletionRecord.makeReferenceError(s.realm, `${n.left.name} is not defined`);
		//Create an var in global scope if varialbe doesnt exist and not in strict mode.
		s.global.add(n.left.name, Value.undef)
		ref = s.ref(n.left.name);
	}
	var gen = function*() {
		for ( let name of names ) {
			yield * ref.setValue(name);
			last = yield that.branchFrame('continue', n.body, s, {label: n.label});
		}
	};
	e.pushFrame({generator: gen(), type: 'loop', label: n.label, ast: n});


	let finished = yield EvaluatorInstruction.waitForFramePop;
	return Value.undef;
}

//TODO: For of does more crazy Symbol iterator stuff
function *evaluateForOfStatement(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepStatement;
	let last = Value.undef;
	let object = yield * e.branch(n.right, s);
	let names = object.observableProperties(s.realm);
	let that = e;
	let ref;
	s = s.createBlockChild();
	if ( n.left.type === 'VariableDeclaration' ) {
		let decl = n.left.declarations[0];
		if ( decl.kind == 'var') s.add(decl.id.name, Value.undef);
		else s.addBlock(decl.id.name, Value.undef);
		//yield * s.put(n.left.declarations[0].id.name, Value.undef);
		ref = s.ref(n.left.declarations[0].id.name, s.realm);
	} else {
		ref = s.ref(n.left.name, s.realm);
	}

	var gen = function*() {
		for ( let name of names ) {
			yield * ref.setValue(yield * object.get(yield * name.toStringNative()));
			last = yield that.branchFrame('continue', n.body, s, {label: n.label});
		}
	};
	e.pushFrame({generator: gen(), type: 'loop', label: n.label});


	let finished = yield EvaluatorInstruction.waitForFramePop;
	return Value.undef;
}

function *evaluateFunctionDeclaration(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMajor;
	let closure = new ClosureValue(n, s);
	s.add(n.id.name, closure);
	return Value.undef;
}

function *evaluateFunctionExpression(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMajor;
	let value = new ClosureValue(n, s);
	if ( n.type === 'ArrowFunctionExpression' ) {
		value.thiz = s.thiz;
		if ( n.expression ) value.returnLastValue = true;
	}
	return value;
}

function *evaluateLabeledStatement(e, n, s) {
	if ( e.yieldPower >= 5 ) yield EvaluatorInstruction.stepMinor;
	return yield * e.branch(n.body, s);
}

function *evaluateLiteral(e, n, s) {
	if ( e.yieldPower >= 5 ) yield EvaluatorInstruction.stepMinor;
	if ( n.regex ) {
		return RegExpValue.make(new RegExp(n.regex.pattern, n.regex.flags), s.realm);
	} else if ( n.value === null ) {
		if ( e.raw === 'null' ) return Value.null;

		//Work around Esprima turning Infinity into null. =\
		let tryFloat = parseFloat(n.raw);
		if ( !isNaN(tryFloat) ) return e.fromNative(tryFloat, n);
		return e.fromNative(null, n);
	} else {
		return e.realm.makeLiteralValue(n.value, n);
	}
}

function *evaluateLogicalExpression(e, n, s) {
	let left = yield * e.branch(n.left, s);
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMajor;
	switch ( n.operator ) {
		case '&&':
			if ( left.truthy ) return yield * e.branch(n.right, s);
			return left;
		case '||':
			if ( left.truthy ) return left;
			return yield * e.branch(n.right, s);
		default:
			throw new Error('Unknown logical operator: ' + n.operator);
	}

}

function *evaluateMemberExpression(e, n, s) {
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMinor;
	let left = yield * e.branch(n.object, s);
	return yield * e.partialMemberExpression(left, n, s);
}

function *evaluateMetaProperty(e, n, s) {
	for ( let i = 0; i < e.frames.length - 1; ++i ) {
		let t = e.frames[i].type;
		if ( t === "function" ) {
			if ( e.frames[i+1].ast.type == "NewExpression" ) {
				return e.frames[i].callee;
			} else {
				return Value.undef;
			}
		}
	}
	return Value.undef;
}


function *evaluateObjectExpression(e, n, s) {
	//TODO: Need to wire up native prototype
	var nat = new ObjectValue(s.realm);
	for ( let i = 0; i < n.properties.length; ++i ) {
		let prop = n.properties[i];
		let key;
		if ( n.computed ) {
			key = (yield * e.branch(prop.key, s)).toNative().toString();
		} else if ( prop.key.type == 'Identifier') {
			key = prop.key.name;
		} else if ( prop.key.type == 'Literal' ) {
			key = prop.key.value.toString();
		}

		let value = yield * e.branch(prop.value, s);
		let pd;

		if ( Object.prototype.hasOwnProperty.call(nat.properties, key) ) {
			pd = nat.properties[key];
		} else {
			pd = new PropertyDescriptor(Value.undef);
			nat.rawSetProperty(key, pd);
		}

		switch ( prop.kind  ) {
			case 'init':
			default:
				pd.value = value;
				break;
			case 'get':
				pd.getter = value;
				break;
			case 'set':
				pd.setter = value;
				break;
		}

	}
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMajor;
	return nat;
}

function *evaluateProgram(e, n, s) {
	let result = Value.undef;
	if ( n.vars )
	for ( var v in n.vars ) {
		s.add(v, Value.undef);
	}
	if ( n.strict === true ) s.strict = true;
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMajor;
	for ( let statement of n.body ) {
		result = yield * e.branch(statement, s);
	}
	return result;
}

function *evaluateReturnStatement(e, n, s) {
	let retVal = Value.undef;
	if ( n.argument ) retVal = yield * e.branch(n.argument, s);
	if ( e.yieldPower >= 2 ) yield EvaluatorInstruction.stepMajor;
	return new CompletionRecord(CompletionRecord.RETURN, retVal);
}

function *evaluateSequenceExpression(e, n, s) {
	let last = Value.undef;
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMajor;
	for ( let expr of n.expressions ) {
		last = yield * e.branch(expr, s);
	}
	return last;
}

function *evaluateSuperExpression(e, n, s) {
	let fr;
	for ( let i = 0; i < e.frames.length; ++i ) {
		fr = e.frames[i];
		if ( fr.creator ) break;
	}
	let result = fr.creator.superTarget;
	return result;
}

function *evaluateSwitchStatement(e, n, s) {
	if ( e.yieldPower >= 2 ) yield EvaluatorInstruction.stepMajor;
	let discriminant = yield * e.branch(n.discriminant, s);
	let last = Value.undef;
	let matches = 0;
	let matchVals = new Array(n.cases.length);
	let matched = false;

	for ( let i = 0; i < n.cases.length; ++i ) {
		let cas = n.cases[i];
		if ( cas.test ) {
			let testval = yield * e.branch(cas.test, s);
			let equality = yield * testval.tripleEquals(discriminant);
			if ( equality.truthy ) ++matches;
			matchVals[i] = equality.truthy;
		}
	}


	let genSwitch = function*(e, n) {

		for ( let i = 0; i < n.cases.length; ++i ) {
			let cas = n.cases[i];
			if ( !matched ) {
				if ( cas.test ) {
					if ( !matchVals[i] ) continue;
				} else {
					if ( matches !== 0 ) continue;
				}
				matched = true;
			}
			for ( let statement of cas.consequent ) {
				last = yield * e.branch(statement, s);
			}
		}
	};

	e.pushFrame({generator: genSwitch(e, n), type: 'loop', label: n.label});
	let finished = yield EvaluatorInstruction.waitForFramePop;

	return last;
}

function *evaluateTaggedTemplateExpression(e, n, s) {
	let quasis = n.quasi.quasis;
	let expressions = n.quasi.expressions;
	let value = Value.fromNative(quasis[0].value.cooked);
	let fn = yield * e.branch(n.tag, s);

	let strings = [];
	let rawStrings = [];
	for ( let i = 0; i < quasis.length; ++i ) {
		strings.push(e.realm.fromNative(quasis[i].value.cooked));
		rawStrings.push(e.realm.fromNative(quasis[i].value.raw));
	}
	let sv = ArrayValue.make(strings, e.realm);
	let rv = ArrayValue.make(rawStrings, e.realm);
	sv.rawSetProperty('raw', new PropertyDescriptor(rv, false));

	let args = [sv];

	for ( let i = 0; i < expressions.length; ++i ) {
		args.push(yield * e.branch(expressions[i], s));
	}

	return yield * doCall(e, n, n.tag, s, function*() { return args; });
}

function *evaluateTemplateLiteral(e, n, s) {
	let value = Value.fromNative(n.quasis[0].value.cooked);
	for ( let i = 0; i < n.expressions.length; ++i ) {
		value = yield * value.add(yield * e.branch(n.expressions[i], s));
		value = yield * value.add(Value.fromNative(n.quasis[i + 1].value.cooked));
	}
	return value;
}

function *evaluateThisExpression(e, n, s) {
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMajor;
	if ( s.thiz ) return s.thiz;
	else return Value.undef;
}

function *evaluateThrowStatement(e, n, s) {
	let value = yield * e.branch(n.argument, s);
	if ( e.yieldPower >= 2 ) yield EvaluatorInstruction.stepMajor;
	return new CompletionRecord(CompletionRecord.THROW, value);
}

function *evaluateTryStatement(e, n, s) {
	if ( e.yieldPower >= 2 ) yield EvaluatorInstruction.stepMajor;
	if ( n.finalizer ) e.pushFrame({generator: e.branch(n.finalizer, s), type: 'finally', scope: s});
	let result = yield e.branchFrame('catch', n.block, s);
	if ( result instanceof CompletionRecord && result.type == CompletionRecord.THROW ) {
		if ( !n.handler ) {
			//console.log("No catch..., throwing", result.obj);
			return result;
		}
		let handlerScope = s.createChild();
		handlerScope.add(n.handler.param.name, result.value);
		return yield * e.branch(n.handler.body, handlerScope);
	}
	return result;
}

function *evaluateUpdateExpression(e, n, s) {
	//TODO: Need to support something like ++x[1];
	let nue;
	if ( e.yieldPower >= 3 ) yield EvaluatorInstruction.stepMajor;
	let ref = yield * e.resolveRef(n.argument, s, true);
	let old = Value.nan;

	if ( ref ) old = yield * ref.getValue();
	if ( old === undefined ) old = Value.nan;
	switch (n.operator) {
		case '++': nue = yield * old.add(e.fromNative(1)); break;
		case '--': nue = yield * old.subtract(e.fromNative(1)); break;
		default: throw new Error('Unknown update expression type: ' + n.operator);
	}
	if ( ref ) yield * ref.setValue(nue, s);

	if ( n.prefix ) return nue;
	return old;
}

function *evaluateUnaryExpression(e, n, s) {
	if ( e.yieldPower >= 4 ) yield EvaluatorInstruction.stepMajor;
	if ( n.operator === 'delete' ) {
		if ( n.argument.type !== 'MemberExpression' && n.argument.type !== 'Identifier' ) {
			//e isnt something you can delete?
			return Value.true;
		}
		let ref = yield * e.resolveRef(n.argument, s);
		if ( !ref ) return Value.false;
		if ( ref.isVariable || !ref.del ) { return Value.false; }
		let worked = ref.del(s);
		if ( worked instanceof CompletionRecord ) return yield worked;
		return Value.fromNative(worked);
	}

	if ( n.operator === 'typeof' ) {
		if ( n.argument.type == 'Identifier' ) {
			if ( !s.has(n.argument.name) ) return yield * Value.undef.typeOf();
		}
	}

	let left = yield * e.branch(n.argument, s);
	switch ( n.operator ) {
		case '-': return yield * left.unaryMinus();
		case '+': return yield * left.unaryPlus();
		case '!': return yield * left.not();
		case '~': return yield * left.bitNot();
		case 'typeof': return yield * left.typeOf();
		case 'void': return Value.undef;
		default:
			throw new Error('Unknown binary operator: ' + n.operator);
	}
}

function *evaluateVariableDeclaration(e, n, s) {
	let kind = n.kind;
	if ( e.yieldPower >= 3 ) yield EvaluatorInstruction.stepMajor;
	for ( let decl of n.declarations ) {
		let value = Value.undef;
		if ( decl.init ) value = yield * e.branch(decl.init, s);
		else if ( s.has(decl.id.name) ) continue;

		if ( kind === 'const' ) {
			s.addConst(decl.id.name, value);
		} else if ( kind == 'let') {
			s.addBlock(decl.id.name, value);
		} else {
			s.add(decl.id.name, value);
		}
	}
	return Value.undef;
}

function* genWhileLoop(e, n, s) {
	let last = Value.undef;
	while ( (yield * e.branch(n.test, s)).truthy ) {
		e.topFrame.ast = n;
		if ( e.yieldPower > 0 ) yield EvaluatorInstruction.eventLoopBodyStart;
		last = yield e.branchFrame('continue', n.body, s);
	}
}

function *evaluateWhileStatement(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMajor;
	e.pushFrame({generator: genWhileLoop(e, n, s), type: 'loop', label: n.label, ast: n});
	let finished = yield EvaluatorInstruction.waitForFramePop;
	return Value.undef;
}

function *evaluateWithStatement(e, n, s) {
	if ( e.yieldPower > 0 ) yield EvaluatorInstruction.stepMajor;
	if ( s.strict ) return CompletionRecord.makeSyntaxError(e.realm, 'Strict mode code may not include a with statement');
	let o = yield * e.branch(n.object, s);
	let ns = s.createBlockChild();
	if ( o instanceof ObjectValue ) {
		let pairs = o.getPropertyValueMap();
		for ( let p in pairs ) {
			ns.set(p, pairs[p]);
		}
	}
	return yield * e.branch(n.body, ns);
}

function findNextStep(type) {
	switch ( type ) {
		case 'ArrayExpression': return evaluateArrayExpression;
		case 'ArrowFunctionExpression': return evaluateFunctionExpression;
		case 'AssignmentExpression': return evaluateAssignmentExpression;
		case 'BinaryExpression': return evaluateBinaryExpression;
		case 'BreakStatement': return evaluateBreakStatement;
		case 'BlockStatement': return evaluateBlockStatement;
		case 'CallExpression': return evaluateCallExpression;
		case 'ClassDeclaration': return evaluateClassDeclaration;
		case 'ClassExpression': return evaluateClassExpression;
		case 'ConditionalExpression': return evaluateConditionalExpression;
		case 'DebuggerStatement': return evaluateEmptyStatement;
		case 'DoWhileStatement': return evaluateDoWhileStatement;
		case 'ContinueStatement': return evaluateContinueStatement;
		case 'EmptyStatement': return evaluateEmptyStatement;
		case 'ExpressionStatement': return evaluateExpressionStatement;
		case 'ForStatement': return evaluateForStatement;
		case 'ForInStatement': return evaluateForInStatement;
		case 'ForOfStatement': return evaluateForOfStatement;
		case 'FunctionDeclaration': return evaluateFunctionDeclaration;
		case 'FunctionExpression': return evaluateFunctionExpression;
		case 'Identifier': return evaluateIdentifier;
		case 'IfStatement': return evaluateIfStatement;
		case 'ImportDeclaration': return evaluateImportDeclaration;
		case 'LabeledStatement': return evaluateLabeledStatement;
		case 'Literal': return evaluateLiteral;
		case 'LogicalExpression': return evaluateLogicalExpression;
		case 'MetaProperty': return evaluateMetaProperty;
		case 'MemberExpression': return evaluateMemberExpression;
		case 'NewExpression': return evaluateCallExpression;
		case 'ObjectExpression': return evaluateObjectExpression;
		case 'Program': return evaluateProgram;
		case 'ReturnStatement': return evaluateReturnStatement;
		case 'SequenceExpression': return evaluateSequenceExpression;
		case 'Super': return evaluateSuperExpression;
		case 'SwitchStatement': return evaluateSwitchStatement;
		case 'TaggedTemplateExpression': return evaluateTaggedTemplateExpression;
		case 'TemplateLiteral': return evaluateTemplateLiteral;
		case 'ThisExpression': return evaluateThisExpression;
		case 'ThrowStatement': return evaluateThrowStatement;
		case 'TryStatement': return evaluateTryStatement;
		case 'UnaryExpression': return evaluateUnaryExpression;
		case 'UpdateExpression': return evaluateUpdateExpression;
		case 'VariableDeclaration': return evaluateVariableDeclaration;
		case 'WhileStatement': return evaluateWhileStatement;
		case 'WithStatement': return evaluateWithStatement;

		case 'BooleanLiteral':
		case 'StringLiteral':
		case 'NumericLiteral':
		case 'NullLiteral':
			return evaluateLiteral;

		default:
			throw new Error('Unknown AST Node Type: ' + type);
	}
}

module.exports = {
	evaluateIdentifier,
	findNextStep,
	classFeatures
};