more stuff

This commit is contained in:
Adam D. Ruppe 2013-07-12 20:36:39 -04:00
parent 31eb1590cf
commit 847f12f2e5
2 changed files with 196 additions and 38 deletions

70
jsvar.d
View File

@ -48,21 +48,17 @@ import std.json;
it should consistently throw on missing semicolons it should consistently throw on missing semicolons
*) nesting comments, `` string literals *) nesting comments, `` string literals
*) opDispatch overloading
*) properties???// *) properties???//
a.prop on the rhs => a.prop() a.prop on the rhs => a.prop()
a.prop on the lhs => a.prop(rhs); a.prop on the lhs => a.prop(rhs);
if opAssign, it can just do a.prop(a.prop().opBinary!op(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? 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 1) ensure operator precedence is sane
2) a++ would prolly be nice, and def -a 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? 4) switches?
6) explicit type conversions somehow (cast?)
10) __FILE__ and __LINE__ as default function arguments should work like in D 10) __FILE__ and __LINE__ as default function arguments should work like in D
16) stack traces on script exceptions 16) stack traces on script exceptions
17) an exception type that we can create in the script 17) an exception type that we can create in the script
@ -80,16 +76,12 @@ import std.json;
6) gotos? labels? labeled break/continue? 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? 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: 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 flesh out prototype objects for Array, String, and Function
opEquals and stricterOpEquals looserOpEquals
opDispatch overriding
it would be nice if delegates on native types could work 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) { public int opApply(scope int delegate(ref var) dg) {
if(this.payloadType() == Type.Array) foreach(i, item; this)
foreach(ref v; this._payload._array) if(auto result = dg(item))
if(auto result = dg(v)) return result;
return result;
return 0; 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)() { public T opCast(T)() {
return this.get!T; return this.get!T;
} }
@ -611,7 +633,7 @@ struct var {
case Type.Object: case Type.Object:
static if(isAssociativeArray!T) { static if(isAssociativeArray!T) {
T ret; T ret;
foreach(k, v; this._properties) foreach(k, v; this._payload._object._properties)
ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); ret[to!(KeyType!T)(k)] = v.get!(ValueType!T);
return ret; return ret;
@ -1203,7 +1225,6 @@ class PrototypeObject {
if(possibleSecondary !is null) { if(possibleSecondary !is null) {
curr = possibleSecondary; curr = possibleSecondary;
if(!triedOne) { if(!triedOne) {
writeln("trying again");
triedOne = true; triedOne = true;
goto tryAgain; goto tryAgain;
} }
@ -1228,3 +1249,14 @@ class DynamicTypeException : Exception {
super(format("Tried to use %s as a %s", v.payloadType(), required), file, line); 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();
}

164
script.d
View File

@ -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) * 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 * scope guards, like in D
* try/catch/finally/throw * 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. * 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. 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). 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; // forces to string
a = a+0.0; // coerces to float 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. * some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D.
* if/else * if/else
* array slicing, but note that slices are rvalues currently * array slicing, but note that slices are rvalues currently
@ -42,14 +57,14 @@
static var b = 10; static var b = 10;
// instance vars go on this instance itself // 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 // "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword
function virt() { function virt() {
b = 30; // lexical scoping is supported for static variables and functions 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 // 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 default arguments supported in any position
when calling, you can use the default keyword to use the default value 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; module arsd.script;
@ -144,7 +163,7 @@ private enum string[] keywords = [
"return", "static", "struct", "import", "module", "assert", "switch", "return", "static", "struct", "import", "module", "assert", "switch",
"while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super", "while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super",
"auto", // provided as an alias for var right now, may change later "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", "var", "for", "try", "new",
"if", "do", "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 { class VariableDeclaration : Expression {
string[] identifiers; 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 { class ForExpression : Expression {
Expression initialization; Expression initialization;
Expression condition; Expression condition;
@ -1091,23 +1174,26 @@ class ExceptionBlockExpression : Expression {
assert(tryExpression !is null); assert(tryExpression !is null);
assert(catchVarDecls.length == catchExpressions.length); assert(catchVarDecls.length == catchExpressions.length);
if(catchExpressions.length) if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0))
try { try {
result = tryExpression.interpret(sc); result = tryExpression.interpret(sc);
} catch(Exception e) { } 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 // 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) { foreach(i, ce; catchExpressions) {
auto catchScope = new PrototypeObject(); auto catchScope = new PrototypeObject();
catchScope.prototype = sc; 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; catchScope._getMember(catchVarDecls[i], false, false) = ex;
result = ce.interpret(catchScope); result = ce.interpret(catchScope);
} } else
result = InterpretResult(ex, sc);
} finally { } finally {
foreach(fe; finallyExpressions) foreach(fe; finallyExpressions)
result = fe.interpret(sc); result = fe.interpret(sc);
@ -1451,7 +1537,7 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
return e1; return e1;
case "=": case "=":
tokens.popFront(); tokens.popFront();
return new AssignExpression(e1, parseAddend(tokens)); return new AssignExpression(e1, parseExpression(tokens));
case "~": case "~":
// FIXME: make sure this has the right associativity // FIXME: make sure this has the right associativity
@ -1497,11 +1583,15 @@ Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
return e1; return e1;
} }
Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) {
Expression ret; Expression ret;
ScriptToken first; ScriptToken first;
string expectedEnd = ";"; string expectedEnd = ";";
//auto e1 = parseFactor(tokens); //auto e1 = parseFactor(tokens);
while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
tokens.popFront();
}
if(!tokens.empty) { if(!tokens.empty) {
first = tokens.front; first = tokens.front;
if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) {
@ -1532,7 +1622,7 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
literal.functionBody = parseExpression(tokens); literal.functionBody = parseExpression(tokens);
auto e = new OpAssignExpression("~", new VariableExpression(i), literal); auto e = new OpAssignExpression("~", new VariableExpression(i), literal);
return e; ret = e;
} else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
auto start = tokens.front; auto start = tokens.front;
tokens.popFront(); tokens.popFront();
@ -1590,6 +1680,10 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 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, ":")) { if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) {
tokens.popFront(); tokens.popFront();
auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier);
@ -1692,6 +1786,33 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
e.ifFalse = parseExpression(tokens); e.ifFalse = parseExpression(tokens);
} }
ret = e; 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")) { } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) {
tokens.popFront(); tokens.popFront();
auto e = new ForExpression(); auto e = new ForExpression();
@ -1736,7 +1857,8 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
auto tryToken = tokens.front; auto tryToken = tokens.front;
auto e = new ExceptionBlockExpression(); auto e = new ExceptionBlockExpression();
tokens.popFront(); tokens.popFront();
e.tryExpression = parseExpression(tokens); e.tryExpression = parseExpression(tokens, true);
bool hadSomething = false; bool hadSomething = false;
while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) {
if(hadSomething) if(hadSomething)
@ -1757,8 +1879,8 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
e.finallyExpressions ~= parseExpression(tokens); e.finallyExpressions ~= parseExpression(tokens);
} }
if(!hadSomething) //if(!hadSomething)
throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber);
ret = e; ret = e;
} else } else
@ -1774,7 +1896,9 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
if(tokens.empty) if(tokens.empty)
throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", first.lineNumber); 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 // FIXME
//if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) //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); //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 "{":
case "scope": case "scope":
case "cast":
// classes // classes
case "class": case "class":
case "new": case "new":