From 847f12f2e5df5bd8cf26a5ee7fb01823d8d6cb9f Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Fri, 12 Jul 2013 20:36:39 -0400 Subject: [PATCH] more stuff --- jsvar.d | 70 +++++++++++++++++------- script.d | 164 ++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 196 insertions(+), 38 deletions(-) diff --git a/jsvar.d b/jsvar.d index 8a2b456..4cd4710 100644 --- a/jsvar.d +++ b/jsvar.d @@ -48,21 +48,17 @@ import std.json; it should consistently throw on missing semicolons *) nesting comments, `` string literals + *) opDispatch overloading *) properties???// a.prop on the rhs => a.prop() a.prop on the lhs => a.prop(rhs); if opAssign, it can just do a.prop(a.prop().opBinary!op(rhs)); But, how do we mark properties in var? Can we make them work this way in D too? - 0) add global functions to the object like assert() + 0) add global functions to the object (or at least provide a convenience function to make a pre-populated global object) 1) ensure operator precedence is sane 2) a++ would prolly be nice, and def -a - 3) loops (foreach - preferably about as well as D (int ranges, arrays, objects with opApply overloaded, and input ranges), do while?) - foreach(i; 1 .. 10) -> for(var i = 1; i < 10; i++) - foreach(i; array) -> for(var i = 0; i < array.length; i++) - foreach(i; object) -> for(var v = new object.iterator; !v.empty(); v.popFront()) { var i = v.front(); / *...* / } 4) switches? - 6) explicit type conversions somehow (cast?) 10) __FILE__ and __LINE__ as default function arguments should work like in D 16) stack traces on script exceptions 17) an exception type that we can create in the script @@ -80,16 +76,12 @@ import std.json; 6) gotos? labels? labeled break/continue? 18) what about something like ruby's blocks or macros? parsing foo(arg) { code } is easy enough, but how would we use it? - try is considered a statement right now and this only works on top level surrounded by {} - it should be usable anywhere - var FIXME: - user defined operator overloading on objects, including opCall + user defined operator overloading on objects, including opCall, opApply, and more flesh out prototype objects for Array, String, and Function - opEquals and stricterOpEquals - opDispatch overriding + looserOpEquals it would be nice if delegates on native types could work */ @@ -471,14 +463,44 @@ struct var { } } - public int opApply(int delegate(ref var) dg) { - if(this.payloadType() == Type.Array) - foreach(ref v; this._payload._array) - if(auto result = dg(v)) - return result; + public int opApply(scope int delegate(ref var) dg) { + foreach(i, item; this) + if(auto result = dg(item)) + return result; return 0; } + public int opApply(scope int delegate(var, ref var) dg) { + if(this.payloadType() == Type.Array) { + foreach(i, ref v; this._payload._array) + if(auto result = dg(var(i), v)) + return result; + } else if(this.payloadType() == Type.Object && this._payload._object !is null) { + // FIXME: if it offers input range primitives, we should use them + // FIXME: user defined opApply on the object + foreach(k, ref v; this._payload._object._properties) + if(auto result = dg(var(k), v)) + return result; + } else if(this.payloadType() == Type.String) { + // this is to prevent us from allocating a new string on each character, hopefully limiting that massively + static immutable string chars = makeAscii!(); + + foreach(i, dchar c; this._payload._string) { + var lol = ""; + if(c < 128) + lol._payload._string = chars[c .. c + 1]; + else + lol._payload._string = to!string(""d ~ c); // blargh, how slow can we go? + if(auto result = dg(var(i), lol)) + return result; + } + } + // throw invalid foreach aggregate + + return 0; + } + + public T opCast(T)() { return this.get!T; } @@ -611,7 +633,7 @@ struct var { case Type.Object: static if(isAssociativeArray!T) { T ret; - foreach(k, v; this._properties) + foreach(k, v; this._payload._object._properties) ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); return ret; @@ -1203,7 +1225,6 @@ class PrototypeObject { if(possibleSecondary !is null) { curr = possibleSecondary; if(!triedOne) { - writeln("trying again"); triedOne = true; goto tryAgain; } @@ -1228,3 +1249,14 @@ class DynamicTypeException : Exception { super(format("Tried to use %s as a %s", v.payloadType(), required), file, line); } } + +template makeAscii() { + string helper() { + string s; + foreach(i; 0 .. 128) + s ~= cast(char) i; + return s; + } + + enum makeAscii = helper(); +} diff --git a/script.d b/script.d index 067ccf8..680776b 100644 --- a/script.d +++ b/script.d @@ -11,7 +11,11 @@ * mixin aka eval (does it at runtime, so more like eval than mixin, but I want it to look like D) * scope guards, like in D * try/catch/finally/throw - * for/while (foreach coming soon) + You can use try as an expression without any following catch to return the exception: + + var a = try throw "exception";; // the double ; is because one closes the try, the second closes the var + // a is now the thrown exception + * for/while/foreach * D style operators: +-/* on all numeric types, ~ on strings and arrays, |&^ on integers. Operators can coerce types as needed: 10 ~ "hey" == "10hey". 10 + "3" == 13. Any math, except bitwise math, with a floating point component returns a floating point component, but pure int math is done as ints (unlike Javascript btw). @@ -23,7 +27,18 @@ a = "" ~ a; // forces to string a = a+0.0; // coerces to float - But I'll add nicer type stuff later. (maybe cast() actually) + Though casting is probably better. + * Type coercion via cast, similarly to D. + var a = "12"; + a.typeof == "String"; + a = cast(int) a; + a.typeof == "Integral"; + a == 12; + + Supported types for casting to: int/long (both actually an alias for long, because of how var works), float/double/real, string, char/dchar (these return *integral* types), and arrays, int[], string[], and float[]. + + This forwards directly to the D function var.opCast. + * some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D. * if/else * array slicing, but note that slices are rvalues currently @@ -42,14 +57,14 @@ static var b = 10; // instance vars go on this instance itself - var instance = 20; + var instancevar = 20; // "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword function virt() { 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; + this.instancevar = 10; } } @@ -82,6 +97,10 @@ default arguments supported in any position when calling, you can use the default keyword to use the default value in any position + + + FIXME: + * make sure superclass ctors are called */ module arsd.script; @@ -144,7 +163,7 @@ private enum string[] keywords = [ "return", "static", "struct", "import", "module", "assert", "switch", "while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super", "auto", // provided as an alias for var right now, may change later - "null", "else", "true", "eval", "goto", "enum", "case", + "null", "else", "true", "eval", "goto", "enum", "case", "cast", "var", "for", "try", "new", "if", "do", ]; @@ -589,6 +608,24 @@ class FunctionLiteralExpression : Expression { } } +class CastExpression : Expression { + string type; + Expression e1; + + override string toString() { + return "cast(" ~ type ~ ") " ~ e1.toString(); + } + + override InterpretResult interpret(PrototypeObject sc) { + var n = e1.interpret(sc).value; + foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) { + if(type == possibleType) + n = mixin("cast(" ~ possibleType ~ ") n"); + } + + return InterpretResult(n, sc); + } +} class VariableDeclaration : Expression { string[] identifiers; @@ -937,6 +974,52 @@ class ScopeExpression : Expression { } } +class ForeachExpression : Expression { + VariableDeclaration decl; + Expression subject; + Expression loopBody; + + override InterpretResult interpret(PrototypeObject sc) { + var result; + + assert(loopBody !is null); + + auto loopScope = new PrototypeObject(); + loopScope.prototype = sc; + + InterpretResult.FlowControl flowControl; + + static string doLoopBody() { return q{ + if(decl.identifiers.length > 1) { + sc._getMember(decl.identifiers[0], false, false) = i; + sc._getMember(decl.identifiers[1], false, false) = item; + } else { + sc._getMember(decl.identifiers[0], false, false) = item; + } + + auto res = loopBody.interpret(loopScope); + result = res.value; + flowControl = res.flowControl; + if(flowControl == InterpretResult.FlowControl.Break) + break; + if(flowControl == InterpretResult.FlowControl.Return) + break; + //if(flowControl == InterpretResult.FlowControl.Continue) + // this is fine, we still want to do the advancement + };} + + var what = subject.interpret(sc).value; + foreach(i, item; what) { + mixin(doLoopBody()); + } + + if(flowControl != InterpretResult.FlowControl.Return) + flowControl = InterpretResult.FlowControl.Normal; + + return InterpretResult(result, sc, flowControl); + } +} + class ForExpression : Expression { Expression initialization; Expression condition; @@ -1091,23 +1174,26 @@ class ExceptionBlockExpression : Expression { assert(tryExpression !is null); assert(catchVarDecls.length == catchExpressions.length); - if(catchExpressions.length) + if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0)) try { result = tryExpression.interpret(sc); } catch(Exception e) { + var ex = var.emptyObject; + ex.type = typeid(e).name; + ex.msg = e.msg; + ex.file = e.file; + ex.line = e.line; + // FIXME: this only allows one but it might be nice to actually do different types at some point + if(catchExpressions.length) foreach(i, ce; catchExpressions) { auto catchScope = new PrototypeObject(); catchScope.prototype = sc; - var ex = var.emptyObject; - ex.type = typeid(e).name; - ex.msg = e.msg; - ex.file = e.file; - ex.line = e.line; catchScope._getMember(catchVarDecls[i], false, false) = ex; result = ce.interpret(catchScope); - } + } else + result = InterpretResult(ex, sc); } finally { foreach(fe; finallyExpressions) result = fe.interpret(sc); @@ -1451,7 +1537,7 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { return e1; case "=": tokens.popFront(); - return new AssignExpression(e1, parseAddend(tokens)); + return new AssignExpression(e1, parseExpression(tokens)); case "~": // FIXME: make sure this has the right associativity @@ -1497,11 +1583,15 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { return e1; } -Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { +Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { Expression ret; ScriptToken first; string expectedEnd = ";"; //auto e1 = parseFactor(tokens); + + while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { + tokens.popFront(); + } if(!tokens.empty) { first = tokens.front; if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { @@ -1532,7 +1622,7 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { literal.functionBody = parseExpression(tokens); auto e = new OpAssignExpression("~", new VariableExpression(i), literal); - return e; + ret = e; } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { auto start = tokens.front; tokens.popFront(); @@ -1590,6 +1680,10 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); + expressions ~= new AssignExpression( + new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), + new StringLiteralExpression(classIdent.str)); + if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { tokens.popFront(); auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); @@ -1692,6 +1786,33 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { e.ifFalse = parseExpression(tokens); } ret = e; + } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { + tokens.popFront(); + auto e = new ForeachExpression(); + tokens.requireNextToken(ScriptToken.Type.symbol, "("); + e.decl = parseVariableDeclaration(tokens, ";"); + tokens.requireNextToken(ScriptToken.Type.symbol, ";"); + e.subject = parseExpression(tokens); + tokens.requireNextToken(ScriptToken.Type.symbol, ")"); + e.loopBody = parseExpression(tokens); + ret = e; + + expectedEnd = ""; + } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { + tokens.popFront(); + auto e = new CastExpression(); + + tokens.requireNextToken(ScriptToken.Type.symbol, "("); + e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; + if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { + e.type ~= "[]"; + tokens.popFront(); + tokens.requireNextToken(ScriptToken.Type.symbol, "]"); + } + tokens.requireNextToken(ScriptToken.Type.symbol, ")"); + + e.e1 = parseExpression(tokens); + ret = e; } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { tokens.popFront(); auto e = new ForExpression(); @@ -1736,7 +1857,8 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { auto tryToken = tokens.front; auto e = new ExceptionBlockExpression(); tokens.popFront(); - e.tryExpression = parseExpression(tokens); + e.tryExpression = parseExpression(tokens, true); + bool hadSomething = false; while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { if(hadSomething) @@ -1757,8 +1879,8 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { e.finallyExpressions ~= parseExpression(tokens); } - if(!hadSomething) - throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); + //if(!hadSomething) + //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); ret = e; } else @@ -1774,7 +1896,9 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { if(tokens.empty) throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", first.lineNumber); - if(expectedEnd.length) { + if(expectedEnd.length && consumeEnd) { + if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) + tokens.popFront(); // FIXME //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) //throw new ScriptCompileException("Parse error, missing "~expectedEnd~" at end of expression (starting on "~to!string(first.lineNumber)~"). Saw "~tokens.front.str~" instead", tokens.front.lineNumber); @@ -1901,6 +2025,8 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin case "{": case "scope": + case "cast": + // classes case "class": case "new":