From 31eb1590cf56d9fb5101483421e0866ae930ed0c Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Fri, 12 Jul 2013 15:45:54 -0400 Subject: [PATCH] script language class fixes --- jsvar.d | 36 +++++++++++++++++++++++++ script.d | 82 +++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/jsvar.d b/jsvar.d index 08e897b..8a2b456 100644 --- a/jsvar.d +++ b/jsvar.d @@ -742,6 +742,12 @@ struct var { this._payload._object = obj; } + public PrototypeObject _object() { + if(this._type == Type.Object) + return this._payload._object; + return null; + } + package Payload _payload; private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){ @@ -1133,6 +1139,8 @@ class PrototypeObject { string name; var _prototype; + package PrototypeObject _secondary; // HACK don't use this + PrototypeObject prototype() { if(_prototype.payloadType() == var.Type.Object) return _prototype._payload._object; @@ -1156,15 +1164,34 @@ class PrototypeObject { return n; } + PrototypeObject copyPropertiesFrom(PrototypeObject p) { + foreach(k, v; p._properties) { + this._properties[k] = v._copy; + } + return this; + } + + // FIXME: maybe throw something else /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { if(name == "prototype") return _prototype; auto curr = this; + + // for the secondary hack + bool triedOne = false; + // for the secondary hack + PrototypeObject possibleSecondary; + + tryAgain: do { auto prop = name in curr._properties; if(prop is null) { + // the secondary hack is to do more scoping in the script, it is really hackish + if(possibleSecondary is null) + possibleSecondary = curr._secondary; + if(!recurse) break; else @@ -1173,6 +1200,15 @@ class PrototypeObject { return *prop; } while(curr); + if(possibleSecondary !is null) { + curr = possibleSecondary; + if(!triedOne) { + writeln("trying again"); + triedOne = true; + goto tryAgain; + } + } + // if we're here, the property was not found, so let's implicitly create it if(throwOnFailure) throw new Exception("no such property " ~ name, file, line); diff --git a/script.d b/script.d index 63dac19..067ccf8 100644 --- a/script.d +++ b/script.d @@ -33,6 +33,7 @@ * int, float, string, array, bool, and json!q{} literals * var.prototype, var.typeof. prototype works more like Mozilla's __proto__ than standard javascript prototype. * classes: + // inheritance works class Foo : bar { // constructors, D style this(var a) { ctor.... } @@ -45,12 +46,17 @@ // "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword function virt() { - // be sure to use this. as a prefix for any class defined variables in here + b = 30; // lexical scoping is supported for static variables and functions + + // but be sure to use this. as a prefix for any class defined instance variables in here + this.instance = 10; } } var foo = new Foo(12); + foo.newFunc = function() { this.derived = 0; }; // this is ok too, and scoping, including 'this', works like in Javascript + You can also use 'new' on another object to get a copy of it. * return, break, continue, but currently cannot do labeled breaks and continues * __FILE__, __LINE__, but currently not as default arguments for D behavior (they always evaluate at the definition point) @@ -488,8 +494,18 @@ class ObjectLiteralExpression : Expression { return s; } + PrototypeObject backing; + this(PrototypeObject backing = null) { + this.backing = backing; + } + override InterpretResult interpret(PrototypeObject sc) { - var n = var.emptyObject; + var n; + if(backing is null) + n = var.emptyObject; + else + n._object = backing; + foreach(k, v; elements) n[k] = v.interpret(sc).value; @@ -503,10 +519,11 @@ class FunctionLiteralExpression : Expression { DefaultArgumentDummyObject = new PrototypeObject(); } - this(VariableDeclaration args, Expression bod) { + this(VariableDeclaration args, Expression bod, PrototypeObject lexicalScope = null) { this(); this.arguments = args; this.functionBody = bod; + this.lexicalScope = lexicalScope; } override string toString() { @@ -528,16 +545,25 @@ class FunctionLiteralExpression : Expression { // the return value is just the last expression's result that was evaluated // to return void, be sure to do a "return;" at the end of the function */ - // PrototypeObject lexicalScope; VariableDeclaration arguments; Expression functionBody; // can be a ScopeExpression btw + PrototypeObject lexicalScope; + override InterpretResult interpret(PrototypeObject sc) { assert(DefaultArgumentDummyObject !is null); var v; v._function = (var _this, var[] args) { auto argumentsScope = new PrototypeObject(); - argumentsScope.prototype = sc; + PrototypeObject scToUse; + if(lexicalScope is null) + scToUse = sc; + else { + scToUse = lexicalScope; + scToUse._secondary = sc; + } + + argumentsScope.prototype = scToUse; argumentsScope._getMember("this", false, false) = _this; argumentsScope._getMember("_arguments", false, false) = args; @@ -986,6 +1012,29 @@ class IfExpression : Expression { } } +// this is kinda like a placement new, and currently isn't exposed inside the language, +// but is used for class inheritance +class ShallowCopyExpression : Expression { + Expression e1; + Expression e2; + + this(Expression e1, Expression e2) { + this.e1 = e1; + this.e2 = e2; + } + + override InterpretResult interpret(PrototypeObject sc) { + auto v = cast(VariableExpression) e1; + if(v is null) + throw new ScriptRuntimeException("not an lvalue", 0 /* FIXME */); + + v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object); + + return InterpretResult(var(null), sc); + } + +} + class NewExpression : Expression { Expression what; Expression[] args; @@ -1003,7 +1052,7 @@ class NewExpression : Expression { var original = what.interpret(sc).value; var n = original._copy; if(n.payloadType() == var.Type.Object) { - var ctor = original._getOwnProperty("__ctor"); + var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null); if(ctor) ctor.apply(n, args); } @@ -1525,10 +1574,14 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { auto vars = new VariableDeclaration(); vars.identifiers = ["__proto", "__obj"]; - vars.initializers = [new ObjectLiteralExpression(), new ObjectLiteralExpression()]; + + auto staticScopeBacking = new PrototypeObject(); + auto instanceScopeBacking = new PrototypeObject(); + + vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; expressions ~= vars; - // FIXME: operators need to have their this be bound somehow since it isn't passwed + // FIXME: operators need to have their this be bound somehow since it isn't passed // OR the op rewrite could pass this expressions ~= new AssignExpression( @@ -1541,9 +1594,14 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { tokens.popFront(); auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); + // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) + // the inheritFrom object itself carries instance data that we need to copy onto our instance expressions ~= new AssignExpression( new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), - new VariableExpression(inheritFrom.str)); + new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); + + // and copying the instance initializer from the parent + expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); } tokens.requireNextToken(ScriptToken.Type.symbol, "{"); @@ -1579,9 +1637,9 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { expressions ~= new AssignExpression( new DotVarExpression( - new VariableExpression("__obj"), + new VariableExpression("__proto"), new VariableExpression("__ctor")), - new FunctionLiteralExpression(args, bod)); + new FunctionLiteralExpression(args, bod, staticScopeBacking)); } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { // instance variable auto decl = parseVariableDeclaration(tokens, ";"); @@ -1606,7 +1664,7 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { new VariableExpression("__proto"), new VariableExpression(ident.str), false), - new FunctionLiteralExpression(args, bod)); + new FunctionLiteralExpression(args, bod, staticScopeBacking)); } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.lineNumber); }