mirror of https://github.com/adamdruppe/arsd.git
more stuff
This commit is contained in:
parent
31eb1590cf
commit
847f12f2e5
70
jsvar.d
70
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();
|
||||
}
|
||||
|
|
164
script.d
164
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":
|
||||
|
|
Loading…
Reference in New Issue