package org.dynjs.runtime.interp; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.dynjs.exception.ThrowException; import org.dynjs.parser.CodeVisitor; import org.dynjs.parser.Statement; import org.dynjs.parser.ast.AdditiveExpression; import org.dynjs.parser.ast.ArrayLiteralExpression; import org.dynjs.parser.ast.AssignmentExpression; import org.dynjs.parser.ast.BitwiseExpression; import org.dynjs.parser.ast.BitwiseInversionOperatorExpression; import org.dynjs.parser.ast.BlockStatement; import org.dynjs.parser.ast.BooleanLiteralExpression; import org.dynjs.parser.ast.BracketExpression; import org.dynjs.parser.ast.BreakStatement; import org.dynjs.parser.ast.CaseClause; import org.dynjs.parser.ast.CatchClause; import org.dynjs.parser.ast.CommaOperator; import org.dynjs.parser.ast.CompoundAssignmentExpression; import org.dynjs.parser.ast.ContinueStatement; import org.dynjs.parser.ast.DefaultCaseClause; import org.dynjs.parser.ast.DeleteOpExpression; import org.dynjs.parser.ast.DoWhileStatement; import org.dynjs.parser.ast.DotExpression; import org.dynjs.parser.ast.EmptyStatement; import org.dynjs.parser.ast.EqualityOperatorExpression; import org.dynjs.parser.ast.Expression; import org.dynjs.parser.ast.ExpressionStatement; import org.dynjs.parser.ast.FloatingNumberExpression; import org.dynjs.parser.ast.ForExprInStatement; import org.dynjs.parser.ast.ForExprOfStatement; import org.dynjs.parser.ast.ForExprStatement; import org.dynjs.parser.ast.ForVarDeclInStatement; import org.dynjs.parser.ast.ForVarDeclOfStatement; import org.dynjs.parser.ast.ForVarDeclStatement; import org.dynjs.parser.ast.FunctionCallExpression; import org.dynjs.parser.ast.FunctionDeclaration; import org.dynjs.parser.ast.FunctionExpression; import org.dynjs.parser.ast.IdentifierReferenceExpression; import org.dynjs.parser.ast.IfStatement; import org.dynjs.parser.ast.InOperatorExpression; import org.dynjs.parser.ast.OfOperatorExpression; import org.dynjs.parser.ast.InstanceofExpression; import org.dynjs.parser.ast.IntegerNumberExpression; import org.dynjs.parser.ast.LogicalExpression; import org.dynjs.parser.ast.LogicalNotOperatorExpression; import org.dynjs.parser.ast.MultiplicativeExpression; import org.dynjs.parser.ast.NamedValue; import org.dynjs.parser.ast.NewOperatorExpression; import org.dynjs.parser.ast.NullLiteralExpression; import org.dynjs.parser.ast.NumberLiteralExpression; import org.dynjs.parser.ast.ObjectLiteralExpression; import org.dynjs.parser.ast.PostOpExpression; import org.dynjs.parser.ast.PreOpExpression; import org.dynjs.parser.ast.ProgramTree; import org.dynjs.parser.ast.PropertyAssignment; import org.dynjs.parser.ast.PropertyGet; import org.dynjs.parser.ast.PropertySet; import org.dynjs.parser.ast.RegexpLiteralExpression; import org.dynjs.parser.ast.RelationalExpression; import org.dynjs.parser.ast.ReturnStatement; import org.dynjs.parser.ast.StrictEqualityOperatorExpression; import org.dynjs.parser.ast.StringLiteralExpression; import org.dynjs.parser.ast.SwitchStatement; import org.dynjs.parser.ast.TernaryExpression; import org.dynjs.parser.ast.ThisExpression; import org.dynjs.parser.ast.ThrowStatement; import org.dynjs.parser.ast.TryStatement; import org.dynjs.parser.ast.TypeOfOpExpression; import org.dynjs.parser.ast.UnaryMinusExpression; import org.dynjs.parser.ast.UnaryPlusExpression; import org.dynjs.parser.ast.VariableDeclaration; import org.dynjs.parser.ast.VariableStatement; import org.dynjs.parser.ast.VoidOperatorExpression; import org.dynjs.parser.ast.WhileStatement; import org.dynjs.parser.ast.WithStatement; import org.dynjs.parser.js.Position; import org.dynjs.runtime.BasicBlock; import org.dynjs.runtime.BlockManager; import org.dynjs.runtime.BlockManager.Entry; import org.dynjs.runtime.Completion; import org.dynjs.runtime.DynArray; import org.dynjs.runtime.DynObject; import org.dynjs.runtime.EnvironmentRecord; import org.dynjs.runtime.ExecutionContext; import org.dynjs.runtime.JSFunction; import org.dynjs.runtime.JSObject; import org.dynjs.runtime.PropertyDescriptor; import org.dynjs.runtime.Reference; import org.dynjs.runtime.Types; import org.dynjs.runtime.builtins.types.BuiltinArray; import org.dynjs.runtime.builtins.types.BuiltinNumber; import org.dynjs.runtime.builtins.types.BuiltinObject; import org.dynjs.runtime.builtins.types.BuiltinRegExp; public class BasicInterpretingVisitor implements CodeVisitor { private BlockManager blockManager; public BasicInterpretingVisitor(BlockManager blockManager) { this.blockManager = blockManager; } @Override public Object visit(Object context, AdditiveExpression expr, boolean strict) { if (expr.getOp().equals("+")) { return visitPlus(context, expr, strict); } else { return visitMinus(context, expr, strict); } } public Object visitPlus(Object context1, AdditiveExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = Types.toPrimitive(context, getValue(context, expr.getLhs().accept(context, this, strict))); Object rhs = Types.toPrimitive(context, getValue(context, expr.getRhs().accept(context, this, strict))); if (lhs instanceof String || rhs instanceof String) { return(Types.toString(context, lhs) + Types.toString(context, rhs)); } Number lhsNum = Types.toNumber(context, lhs); Number rhsNum = Types.toNumber(context, rhs); if (Double.isNaN(lhsNum.doubleValue()) || Double.isNaN(rhsNum.doubleValue())) { return(Double.NaN); } if (lhsNum instanceof Double || rhsNum instanceof Double) { if (lhsNum.doubleValue() == 0.0 && rhsNum.doubleValue() == 0.0) { if (Double.compare(lhsNum.doubleValue(), 0.0) < 0 && Double.compare(rhsNum.doubleValue(), 0.0) < 0) { return(-0.0); } else { return(0.0); } } return(lhsNum.doubleValue() + rhsNum.doubleValue()); } return(lhsNum.longValue() + rhsNum.longValue()); } public Object visitMinus(Object context1, AdditiveExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Number lhs = Types.toNumber(context, getValue(context, expr.getLhs().accept(context, this, strict))); Number rhs = Types.toNumber(context, getValue(context, expr.getRhs().accept(context, this, strict))); if (Double.isNaN(lhs.doubleValue()) || Double.isNaN(rhs.doubleValue())) { return(Double.NaN); } if (lhs instanceof Double || rhs instanceof Double) { if (lhs.doubleValue() == 0.0 && rhs.doubleValue() == 0.0) { if (Double.compare(lhs.doubleValue(), 0.0) < 0 && Double.compare(rhs.doubleValue(), 0.0) < 0) { return(+0.0); } } return(lhs.doubleValue() - rhs.doubleValue()); } return(lhs.longValue() - rhs.longValue()); } @Override public Object visit(Object context1, BitwiseExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Long lhsNum = null; if (expr.getOp().equals(">>>")) { lhsNum = Types.toUint32(context, lhs); } else { lhsNum = Types.toInt32(context, lhs); } Object value = expr.getRhs().accept(context, this, strict); if (expr.getOp().equals("<<")) { // 11.7.1 Long rhsNum = Types.toUint32(context, getValue(context, value)); int shiftCount = rhsNum.intValue() & 0x1F; return((int) (lhsNum.longValue() << shiftCount)); } else if (expr.getOp().equals(">>")) { // 11.7.2 Long rhsNum = Types.toUint32(context, getValue(context, value)); int shiftCount = rhsNum.intValue() & 0x1F; return((int) (lhsNum.longValue() >> shiftCount)); } else if (expr.getOp().equals(">>>")) { // 11.7.3 Long rhsNum = Types.toUint32(context, getValue(context, value)); int shiftCount = rhsNum.intValue() & 0x1F; return(lhsNum.longValue() >>> shiftCount); } else if (expr.getOp().equals("&")) { Long rhsNum = Types.toInt32(context, getValue(context, value)); return(lhsNum.longValue() & rhsNum.longValue()); } else if (expr.getOp().equals("|")) { Long rhsNum = Types.toInt32(context, getValue(context, value)); return(lhsNum.longValue() | rhsNum.longValue()); } else if (expr.getOp().equals("^")) { Long rhsNum = Types.toInt32(context, getValue(context, value)); return(lhsNum.longValue() ^ rhsNum.longValue()); } return null; // not reached } @Override public Object visit(Object context1, ArrayLiteralExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; DynArray array = BuiltinArray.newArray(context); int i = 0; for (Expression each : expr.getExprs()) { Object value = null; if (each != null) { value = getValue(context, each.accept(context, this, strict)); array.defineOwnProperty(context, "" + i, PropertyDescriptor.newPropertyDescriptorForObjectInitializer(value), false); } ++i; } array.put(context, "length", (long) i, true); return(array); } @Override public Object visit(Object context1, AssignmentExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = expr.getLhs().accept(context, this, strict); if (!(lhs instanceof Reference)) { throw new ThrowException(context, context.createReferenceError(expr.getLhs() + " is not a reference")); } Reference lhsRef = (Reference) lhs; Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); lhsRef.putValue(context, rhs); return(rhs); } @Override public Object visit(Object context1, BitwiseInversionOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; return(~Types.toInt32(context, getValue(context, expr.getExpr().accept(context, this, strict)))); } @Override public Object visit(Object context1, BlockStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; List<Statement> content = statement.getBlockContent(); Object completionValue = Types.UNDEFINED; for (Statement each : content) { Position position = each.getPosition(); if (position != null) { context.setLineNumber(position.getLine()); } Completion completion = (Completion) each.accept(context, this, strict); if (completion.type == Completion.Type.NORMAL) { completionValue = completion.value; continue; } if (completion.type == Completion.Type.CONTINUE) { return(completion); } if (completion.type == Completion.Type.RETURN) { return(completion); } if (completion.type == Completion.Type.BREAK) { completion.value = completionValue; if (completion.target != null && statement.getLabels().contains(completion.target)) { return(Completion.createNormal(completionValue)); } else { return(completion); } } } return(Completion.createNormal(completionValue)); } @Override public Object visit(Object context1, BooleanLiteralExpression expr, boolean strict) { return(expr.getValue()); } @Override public Object visit(Object context, BreakStatement statement, boolean strict) { return(Completion.createBreak(statement.getTarget())); } @Override public Object visit(Object context, CaseClause clause, boolean strict) { // not used, handled by switch-statement return null; } @Override public Object visit(Object context, DefaultCaseClause clause, boolean strict) { // not used, handled by switch-statement return null; } @Override public Object visit(Object context, CatchClause clause, boolean strict) { // not used, handled by try-statement return null; } @Override public Object visit(Object context1, CompoundAssignmentExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object r = expr.getRootExpr().accept(context, this, strict); Object lref = expr.getRootExpr().getLhs().accept(context, this, strict); if (lref instanceof Reference) { if (((Reference) lref).isStrictReference()) { if (((Reference) lref).getBase() instanceof EnvironmentRecord) { if (((Reference) lref).getReferencedName().equals("arguments") || ((Reference) lref).getReferencedName().equals("eval")) { throw new ThrowException(context, context.createSyntaxError("invalid assignment: " + ((Reference) lref).getReferencedName())); } } } ((Reference) lref).putValue(context, r); return(r); } throw new ThrowException(context, context.createReferenceError("cannot assign to non-reference")); } @Override public Object visit(Object context, ContinueStatement statement, boolean strict) { return(Completion.createContinue(statement.getTarget())); } @Override public Object visit(Object context1, DeleteOpExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object result = expr.getExpr().accept(context, this, strict); if (!(result instanceof Reference)) { return(true); } Reference ref = (Reference) result; if (ref.isUnresolvableReference()) { if (strict) { throw new ThrowException(context, context.createSyntaxError("cannot delete unresolvable reference")); } else { return(true); } } if (ref.isPropertyReference()) { return(Types.toObject(context, ref.getBase()).delete(context, ref.getReferencedName(), ref.isStrictReference())); } if (ref.isStrictReference()) { throw new ThrowException(context, context.createSyntaxError("cannot delete from environment record binding")); } EnvironmentRecord bindings = (EnvironmentRecord) ref.getBase(); return(bindings.deleteBinding(context, ref.getReferencedName())); } @Override public Object visit(Object context1, DoWhileStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Expression testExpr = statement.getTest(); Statement block = statement.getBlock(); Object v = null; while (true) { Completion completion = invokeCompiledBlockStatement(context, "DoWhile", block); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.CONTINUE) { if (completion.target == null) { // nothing } else if (!statement.getLabels().contains(completion.target)) { return(completion); } } else if (completion.type == Completion.Type.BREAK) { if (completion.target == null) { break; } else if (!statement.getLabels().contains(completion.target)) { return(completion); } else { break; } } else if (completion.type == Completion.Type.RETURN) { return(Completion.createReturn(v)); } Boolean testResult = Types.toBoolean(getValue(context, testExpr.accept(context, this, strict))); if (!testResult) { break; } } return(Completion.createNormal(v)); } @Override public Object visit(Object context, EmptyStatement statement, boolean strict) { return(Completion.createNormal()); } @Override public Object visit(Object context1, EqualityOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); if (expr.getOp().equals("==")) { return(Types.compareEquality(context, lhs, rhs)); } else { return(!Types.compareEquality(context, lhs, rhs)); } } @Override public Object visit(Object context1, CommaOperator expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; getValue(context, expr.getLhs().accept(context, this, strict)); return(getValue(context, expr.getRhs().accept(context, this, strict))); // leave RHS on the stack } @Override public Object visit(Object context1, ExpressionStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Expression expr = statement.getExpr(); if (expr instanceof FunctionDeclaration) { return(Completion.createNormal()); } else { return(Completion.createNormal(getValue(context, expr.accept(context, this, strict)))); } } @Override public Object visit(Object context1, FloatingNumberExpression expr, boolean strict) { return(expr.getValue()); } @Override public Object visit(Object context1, ForExprInStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object exprRef = statement.getRhs().accept(context, this, strict); Object exprValue = getValue(context, exprRef); if (exprValue == Types.NULL || exprValue == Types.UNDEFINED) { return(Completion.createNormal()); } JSObject obj = Types.toObject(context, exprValue); Object v = null; List<String> names = obj.getAllEnumerablePropertyNames().toList(); for (String each : names) { Object lhsRef = statement.getExpr().accept(context, this, strict); if (lhsRef instanceof Reference) { ((Reference) lhsRef).putValue(context, each); } Completion completion = (Completion) statement.getBlock().accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForIn", statement.getBlock()); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { return(completion); } } if (completion.type == Completion.Type.RETURN || completion.type == Completion.Type.BREAK) { return(completion); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, ForExprOfStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object exprRef = statement.getRhs().accept(context, this, strict); Object exprValue = getValue(context, exprRef); if (exprValue == Types.NULL || exprValue == Types.UNDEFINED) { return(Completion.createNormal()); } JSObject obj = Types.toObject(context, exprValue); Object v = null; List<String> names = obj.getAllEnumerablePropertyNames().toList(); for (String each : names) { Object lhsRef = statement.getExpr().accept(context, this, strict); if (lhsRef instanceof Reference) { Reference propertyRef = context.createPropertyReference(obj, each); ((Reference) lhsRef).putValue(context, propertyRef.getValue(context)); } Completion completion = (Completion) statement.getBlock().accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForOf", statement.getBlock()); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { return(completion); } } if (completion.type == Completion.Type.RETURN || completion.type == Completion.Type.BREAK) { return(completion); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, ForExprStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; if (statement.getExpr() != null) { statement.getExpr().accept(context, this, strict); } Expression test = statement.getTest(); Expression incr = statement.getIncrement(); Statement body = statement.getBlock(); Object v = null; while (true) { if (test != null) { if (!Types.toBoolean(getValue(context, test.accept(context, this, strict)))) { break; } } Completion completion = (Completion) body.accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForExpr", body); if (completion.value != null && completion.value != Types.UNDEFINED) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { completion.value = v; return(completion); } } if (completion.type == Completion.Type.RETURN) { return(completion); } if (completion.type == Completion.Type.CONTINUE) { if (completion.target != null && !statement.getLabels().contains(completion.target)) { return(completion); } } if (incr != null) { getValue(context, incr.accept(context, this, strict)); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, ForVarDeclInStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; String varName = (String) statement.getDeclaration().accept(context, this, strict); Object exprRef = statement.getRhs().accept(context, this, strict); Object exprValue = getValue(context, exprRef); if (exprValue == Types.NULL || exprValue == Types.UNDEFINED) { return(Completion.createNormal()); } JSObject obj = Types.toObject(context, exprValue); Object v = null; List<String> names = obj.getAllEnumerablePropertyNames().toList(); for (String each : names) { Reference varRef = context.resolve(varName); varRef.putValue(context, each); Completion completion = (Completion) statement.getBlock().accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForVarDeclsIn", statement.getBlock()); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { return(completion); } } if (completion.type == Completion.Type.RETURN || completion.type == Completion.Type.BREAK) { return(completion); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, ForVarDeclOfStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; String varName = (String) statement.getDeclaration().accept(context, this, strict); Object exprRef = statement.getRhs().accept(context, this, strict); Object exprValue = getValue(context, exprRef); if (exprValue == Types.NULL || exprValue == Types.UNDEFINED) { return(Completion.createNormal()); } JSObject obj = Types.toObject(context, exprValue); Object v = null; List<String> names = obj.getAllEnumerablePropertyNames().toList(); for (String each : names) { Reference varRef = context.resolve(varName); Reference propertyRef = context.createPropertyReference(obj, each); varRef.putValue(context, propertyRef.getValue(context)); Completion completion = (Completion) statement.getBlock().accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForVarDeclsOf", statement.getBlock()); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { return(completion); } } if (completion.type == Completion.Type.RETURN || completion.type == Completion.Type.BREAK) { return(completion); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, ForVarDeclStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; List<VariableDeclaration> decls = statement.getDeclarationList(); for (VariableDeclaration each : decls) { each.accept(context, this, strict); } Expression test = statement.getTest(); Expression incr = statement.getIncrement(); Statement body = statement.getBlock(); Object v = null; while (true) { if (test != null) { if (!Types.toBoolean(getValue(context, test.accept(context, this, strict)))) { break; } } Completion completion = (Completion) body.accept(context, this, strict); //Completion completion = invokeCompiledBlockStatement(context, "ForVarDecl", body); if (completion.value != null && completion.value != Types.UNDEFINED) { v = completion.value; } if (completion.type == Completion.Type.BREAK) { if (completion.target == null || statement.getLabels().contains(completion.target)) { return(Completion.createNormal(v)); } else { completion.value = v; return(completion); } } if (completion.type == Completion.Type.RETURN) { return(completion); } if (completion.type == Completion.Type.CONTINUE) { if (completion.target != null && !statement.getLabels().contains(completion.target)) { return(completion); } } if (incr != null) { getValue(context, incr.accept(context, this, strict)); } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, FunctionCallExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object ref = expr.getMemberExpression().accept(context, this, strict); Object function = getValue(context, ref); List<Expression> argExprs = expr.getArgumentExpressions(); Object[] args = new Object[argExprs.size()]; int i = 0; for (Expression each : argExprs) { args[i] = getValue(context, each.accept(context, this, strict)); ++i; } if (!(function instanceof JSFunction)) { throw new ThrowException(context, context.createTypeError(expr.getMemberExpression() + " is not calllable")); } Object thisValue = null; if (ref instanceof Reference) { if (((Reference) ref).isPropertyReference()) { thisValue = ((Reference) ref).getBase(); } else { thisValue = ((EnvironmentRecord) ((Reference) ref).getBase()).implicitThisValue(); } } return(context.call(ref, (JSFunction) function, thisValue, args)); } @Override public Object visit(Object context, FunctionDeclaration statement, boolean strict) { return(Completion.createNormal()); } @Override public Object visit(Object context, FunctionExpression expr, boolean strict) { JSFunction compiledFn = ((ExecutionContext) context).getCompiler().compileFunction((ExecutionContext) context, expr.getDescriptor().getIdentifier(), expr.getDescriptor().getFormalParameterNames(), expr.getDescriptor().getBlock(), expr.getDescriptor().isStrict() || strict); return(compiledFn); } @Override public Object visit(Object context, IdentifierReferenceExpression expr, boolean strict) { return(((ExecutionContext) context).resolve(expr.getIdentifier())); } @Override public Object visit(Object context1, IfStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Boolean result = Types.toBoolean(getValue(context, statement.getTest().accept(context, this, strict))); if (result) { return(invokeCompiledBlockStatement(context, "Then", statement.getThenBlock())); } else if (statement.getElseBlock() != null) { return(invokeCompiledBlockStatement(context, "Else", statement.getElseBlock())); } else { return(Completion.createNormal()); } } @Override public Object visit(Object context1, InOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); if (!(rhs instanceof JSObject)) { throw new ThrowException(context, context.createTypeError(expr.getRhs() + " is not an object")); } return(((JSObject) rhs).hasProperty(context, Types.toString(context, lhs))); } @Override public Object visit(Object context1, OfOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); if (!(rhs instanceof JSObject)) { throw new ThrowException(context, context.createTypeError(expr.getRhs() + " is not an object")); } return(((JSObject) rhs).hasProperty(context, Types.toString(context, lhs))); } @Override public Object visit(Object context1, InstanceofExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); if (rhs == Types.UNDEFINED) { throw new ThrowException(context, context.createTypeError(expr.getRhs() + " is undefined.")); } if (rhs instanceof JSObject) { if (!(rhs instanceof JSFunction)) { throw new ThrowException(context, context.createTypeError(expr.getRhs() + " is not a function")); } return(((JSFunction) rhs).hasInstance(context, lhs)); } else if (rhs instanceof Class) { Class clazz = (Class) rhs; return(lhs.getClass().getName().equals(clazz.getName())); } return null; // not reached } @Override public Object visit(Object context1, IntegerNumberExpression expr, boolean strict) { return(expr.getValue()); } @Override public Object visit(Object context1, LogicalExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); if ((expr.getOp().equals("||") && Types.toBoolean(lhs)) || (expr.getOp().equals("&&") && !Types.toBoolean(lhs))) { return(lhs); } else { return expr.getRhs().accept(context, this, strict); } } @Override public Object visit(Object context1, LogicalNotOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; return(!Types.toBoolean(getValue(context, expr.getExpr().accept(context, this, strict)))); } @Override public Object visit(Object context1, DotExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object baseRef = expr.getLhs().accept(context, this, strict); Object baseValue = getValue(context, baseRef); String propertyName = expr.getIdentifier(); Types.checkObjectCoercible(context, baseValue, propertyName); return(context.createPropertyReference(baseValue, propertyName)); } @Override public Object visit(Object context1, BracketExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object baseRef = expr.getLhs().accept(context, this, strict); Object baseValue = getValue(context, baseRef); Object identifier = getValue(context, expr.getRhs().accept(context, this, strict)); Types.checkObjectCoercible(context, baseValue); String propertyName = Types.toString(context, identifier); return(context.createPropertyReference(baseValue, propertyName)); } @Override public Object visit(Object context1, MultiplicativeExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Number lval = Types.toNumber(context, getValue(context, expr.getLhs().accept(context, this, strict))); Number rval = Types.toNumber(context, getValue(context, expr.getRhs().accept(context, this, strict))); if (Double.isNaN(lval.doubleValue()) || Double.isNaN(rval.doubleValue())) { return(Double.NaN); } if (lval instanceof Double || rval instanceof Double) { switch (expr.getOp()) { case "*": return(lval.doubleValue() * rval.doubleValue()); case "/": // Divide-by-zero if (isZero(rval)) { if (isZero(lval)) { return(Double.NaN); } else if (isSameSign(lval, rval)) { return(Double.POSITIVE_INFINITY); } else { return(Double.NEGATIVE_INFINITY); } // Zero-divided-by-something } else if (isZero(lval)) { if (isSameSign(lval, rval)) { return(0L); } else { return(-0.0); } } // Regular math double primaryValue = lval.doubleValue() / rval.doubleValue(); if (isRepresentableByLong(primaryValue)) { return((long) primaryValue); } else { return(primaryValue); } case "%": if (rval.doubleValue() == 0.0) { return(Double.NaN); } return(BuiltinNumber.modulo(lval, rval)); } } else { switch (expr.getOp()) { case "*": return(lval.longValue() * rval.longValue()); case "/": if (rval.longValue() == 0L) { if (lval.longValue() == 0L) { return(Double.NaN); } else if (isSameSign(lval, rval)) { return(Double.POSITIVE_INFINITY); } else { return(Double.NEGATIVE_INFINITY); } } if (lval.longValue() == 0) { if (Double.compare(rval.doubleValue(), 0.0) > 0) { return(0L); } else { return(-0.0); } } double primaryResult = lval.doubleValue() / rval.longValue(); if (primaryResult == (long) primaryResult) { return((long) primaryResult); } else { return(primaryResult); } case "%": if (rval.longValue() == 0L) { return(Double.NaN); } return(BuiltinNumber.modulo(lval, rval)); } } return null; // not reached } @Override public Object visit(Object context1, NewOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object ref = expr.getExpr().accept(context, this, strict); Object memberExpr = getValue(context, ref); Object[] args = new Object[expr.getArgumentExpressions().size()]; int i = 0; for (Expression each : expr.getArgumentExpressions()) { args[i] = getValue(context, each.accept(context, this, strict)); ++i; } if (memberExpr instanceof JSFunction) { return(context.construct(ref, (JSFunction) memberExpr, args)); } throw new ThrowException(context, context.createTypeError("can only construct using functions")); } @Override public Object visit(Object context, NullLiteralExpression expr, boolean strict) { return(Types.NULL); } @Override public Object visit(Object context1, ObjectLiteralExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; DynObject obj = BuiltinObject.newObject(context); List<PropertyAssignment> assignments = expr.getPropertyAssignments(); for (PropertyAssignment each : assignments) { Object ref = each.accept(context, this, strict); String debugName = each.getName(); if (ref instanceof Reference) { debugName = ((Reference) ref).getReferencedName(); } Object value = getValue(context, ref); Object original = obj.getOwnProperty(context, each.getName()); PropertyDescriptor desc = null; if (each instanceof PropertyGet) { desc = PropertyDescriptor.newPropertyDescriptorForObjectInitializerGet(original, debugName, (JSFunction) value); } else if (each instanceof PropertySet) { desc = PropertyDescriptor.newPropertyDescriptorForObjectInitializerSet(original, debugName, (JSFunction) value); } else { desc = PropertyDescriptor.newPropertyDescriptorForObjectInitializer(debugName, value); } obj.defineOwnProperty(context, each.getName(), desc, false); } return(obj); } @Override public Object visit(Object context1, PostOpExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = expr.getExpr().accept(context, this, strict); if (lhs instanceof Reference) { if (((Reference) lhs).isStrictReference()) { if (((Reference) lhs).getBase() instanceof EnvironmentRecord) { if (((Reference) lhs).getReferencedName().equals("arguments") || ((Reference) lhs).getReferencedName().equals("eval")) { throw new ThrowException(context, context.createSyntaxError("invalid assignment: " + ((Reference) lhs).getReferencedName())); } } } Number newValue = null; Number oldValue = Types.toNumber(context, getValue(context, lhs)); if (oldValue instanceof Double) { switch (expr.getOp()) { case "++": newValue = oldValue.doubleValue() + 1; break; case "--": newValue = oldValue.doubleValue() - 1; break; } } else { switch (expr.getOp()) { case "++": newValue = oldValue.longValue() + 1; break; case "--": newValue = oldValue.longValue() - 1; break; } } ((Reference) lhs).putValue(context, newValue); return(oldValue); } return null; // not reached } @Override public Object visit(Object context1, PreOpExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = expr.getExpr().accept(context, this, strict); if (lhs instanceof Reference) { if (((Reference) lhs).isStrictReference()) { if (((Reference) lhs).getBase() instanceof EnvironmentRecord) { if (((Reference) lhs).getReferencedName().equals("arguments") || ((Reference) lhs).getReferencedName().equals("eval")) { throw new ThrowException(context, context.createSyntaxError("invalid assignment: " + ((Reference) lhs).getReferencedName())); } } } Number newValue = null; Number oldValue = Types.toNumber(context, getValue(context, lhs)); if (oldValue instanceof Double) { switch (expr.getOp()) { case "++": newValue = oldValue.doubleValue() + 1; break; case "--": newValue = oldValue.doubleValue() - 1; break; } } else { switch (expr.getOp()) { case "++": newValue = oldValue.longValue() + 1; break; case "--": newValue = oldValue.longValue() - 1; break; } } ((Reference) lhs).putValue(context, newValue); return(newValue); } return null; // not reached } @Override public Object visit(Object context, PropertyGet propertyGet, boolean strict) { JSFunction compiledFn = ((ExecutionContext) context).getCompiler().compileFunction((ExecutionContext) context, null, new String[]{}, propertyGet.getBlock(), strict); return(compiledFn); } @Override public Object visit(Object context, PropertySet propertySet, boolean strict) { JSFunction compiledFn = ((ExecutionContext) context).getCompiler().compileFunction((ExecutionContext) context, null, new String[]{propertySet.getIdentifier()}, propertySet.getBlock(), strict); return(compiledFn); } @Override public Object visit(Object context, NamedValue namedValue, boolean strict) { return namedValue.getExpr().accept(context, this, strict); } @Override public Object visit(Object context, RegexpLiteralExpression expr, boolean strict) { return(BuiltinRegExp.newRegExp((ExecutionContext) context, expr.getPattern(), expr.getFlags())); } @Override public Object visit(Object context1, RelationalExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lval = getValue(context, expr.getLhs().accept(context, this, strict)); Object rval = getValue(context, expr.getRhs().accept(context, this, strict)); Object r = null; switch (expr.getOp()) { case "<": r = Types.compareRelational(context, lval, rval, true); if (r == Types.UNDEFINED) { return(false); } else { return(r); } case ">": r = Types.compareRelational(context, rval, lval, false); if (r == Types.UNDEFINED) { return(false); } else { return(r); } case "<=": r = Types.compareRelational(context, rval, lval, false); if (r == Boolean.TRUE || r == Types.UNDEFINED) { return(false); } else { return(true); } case ">=": r = Types.compareRelational(context, lval, rval, true); if (r == Boolean.TRUE || r == Types.UNDEFINED) { return(false); } else { return(true); } } return null; // not reached } @Override public Object visit(Object context1, ReturnStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; if (statement.getExpr() != null) { Object value = statement.getExpr().accept(context, this, strict); return(Completion.createReturn(getValue(context, value))); } else { return(Completion.createReturn(Types.UNDEFINED)); } } @Override public Object visit(Object context1, StrictEqualityOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object lhs = getValue(context, expr.getLhs().accept(context, this, strict)); Object rhs = getValue(context, expr.getRhs().accept(context, this, strict)); Object result = null; if (expr.getOp().equals("===")) { result = Types.compareStrictEquality(context, lhs, rhs); } else { result = !Types.compareStrictEquality(context, lhs, rhs); } return(result); } @Override public Object visit(Object context, StringLiteralExpression expr, boolean strict) { return(expr.getLiteral()); } @Override public Object visit(Object context1, SwitchStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object value = getValue(context, statement.getExpr().accept(context, this, strict)); Object v = null; int numClauses = statement.getCaseClauses().size(); int startIndex = -1; int defaultIndex = -1; for (int i = 0; i < numClauses; ++i) { CaseClause each = statement.getCaseClauses().get(i); if (each instanceof DefaultCaseClause) { defaultIndex = i; continue; } Object caseTest = each.getExpression().accept(context, this, strict); if (Types.compareStrictEquality(context, value, getValue(context, caseTest))) { startIndex = i; break; } } if (startIndex < 0 && defaultIndex >= 0) { startIndex = defaultIndex; } if (startIndex >= 0) { for (int i = startIndex; i < numClauses; ++i) { CaseClause each = statement.getCaseClauses().get(i); if (each.getBlock() != null) { Completion completion = (Completion) each.getBlock().accept(context, this, strict); v = completion.value; if (completion.type == Completion.Type.BREAK) { break; } else if (completion.type == Completion.Type.RETURN) { return(completion); } } } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, TernaryExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; if (Types.toBoolean(getValue(context, expr.getTest().accept(context, this, strict)))) { return expr.getThenExpr().accept(context, this, strict); } else { return expr.getElseExpr().accept(context, this, strict); } } @Override public Object visit(Object context, ThisExpression expr, boolean strict) { return(((ExecutionContext) context).getThisBinding()); } @Override public Object visit(Object context1, ThrowStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object throwable = getValue(context, statement.getExpr().accept(context, this, strict)); // if ( throwable instanceof Throwable ) { // ((Throwable) throwable).printStackTrace(); // } throw new ThrowException(context, throwable); } @Override public Object visit(Object context, TryStatement statement, boolean strict) { Completion b = null; boolean finallyExecuted = false; try { b = invokeCompiledBlockStatement(context, "Try", statement.getTryBlock()); } catch (ThrowException e) { if (statement.getCatchClause() != null) { // BasicBlock catchBlock = new InterpretedStatement(statement.getCatchClause().getBlock(), strict); BasicBlock catchBlock = compiledBlockStatement(context, "Catch", statement.getCatchClause().getBlock()); try { b = ((ExecutionContext) context).executeCatch(catchBlock, statement.getCatchClause().getIdentifier(), e.getValue()); } catch (ThrowException e2) { if (statement.getFinallyBlock() != null) { Completion f = invokeCompiledBlockStatement(context, "Finally", statement.getFinallyBlock()); if (f.type == Completion.Type.NORMAL) { if (b != null) { return(b); } else { throw e2; } } else { return(f); } } else { throw e2; } } } if (statement.getFinallyBlock() != null) { finallyExecuted = true; Completion f = invokeCompiledBlockStatement(context, "Finally", statement.getFinallyBlock()); if (f.type == Completion.Type.NORMAL) { if (b != null) { return(b); } else { throw e; } } else { return(f); } } else { if (b != null) { return(b); } else { throw e; } } } if (!finallyExecuted && statement.getFinallyBlock() != null) { Completion f = invokeCompiledBlockStatement(context, "Finally", statement.getFinallyBlock()); if (f.type == Completion.Type.NORMAL) { return(b); } else { return(f); } } return b; } @Override public Object visit(Object context1, TypeOfOpExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; return(Types.typeof(context, expr.getExpr().accept(context, this, strict))); } @Override public Object visit(Object context1, UnaryMinusExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Object value = getValue(context, expr.getExpr().accept(context, this, strict)); Number oldValue = Types.toNumber(context, value); if (oldValue instanceof Double) { if (Double.isNaN(oldValue.doubleValue())) { return(Double.NaN); } else { return(-1 * oldValue.doubleValue()); } } else if (oldValue.longValue() == 0L) { return(-0.0); } else { return(-1 * oldValue.longValue()); } } @Override public Object visit(Object context1, UnaryPlusExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; return(Types.toNumber(context, getValue(context, expr.getExpr().accept(context, this, strict)))); } @Override public Object visit(Object context1, VariableDeclaration expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; if (expr.getExpr() != null) { Object value = getValue(context, expr.getExpr().accept(context, this, strict)); Reference var = context.resolve(expr.getIdentifier()); var.putValue(context, value); } return(expr.getIdentifier()); } @Override public Object visit(Object context, VariableStatement statement, boolean strict) { for (VariableDeclaration each : statement.getVariableDeclarations()) { each.accept(context, this, strict); } return(Completion.createNormal(Types.UNDEFINED)); } @Override public Object visit(Object context1, VoidOperatorExpression expr, boolean strict) { ExecutionContext context = (ExecutionContext) context1; getValue(context, expr.getExpr().accept(context, this, strict)); return(Types.UNDEFINED); } @Override public Object visit(Object context1, WhileStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; Expression testExpr = statement.getTest(); Statement block = statement.getBlock(); Object v = null; while (true) { Boolean testResult = Types.toBoolean(getValue(context, testExpr.accept(context, this, strict))); if (testResult) { // block.accept(context, this, strict); // Completion completion = (Completion) pop(); Completion completion = invokeCompiledBlockStatement(context, "While", block); if (completion.value != null) { v = completion.value; } if (completion.type == Completion.Type.CONTINUE) { if (completion.target == null) { continue; } else if (!statement.getLabels().contains(completion.target)) { return(completion); } else { continue; } } if (completion.type == Completion.Type.BREAK) { if (completion.target == null) { break; } else if (!statement.getLabels().contains(completion.target)) { return(completion); } else { break; } } if (completion.type == Completion.Type.RETURN) { return(Completion.createReturn(v)); } } else { break; } } return(Completion.createNormal(v)); } @Override public Object visit(Object context1, WithStatement statement, boolean strict) { ExecutionContext context = (ExecutionContext) context1; JSObject obj = Types.toObject(context, getValue(context, statement.getExpr().accept(context, this, strict))); BasicBlock block = compiledBlockStatement(context, "With", statement.getBlock()); return(context.executeWith(obj, block)); } protected BasicBlock compiledBlockStatement(Object context, String grist, Statement statement) { Entry entry = this.blockManager.retrieve(statement.getStatementNumber()); if (entry.getCompiled() == null) { BasicBlock compiledBlock = ((ExecutionContext) context).getCompiler().compileBasicBlock((ExecutionContext) context, grist, statement, ((ExecutionContext) context).isStrict()); entry.setCompiled(compiledBlock); } return entry.getCompiled(); } protected Completion invokeCompiledBlockStatement(Object context, String grist, Statement statement) { BasicBlock block = compiledBlockStatement(context, grist, statement); return block.call((ExecutionContext) context); } protected Object getValue(ExecutionContext context, Object obj) { return Types.getValue(context, obj); } private boolean isZero(Number n) { return n.doubleValue() == 0.0; } private boolean isNegativeZero(Number n) { return isZero(n) && isNegative(n); } private boolean isPositiveZero(Number n) { return isZero(n) && isPositive(n); } private boolean isNegative(Number n) { return (Double.compare(n.doubleValue(), 0.0) < 0); } private boolean isPositive(Number n) { return (Double.compare(n.doubleValue(), 0.0) >= 0); } private boolean isSameSign(Number n1, Number n2) { return (isPositive(n1) && isPositive(n2)) || (isNegative(n1) && isNegative(n2)); } private boolean isDifferentSign(Number n1, Number n2) { return (isPositive(n1) && isNegative(n2)) || (isNegative(n1) && isPositive(n2)); } private boolean isRepresentableByLong(double n) { if (isNegativeZero(n)) { return false; } return (n == (long) n); } }