Support for the with statement
authorLars Knoll <lars.knoll@digia.com>
Sat, 24 Nov 2012 21:07:02 +0000 (22:07 +0100)
committerSimon Hausmann <simon.hausmann@digia.com>
Sun, 25 Nov 2012 09:20:25 +0000 (10:20 +0100)
Add the with object (list) to the environment, and check properties
there if it's available.
Generate IR statements implementing with() support.
Add two new builtin methods to enter and leave a with scope.
Implement support for the builtin's in masm.
Make sure exception handling works across with scopes.

Change-Id: I8257a16cfccc91a1acedfd740ade711b016b33fd
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
qmljs_engine.h
qmljs_environment.cpp
qmljs_environment.h
qmljs_runtime.cpp
qmljs_runtime.h
qv4codegen.cpp
qv4ir.cpp
qv4ir_p.h
qv4isel_masm.cpp
tests/with.js [new file with mode: 0644]

index 5460412..8d56627 100644 (file)
@@ -131,6 +131,7 @@ struct ExecutionEngine
 
     struct ExceptionHandler {
         ExecutionContext *context;
+        DeclarativeEnvironment::With *with;
         const uchar *code; // Interpreter state
         int targetTempIndex; // Interpreter state
         jmp_buf stackFrame;
index 121670c..5504763 100644 (file)
@@ -52,11 +52,12 @@ DeclarativeEnvironment::DeclarativeEnvironment(ExecutionEngine *e)
     arguments = 0;
     argumentCount = 0;
     locals = 0;
-    activation = 0;
     formals = 0;
     formalCount = 0;
     vars = 0;
     varCount = 0;
+    activation = 0;
+    withObject = 0;
 }
 
 DeclarativeEnvironment::DeclarativeEnvironment(FunctionObject *f, Value *args, uint argc)
@@ -64,11 +65,6 @@ DeclarativeEnvironment::DeclarativeEnvironment(FunctionObject *f, Value *args, u
     outer = f->scope;
     engine = outer->engine;
 
-    if (f->needsActivation)
-        activation = engine->newActivationObject(this);
-    else
-        activation = 0;
-
     formals = f->formalParameterList;
     formalCount = f->formalParameterCount;
     arguments = args;
@@ -85,6 +81,13 @@ DeclarativeEnvironment::DeclarativeEnvironment(FunctionObject *f, Value *args, u
     locals = varCount ? new Value[varCount] : 0;
     if (varCount)
         std::fill(locals, locals + varCount, Value::undefinedValue());
+
+    if (f->needsActivation)
+        activation = engine->newActivationObject(this);
+    else
+        activation = 0;
+
+    withObject = 0;
 }
 
 bool DeclarativeEnvironment::hasBinding(String *name) const
@@ -97,7 +100,9 @@ bool DeclarativeEnvironment::hasBinding(String *name) const
         if (__qmljs_string_equal(formals[i], name))
             return true;
     }
-    return deletableLocals.contains(name->toQString());
+    if (!deletableLocals)
+        return false;
+    return deletableLocals->contains(name->toQString());
 }
 
 void DeclarativeEnvironment::createMutableBinding(String *name, bool deletable)
@@ -106,7 +111,9 @@ void DeclarativeEnvironment::createMutableBinding(String *name, bool deletable)
     assert(deletable);
     assert(!hasBinding(name));
 
-    deletableLocals.insert(name->toQString(), Value::undefinedValue());
+    if (!deletableLocals)
+        deletableLocals = new QHash<QString, Value>();
+    deletableLocals->insert(name->toQString(), Value::undefinedValue());
 }
 
 void DeclarativeEnvironment::setMutableBinding(String *name, Value value, bool strict)
@@ -126,8 +133,9 @@ void DeclarativeEnvironment::setMutableBinding(String *name, Value value, bool s
             return;
         }
     }
-    QHash<QString, Value>::iterator it = deletableLocals.find(name->toQString());
-    if (it != deletableLocals.end()) {
+    assert(deletableLocals);
+    QHash<QString, Value>::iterator it = deletableLocals->find(name->toQString());
+    if (it != deletableLocals->end()) {
         *it = value;
         return;
     }
@@ -146,8 +154,9 @@ Value DeclarativeEnvironment::getBindingValue(String *name, bool strict) const
         if (__qmljs_string_equal(formals[i], name))
             return arguments[i];
     }
-    QHash<QString, Value>::const_iterator it = deletableLocals.find(name->toQString());
-    if (it != deletableLocals.end())
+    assert(deletableLocals);
+    QHash<QString, Value>::const_iterator it = deletableLocals->find(name->toQString());
+    if (it != deletableLocals->end())
         return *it;
 
     assert(false);
@@ -155,14 +164,33 @@ Value DeclarativeEnvironment::getBindingValue(String *name, bool strict) const
 
 bool DeclarativeEnvironment::deleteBinding(String *name)
 {
-    QHash<QString, Value>::iterator it = deletableLocals.find(name->toQString());
-    if (it != deletableLocals.end()) {
-        deletableLocals.erase(it);
-        return true;
+    if (deletableLocals) {
+        QHash<QString, Value>::iterator it = deletableLocals->find(name->toQString());
+        if (it != deletableLocals->end()) {
+            deletableLocals->erase(it);
+            return true;
+        }
     }
     return !hasBinding(name);
 }
 
+void DeclarativeEnvironment::pushWithObject(Object *with)
+{
+    With *w = new With;
+    w->next = withObject;
+    w->object = with;
+    withObject = w;
+}
+
+void DeclarativeEnvironment::popWithObject()
+{
+    assert(withObject);
+
+    With *w = withObject;
+    withObject = w->next;
+    delete w;
+}
+
 
 void ExecutionContext::init(ExecutionEngine *eng)
 {
@@ -177,6 +205,14 @@ void ExecutionContext::init(ExecutionEngine *eng)
 PropertyDescriptor *ExecutionContext::lookupPropertyDescriptor(String *name, PropertyDescriptor *tmp)
 {
     for (DeclarativeEnvironment *ctx = lexicalEnvironment; ctx; ctx = ctx->outer) {
+        if (ctx->withObject) {
+            DeclarativeEnvironment::With *w = ctx->withObject;
+            while (w) {
+                if (PropertyDescriptor *pd = w->object->__getPropertyDescriptor__(this, name, tmp))
+                    return pd;
+                w = w->next;
+            }
+        }
         if (ctx->activation) {
             if (PropertyDescriptor *pd = ctx->activation->__getPropertyDescriptor__(this, name, tmp))
                 return pd;
index b16c0f9..7a3a012 100644 (file)
@@ -59,7 +59,6 @@ struct DeclarativeEnvironment
     ExecutionEngine *engine;
     DeclarativeEnvironment *outer;
 
-    Object *activation;
     Value *arguments;
     unsigned int argumentCount;
     Value *locals;
@@ -68,18 +67,29 @@ struct DeclarativeEnvironment
     String **vars;
     unsigned int varCount;
 
+    Object *activation;
+    struct With {
+        Object *object;
+        With *next;
+    } *withObject;
+
     // these get used for createMutableBinding(..., true).
     // the only place this is being used is eval(...)
-    QHash<QString, Value> deletableLocals;
+    QHash<QString, Value> *deletableLocals;
 
     DeclarativeEnvironment(ExecutionEngine *e);
     DeclarativeEnvironment(FunctionObject *f, Value *args, uint argc);
+    ~DeclarativeEnvironment() { delete deletableLocals; }
 
     bool hasBinding(String *name) const;
     void createMutableBinding(String *name, bool deletable);
     void setMutableBinding(String *name, Value value, bool strict);
     Value getBindingValue(String *name, bool strict) const;
     bool deleteBinding(String *name);
+
+    // ### needs a bit of work in exception handlers
+    void pushWithObject(Object *with);
+    void popWithObject();
 };
 
 struct ExecutionContext
index 4053e42..eabceef 100644 (file)
@@ -819,6 +819,12 @@ void __qmljs_throw(Value value, ExecutionContext *context)
         context->leaveCallContext();
         context = context->parent;
     }
+    DeclarativeEnvironment *env = context->lexicalEnvironment;
+    while (env->withObject != handler.with) {
+        DeclarativeEnvironment::With *w = env->withObject;
+        env->withObject = w->next;
+        delete w;
+    }
 
     context->engine->exception = value;
 
@@ -831,6 +837,7 @@ void *__qmljs_create_exception_handler(ExecutionContext *context)
     context->engine->unwindStack.append(ExecutionEngine::ExceptionHandler());
     ExecutionEngine::ExceptionHandler &handler = context->engine->unwindStack.last();
     handler.context = context;
+    handler.with = context->lexicalEnvironment->withObject;
     return handler.stackFrame;
 }
 
@@ -856,6 +863,17 @@ void __qmljs_builtin_throw(Value val, ExecutionContext *context)
     __qmljs_throw(val, context);
 }
 
+void __qmljs_builtin_push_with(Value o, ExecutionContext *ctx)
+{
+    Object *obj = __qmljs_to_object(o, ctx).asObject();
+    ctx->lexicalEnvironment->pushWithObject(obj);
+}
+
+void __qmljs_builtin_pop_with(ExecutionContext *ctx)
+{
+    ctx->lexicalEnvironment->popWithObject();
+}
+
 
 } // extern "C"
 
index 4a1e348..ff8395a 100644 (file)
@@ -101,6 +101,8 @@ Value __qmljs_construct_value(ExecutionContext *context, Value func, Value *args
 
 Value __qmljs_builtin_typeof(Value val, ExecutionContext *context);
 void __qmljs_builtin_throw(Value val, ExecutionContext *context);
+void __qmljs_builtin_push_with(Value o, ExecutionContext *ctx);
+void __qmljs_builtin_pop_with(ExecutionContext *ctx);
 
 // constructors
 Value __qmljs_init_closure(IR::Function *clos, ExecutionContext *ctx);
index a8e3c0b..33bb064 100644 (file)
@@ -665,7 +665,7 @@ void Codegen::variableDeclaration(VariableDeclaration *ast)
     if (! initializer)
         initializer = _block->CONST(IR::UndefinedType, 0);
 
-    if (! _env->parent) {
+    if (! _env->parent || _function->insideWith) {
         // it's global code.
         move(_block->NAME(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn), initializer);
     } else {
@@ -1113,7 +1113,7 @@ IR::Expr *Codegen::identifier(const QString &name, int line, int col)
 {
     int index = _env->findMember(name);
 
-    if (! _function->hasDirectEval && _env->parent) {
+    if (! _function->hasDirectEval && !_function->insideWith && _env->parent) {
         if (index != -1) {
             return _block->TEMP(index);
         }
@@ -2137,9 +2137,26 @@ bool Codegen::visit(WhileStatement *ast)
     return false;
 }
 
-bool Codegen::visit(WithStatement *)
+bool Codegen::visit(WithStatement *ast)
 {
-    assert(!"with not implemented");
+    IR::BasicBlock *withBlock = _function->newBasicBlock();
+
+    _block->JUMP(withBlock);
+    _block = withBlock;
+    int withObject = _block->newTemp();
+    _block->MOVE(_block->TEMP(withObject), *expression(ast->expression));
+    IR::ExprList *args = _function->New<IR::ExprList>();
+    args->init(_block->TEMP(withObject));
+    _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_with, 0, 0), args));
+    ++_function->insideWith;
+    statement(ast->statement);
+    --_function->insideWith;
+    _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_with, 0, 0), 0));
+
+    IR::BasicBlock *next = _function->newBasicBlock();
+    _block->JUMP(next);
+    _block = next;
+
     return false;
 }
 
index 6d23129..57f6de9 100644 (file)
--- a/qv4ir.cpp
+++ b/qv4ir.cpp
@@ -234,6 +234,10 @@ static const char *builtin_to_string(Name::Builtin b)
         return "builtin_foreach_iterator_object";
     case IR::Name::builtin_foreach_next_property_name:
         return "builtin_foreach_next_property_name";
+    case IR::Name::builtin_push_with:
+        return "builtin_push_with";
+    case IR::Name::builtin_pop_with:
+        return "builtin_pop_with";
     }
     return "builtin_(###FIXME)";
 };
index 502275d..b0be0f9 100644 (file)
--- a/qv4ir_p.h
+++ b/qv4ir_p.h
@@ -276,7 +276,9 @@ struct Name: Expr {
         builtin_delete_exception_handler,
         builtin_get_exception,
         builtin_foreach_iterator_object,
-        builtin_foreach_next_property_name
+        builtin_foreach_next_property_name,
+        builtin_push_with,
+        builtin_pop_with
     };
 
     const QString *id;
@@ -601,6 +603,7 @@ struct Function {
 
     bool hasDirectEval: 1;
     bool hasNestedFunctions: 1;
+    int insideWith;
 
     template <typename _Tp> _Tp *New() { return new (pool->allocate(sizeof(_Tp))) _Tp(); }
 
@@ -613,6 +616,7 @@ struct Function {
         , codeData(0)
         , hasDirectEval(false)
         , hasNestedFunctions(false)
+        , insideWith(0)
     { this->name = newString(name); }
 
     ~Function();
index 3bd4049..270209a 100644 (file)
@@ -264,6 +264,15 @@ void InstructionSelection::callActivationProperty(IR::Call *call, IR::Temp *resu
         generateFunctionCall(result, __qmljs_foreach_next_property_name, arg);
     }
         break;
+    case IR::Name::builtin_push_with: {
+        IR::Temp *arg = call->args->expr->asTemp();
+        assert(arg != 0);
+        generateFunctionCall(Void, __qmljs_builtin_push_with, arg, ContextRegister);
+    }
+        break;
+    case IR::Name::builtin_pop_with:
+        generateFunctionCall(Void, __qmljs_builtin_pop_with, ContextRegister);
+        break;
     }
 }
 
diff --git a/tests/with.js b/tests/with.js
new file mode 100644 (file)
index 0000000..91ab5a0
--- /dev/null
@@ -0,0 +1,43 @@
+var o = { "x": 1 }
+var x = 0;
+with(o) {
+    with( { "x": 2 } ) {
+        print(x)
+    }
+    print(x)
+}
+print(x)
+
+
+function foo() {
+    var x = 0;
+    with(o) {
+        with( { "x": 2 } ) {
+            print(x)
+        }
+        print(x)
+    }
+    print(x)
+}
+
+print("\n")
+foo();
+
+
+function bar() {
+    var x = 0;
+    try {
+        with(o) {
+            with( { "x": 2 } ) {
+                print(x)
+                throw 0;
+            }
+            print(x)
+        }
+    }
+    catch(e) {}
+    print(x)
+}
+
+print("\n")
+bar();