mirror of https://github.com/adamdruppe/arsd.git
type matching and overloading
This commit is contained in:
parent
94c7618e80
commit
33534c838b
221
jsvar.d
221
jsvar.d
|
@ -685,6 +685,11 @@ struct var {
|
|||
this = t.toArsdJsvar();
|
||||
} else
|
||||
this = t.toArsdJsvar();
|
||||
} else static if(is(T : PrototypeObject)) {
|
||||
// support direct assignment of pre-made implementation objects
|
||||
// so prewrapped stuff can be easily passed.
|
||||
this._type = Type.Object;
|
||||
this._payload._object = t;
|
||||
} else static if(isFloatingPoint!T) {
|
||||
this._type = Type.Floating;
|
||||
this._payload._floating = t;
|
||||
|
@ -726,14 +731,9 @@ struct var {
|
|||
} else static if(isSomeString!T) {
|
||||
this._type = Type.String;
|
||||
this._payload._string = to!string(t);
|
||||
} else static if(is(T : PrototypeObject)) {
|
||||
// support direct assignment of pre-made implementation objects
|
||||
// so prewrapped stuff can be easily passed.
|
||||
this._type = Type.Object;
|
||||
this._payload._object = t;
|
||||
} else static if(is(T == class)) {
|
||||
this._type = Type.Object;
|
||||
this._payload._object = wrapNativeObject(t);
|
||||
this._payload._object = t is null ? null : wrapNativeObject(t);
|
||||
} else static if(.isScriptableOpaque!T) {
|
||||
// auto-wrap other classes with reference semantics
|
||||
this._type = Type.Object;
|
||||
|
@ -1121,13 +1121,13 @@ struct var {
|
|||
return this.get!T;
|
||||
}
|
||||
|
||||
public int opCmp(T)(T t) {
|
||||
public double opCmp(T)(T t) {
|
||||
auto f = this.get!double;
|
||||
static if(is(T == var))
|
||||
auto r = t.get!double;
|
||||
else
|
||||
auto r = t;
|
||||
return cast(int)(f - r);
|
||||
return f - r;
|
||||
}
|
||||
|
||||
public bool opEquals(T)(T t) {
|
||||
|
@ -1135,7 +1135,15 @@ struct var {
|
|||
}
|
||||
|
||||
public bool opEquals(T:var)(T t) const {
|
||||
// int and float can implicitly convert
|
||||
if(this._type == Type.Integral && t._type == Type.Floating)
|
||||
return _payload._integral == t._payload._floating;
|
||||
if(t._type == Type.Integral && this._type == Type.Floating)
|
||||
return t._payload._integral == this._payload._floating;
|
||||
|
||||
// but the others are kinda strict
|
||||
// FIXME: should this be == or === ?
|
||||
|
||||
if(this._type != t._type)
|
||||
return false;
|
||||
final switch(this._type) {
|
||||
|
@ -2017,15 +2025,16 @@ var subclassable(T)() if(is(T == class) || is(T == interface)) {
|
|||
@scriptable this(Parameters!ctor p) { super(p); }
|
||||
|
||||
static foreach(memberName; __traits(allMembers, T)) {
|
||||
static if(__traits(isVirtualMethod, __traits(getMember, T, memberName)))
|
||||
static if(memberName != "toHash")
|
||||
static foreach(overload; __traits(getOverloads, T, memberName))
|
||||
static if(__traits(isVirtualMethod, overload))
|
||||
// note: overload behavior undefined
|
||||
static if(!(functionAttributes!(__traits(getMember, T, memberName)) & (FunctionAttribute.pure_ | FunctionAttribute.safe | FunctionAttribute.trusted | FunctionAttribute.nothrow_)))
|
||||
static if(!(functionAttributes!(overload) & (FunctionAttribute.pure_ | FunctionAttribute.safe | FunctionAttribute.trusted | FunctionAttribute.nothrow_)))
|
||||
mixin(q{
|
||||
@scriptable
|
||||
override ReturnType!(__traits(getMember, T, memberName))
|
||||
override ReturnType!(overload)
|
||||
}~memberName~q{
|
||||
(Parameters!(__traits(getMember, T, memberName)) p)
|
||||
(Parameters!(overload) p)
|
||||
{
|
||||
//import std.stdio; writeln("calling ", T.stringof, ".", memberName, " - ", methodOverriddenByScript(memberName), "/", _next_devirtualized, " on ", cast(size_t) cast(void*) this);
|
||||
if(_next_devirtualized || !methodOverriddenByScript(memberName))
|
||||
|
@ -2243,6 +2252,7 @@ WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(
|
|||
|
||||
foreach(memberName; __traits(allMembers, Class)) static if(is(typeof(__traits(getMember, obj, memberName)) type)) {
|
||||
static if(is(type == function)) {
|
||||
auto os = new OverloadSet();
|
||||
foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) {
|
||||
var gen;
|
||||
gen._function = delegate (var vthis_, var[] vargs) {
|
||||
|
@ -2289,8 +2299,19 @@ WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(
|
|||
|
||||
return ret;
|
||||
};
|
||||
_properties[memberName] = gen;
|
||||
|
||||
Parameters!(overload) fargs;
|
||||
// FIXME: if an argument type is a class, we need to actually look it up in the script context somehow
|
||||
var[] definition;
|
||||
foreach(arg; fargs) {
|
||||
definition ~= var(arg);
|
||||
}
|
||||
|
||||
//import std.stdio; writeln(Class.stringof, ".", memberName, " ", definition);
|
||||
os.addOverload(OverloadSet.Overload(definition, gen));
|
||||
}
|
||||
|
||||
_properties[memberName] = var(os);
|
||||
} else {
|
||||
static if(.isScriptable!(__traits(getAttributes, __traits(getMember, Class, memberName)))())
|
||||
// if it has a type but is not a function, it is prolly a member
|
||||
|
@ -2393,6 +2414,180 @@ bool isScriptableOpaque(T)() {
|
|||
return false;
|
||||
}
|
||||
|
||||
int typeCompatibilityScore(var arg, var type) {
|
||||
int thisScore = 0;
|
||||
|
||||
if(type.payloadType == var.Type.Object && type._payload._object is null) {
|
||||
thisScore += 1; // generic match
|
||||
return thisScore;
|
||||
}
|
||||
if(arg.payloadType == var.Type.Integral && type.payloadType == var.Type.Floating) {
|
||||
thisScore += 2; // match with implicit conversion
|
||||
// FIXME: want to support implicit from whatever to bool?
|
||||
} else if(arg.payloadType != type.payloadType) {
|
||||
thisScore = 0;
|
||||
return thisScore;
|
||||
} else {
|
||||
// exact type category match
|
||||
if(type.payloadType == var.Type.Array) {
|
||||
// arrays not supported here....
|
||||
thisScore = 0;
|
||||
return thisScore;
|
||||
} else if(type.payloadType == var.Type.Object) {
|
||||
// objects are the interesting one...
|
||||
// want to see if it matches by seeing if the
|
||||
// given type is identical or its prototype is one of the given type's prototype chain
|
||||
|
||||
int depth = 0;
|
||||
PrototypeObject pt = type._payload._object;
|
||||
while(pt !is null) {
|
||||
depth++;
|
||||
pt = pt.prototype;
|
||||
}
|
||||
|
||||
if(type._payload._object is arg._payload._object)
|
||||
thisScore += 2 + depth;
|
||||
else {
|
||||
if(arg._payload._object is null)
|
||||
return 0; // null sucks.
|
||||
|
||||
auto test = type._payload._object.prototype;
|
||||
// generic object compared against generic object matches
|
||||
if(test is null && type._payload._object.prototype is null)
|
||||
thisScore += 1;
|
||||
else {
|
||||
pt = arg._payload._object;
|
||||
while(pt !is null) {
|
||||
if(pt is test) {
|
||||
thisScore += 1 + depth;
|
||||
break;
|
||||
}
|
||||
pt = pt.prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
thisScore += 3; // exact match without implicit conversion
|
||||
}
|
||||
}
|
||||
|
||||
return thisScore;
|
||||
}
|
||||
|
||||
/++
|
||||
Does dynamic dispatch to overloads in a jsvar function set.
|
||||
|
||||
History:
|
||||
Added September 1, 2020.
|
||||
+/
|
||||
class OverloadSet : PrototypeObject {
|
||||
this() {
|
||||
_properties["opCall"] = &opCall;
|
||||
_properties["apply"] = &apply;
|
||||
}
|
||||
|
||||
///
|
||||
void addIndividualOverload(alias f)() {
|
||||
var func = &f;
|
||||
var[] argTypes;
|
||||
static if(is(typeof(f) Params == __parameters))
|
||||
foreach(param; Params)
|
||||
argTypes ~= var(param.init);
|
||||
//import std.stdio; writeln("registered ", argTypes);
|
||||
overloads ~= Overload(argTypes, func);
|
||||
}
|
||||
|
||||
///
|
||||
void addOverloadsOf(alias what)() {
|
||||
foreach(alias f; __traits(getOverloads, __traits(parent, what), __traits(identifier, what)))
|
||||
addIndividualOverload!f;
|
||||
}
|
||||
|
||||
static struct Overload {
|
||||
// I don't even store the arity of a function object
|
||||
// so argTypes is the nest best thing.
|
||||
var[] argTypes;
|
||||
var func;
|
||||
}
|
||||
Overload[] overloads;
|
||||
|
||||
private bool exactMatch(var[] a, var[] b) {
|
||||
if(a.length != b.length)
|
||||
return false;
|
||||
foreach(i; 0 .. a.length) {
|
||||
if(a[i] !is b[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void addOverload(Overload o) {
|
||||
foreach(ref ov; overloads)
|
||||
if(exactMatch(ov.argTypes, o.argTypes)) {
|
||||
ov.func = o.func;
|
||||
return;
|
||||
}
|
||||
overloads ~= o;
|
||||
}
|
||||
|
||||
/*
|
||||
I might have to add Object, Exception, and others to jsvar to represent types.
|
||||
maybe even int and such.
|
||||
|
||||
An object should probably have a secret property that gives its name...
|
||||
*/
|
||||
var apply(var this_, var[] arguments) {
|
||||
return opCall(arguments[0], arguments[1].get!(var[]));
|
||||
}
|
||||
|
||||
var opCall(var this_, var[] arguments) {
|
||||
// remember script.d supports default args too.
|
||||
int bestScore = int.min;
|
||||
Overload bestMatch;
|
||||
foreach(overload; overloads) {
|
||||
if(overload.argTypes.length == 0) {
|
||||
if(bestScore < 0) {
|
||||
bestScore = 0;
|
||||
bestMatch = overload;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int thisScore = 0;
|
||||
foreach(idx, arg; arguments) {
|
||||
if(idx >= overload.argTypes.length) {
|
||||
thisScore = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// now if it is an object, if we can match, add score based on how derived the specified type is.
|
||||
// if it is the very same object, that's the best match possible. (prototype chain length + 1. and the one base point for matching at all.)
|
||||
// then if not, if the arg given can implicitly convert to the arg specified, give points based on how many prototypes the arg specified has. (plus one base point for matching at all)
|
||||
|
||||
// otherwise just give one point.
|
||||
|
||||
auto s = typeCompatibilityScore(arg, overload.argTypes[idx]);
|
||||
if(s == 0) {
|
||||
thisScore = 0;
|
||||
break;
|
||||
}
|
||||
thisScore += s;
|
||||
}
|
||||
|
||||
if(thisScore > 0 && thisScore > bestScore) {
|
||||
bestScore = thisScore;
|
||||
bestMatch = overload;
|
||||
}
|
||||
}
|
||||
|
||||
if(bestScore < 0)
|
||||
throw new Exception("no matching overload found");
|
||||
|
||||
|
||||
return bestMatch.func.apply(this_, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
bool appearsNumeric(string n) {
|
||||
if(n.length == 0)
|
||||
return false;
|
||||
|
|
264
script.d
264
script.d
|
@ -1,8 +1,18 @@
|
|||
// dmd -g -ofscripttest -unittest -main script.d jsvar.d && ./scripttest
|
||||
/*
|
||||
|
||||
FIXME: i kinda do want a catch type filter e.g. catch(Exception f)
|
||||
and perhaps overloads
|
||||
|
||||
|
||||
|
||||
For type annotations, maybe it can statically match later, but right now
|
||||
it just forbids any assignment to that variable that isn't that type.
|
||||
|
||||
I'll have to define int, float, etc though as basic types.
|
||||
|
||||
|
||||
|
||||
FIXME: I also kinda want implicit construction of structs at times.
|
||||
|
||||
REPL plan:
|
||||
|
@ -224,6 +234,8 @@ make sure superclass ctors are called
|
|||
|
||||
|
||||
History:
|
||||
September 1, 2020: added overloading for functions and type matching in `catch` blocks among other bug fixes
|
||||
|
||||
April 28, 2020: added `#{}` as an alternative to the `json!q{}` syntax for object literals. Also fixed unary `!` operator.
|
||||
|
||||
April 26, 2020: added `switch`, fixed precedence bug, fixed doc issues and added some unittests
|
||||
|
@ -1295,9 +1307,14 @@ class CastExpression : Expression {
|
|||
|
||||
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)
|
||||
switch(type) {
|
||||
foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) {
|
||||
case possibleType:
|
||||
n = mixin("cast(" ~ possibleType ~ ") n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// FIXME, we can probably cast other types like classes here.
|
||||
}
|
||||
|
||||
return InterpretResult(n, sc);
|
||||
|
@ -1307,6 +1324,7 @@ class CastExpression : Expression {
|
|||
class VariableDeclaration : Expression {
|
||||
string[] identifiers;
|
||||
Expression[] initializers;
|
||||
string[] typeSpecifiers;
|
||||
|
||||
this() {}
|
||||
|
||||
|
@ -1315,7 +1333,12 @@ class VariableDeclaration : Expression {
|
|||
foreach(i, ident; identifiers) {
|
||||
if(i)
|
||||
s ~= ", ";
|
||||
s ~= "var " ~ ident;
|
||||
s ~= "var ";
|
||||
if(typeSpecifiers[i].length) {
|
||||
s ~= typeSpecifiers[i];
|
||||
s ~= " ";
|
||||
}
|
||||
s ~= ident;
|
||||
if(initializers[i] !is null)
|
||||
s ~= " = " ~ initializers[i].toString();
|
||||
}
|
||||
|
@ -1338,6 +1361,67 @@ class VariableDeclaration : Expression {
|
|||
}
|
||||
}
|
||||
|
||||
class FunctionDeclaration : Expression {
|
||||
DotVarExpression where;
|
||||
string ident;
|
||||
FunctionLiteralExpression expr;
|
||||
|
||||
this(DotVarExpression where, string ident, FunctionLiteralExpression expr) {
|
||||
this.where = where;
|
||||
this.ident = ident;
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
var n = expr.interpret(sc).value;
|
||||
|
||||
var replacement;
|
||||
|
||||
if(expr.isMacro) {
|
||||
// can't overload macros
|
||||
replacement = n;
|
||||
} else {
|
||||
var got;
|
||||
|
||||
if(where is null) {
|
||||
got = sc._getMember(ident, false, false);
|
||||
} else {
|
||||
got = where.interpret(sc).value;
|
||||
}
|
||||
|
||||
OverloadSet os = got.get!OverloadSet;
|
||||
if(os is null) {
|
||||
os = new OverloadSet;
|
||||
}
|
||||
|
||||
os.addOverload(OverloadSet.Overload(expr.arguments ? toTypes(expr.arguments.typeSpecifiers, sc) : null, n));
|
||||
|
||||
replacement = var(os);
|
||||
}
|
||||
|
||||
if(where is null) {
|
||||
sc._getMember(ident, false, false) = replacement;
|
||||
} else {
|
||||
where.setVar(sc, replacement, false, true);
|
||||
}
|
||||
|
||||
return InterpretResult(n, sc);
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
string s = (expr.isMacro ? "macro" : "function") ~ " ";
|
||||
s ~= ident;
|
||||
s ~= "(";
|
||||
if(expr.arguments !is null)
|
||||
s ~= expr.arguments.toString();
|
||||
|
||||
s ~= ") ";
|
||||
s ~= expr.functionBody.toString();
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
template CtList(T...) { alias CtList = T; }
|
||||
|
||||
class BinaryExpression : Expression {
|
||||
|
@ -1899,6 +1983,43 @@ unittest {
|
|||
});
|
||||
}
|
||||
|
||||
unittest {
|
||||
// overloads
|
||||
interpret(q{
|
||||
function foo(int a) { return 10 + a; }
|
||||
function foo(float a) { return 100 + a; }
|
||||
function foo(string a) { return "string " ~ a; }
|
||||
|
||||
assert(foo(4) == 14);
|
||||
assert(foo(4.5) == 104.5);
|
||||
assert(foo("test") == "string test");
|
||||
|
||||
// can redefine specific override
|
||||
function foo(int a) { return a; }
|
||||
assert(foo(4) == 4);
|
||||
// leaving others in place
|
||||
assert(foo(4.5) == 104.5);
|
||||
assert(foo("test") == "string test");
|
||||
});
|
||||
}
|
||||
|
||||
unittest {
|
||||
// catching objects
|
||||
interpret(q{
|
||||
class Foo {}
|
||||
class Bar : Foo {}
|
||||
|
||||
var res = try throw new Bar(); catch(Bar b) { 2 } catch(e) { 1 };
|
||||
assert(res == 2);
|
||||
|
||||
var res = try throw new Foo(); catch(Bar b) { 2 } catch(e) { 1 };
|
||||
assert(res == 1);
|
||||
|
||||
var res = try throw Foo; catch(Foo b) { 2 } catch(e) { 1 };
|
||||
assert(res == 2);
|
||||
});
|
||||
}
|
||||
|
||||
class ForeachExpression : Expression {
|
||||
VariableDeclaration decl;
|
||||
Expression subject;
|
||||
|
@ -2151,10 +2272,39 @@ class ThrowExpression : Expression {
|
|||
}
|
||||
}
|
||||
|
||||
bool isCompatibleType(var v, string specifier, PrototypeObject sc) {
|
||||
var t = toType(specifier, sc);
|
||||
auto score = typeCompatibilityScore(v, t);
|
||||
return score > 0;
|
||||
}
|
||||
|
||||
var toType(string specifier, PrototypeObject sc) {
|
||||
switch(specifier) {
|
||||
case "int", "long": return var(0);
|
||||
case "float", "double": return var(0.0);
|
||||
case "string": return var("");
|
||||
default:
|
||||
auto got = sc._peekMember(specifier, true);
|
||||
if(got)
|
||||
return *got;
|
||||
else
|
||||
return var.init;
|
||||
}
|
||||
}
|
||||
|
||||
var[] toTypes(string[] specifiers, PrototypeObject sc) {
|
||||
var[] arr;
|
||||
foreach(s; specifiers)
|
||||
arr ~= toType(s, sc);
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
class ExceptionBlockExpression : Expression {
|
||||
Expression tryExpression;
|
||||
|
||||
string[] catchVarDecls;
|
||||
string[] catchVarTypeSpecifiers;
|
||||
Expression[] catchExpressions;
|
||||
|
||||
Expression[] finallyExpressions;
|
||||
|
@ -2165,12 +2315,30 @@ class ExceptionBlockExpression : Expression {
|
|||
assert(tryExpression !is null);
|
||||
assert(catchVarDecls.length == catchExpressions.length);
|
||||
|
||||
void caught(var ex) {
|
||||
if(catchExpressions.length)
|
||||
foreach(i, ce; catchExpressions) {
|
||||
if(catchVarTypeSpecifiers[i].length == 0 || isCompatibleType(ex, catchVarTypeSpecifiers[i], sc)) {
|
||||
auto catchScope = new PrototypeObject();
|
||||
catchScope.prototype = sc;
|
||||
catchScope._getMember(catchVarDecls[i], false, false) = ex;
|
||||
|
||||
result = ce.interpret(catchScope);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
result = InterpretResult(ex, sc);
|
||||
}
|
||||
|
||||
if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0))
|
||||
try {
|
||||
result = tryExpression.interpret(sc);
|
||||
} catch(NonScriptCatchableException e) {
|
||||
// the script cannot catch these so it continues up regardless
|
||||
throw e;
|
||||
} catch(ScriptException e) {
|
||||
// FIXME: what about the other information here? idk.
|
||||
caught(e.payload);
|
||||
} catch(Exception e) {
|
||||
var ex = var.emptyObject;
|
||||
ex.type = typeid(e).name;
|
||||
|
@ -2178,16 +2346,7 @@ class ExceptionBlockExpression : Expression {
|
|||
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;
|
||||
catchScope._getMember(catchVarDecls[i], false, false) = ex;
|
||||
|
||||
result = ce.interpret(catchScope);
|
||||
} else
|
||||
result = InterpretResult(ex, sc);
|
||||
caught(ex);
|
||||
} finally {
|
||||
foreach(fe; finallyExpressions)
|
||||
result = fe.interpret(sc);
|
||||
|
@ -2864,12 +3023,14 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool
|
|||
tokens.requireNextToken(ScriptToken.Type.symbol, ")");
|
||||
auto bod = parseExpression(tokens);
|
||||
|
||||
expressions ~= new AssignExpression(
|
||||
expressions ~= new FunctionDeclaration(
|
||||
new DotVarExpression(
|
||||
new VariableExpression("__proto"),
|
||||
new VariableExpression(ident.str),
|
||||
false),
|
||||
new FunctionLiteralExpression(args, bod, staticScopeBacking));
|
||||
ident.str,
|
||||
new FunctionLiteralExpression(args, bod, staticScopeBacking)
|
||||
);
|
||||
} else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber);
|
||||
}
|
||||
|
||||
|
@ -3015,22 +3176,39 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool
|
|||
tokens.popFront();
|
||||
e.tryExpression = parseExpression(tokens, true);
|
||||
|
||||
bool hadSomething = false;
|
||||
bool hadFinally = false;
|
||||
while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) {
|
||||
if(hadSomething)
|
||||
throw new ScriptCompileException("Only one catch block is allowed currently ", tokens.front.scriptFilename, tokens.front.lineNumber);
|
||||
hadSomething = true;
|
||||
if(hadFinally)
|
||||
throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber);
|
||||
tokens.popFront();
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, "(");
|
||||
if(tokens.peekNextToken(ScriptToken.Type.keyword, "var"))
|
||||
tokens.popFront();
|
||||
|
||||
auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
|
||||
e.catchVarDecls ~= ident.str;
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, ")");
|
||||
if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber);
|
||||
auto next = tokens.front;
|
||||
if(next.type == ScriptToken.Type.identifier) {
|
||||
auto type = ident;
|
||||
ident = next;
|
||||
|
||||
e.catchVarTypeSpecifiers ~= type.str;
|
||||
e.catchVarDecls ~= ident.str;
|
||||
|
||||
tokens.popFront();
|
||||
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, ")");
|
||||
} else {
|
||||
e.catchVarTypeSpecifiers ~= null;
|
||||
e.catchVarDecls ~= ident.str;
|
||||
if(next.type != ScriptToken.Type.symbol || next.str != ")")
|
||||
throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber);
|
||||
tokens.popFront();
|
||||
}
|
||||
e.catchExpressions ~= parseExpression(tokens);
|
||||
}
|
||||
while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) {
|
||||
hadSomething = true;
|
||||
hadFinally = true;
|
||||
tokens.popFront();
|
||||
e.finallyExpressions ~= parseExpression(tokens);
|
||||
}
|
||||
|
@ -3080,12 +3258,34 @@ VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStrea
|
|||
if(tokens.empty)
|
||||
throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber);
|
||||
|
||||
string type;
|
||||
|
||||
auto next = tokens.front;
|
||||
tokens.popFront;
|
||||
if(tokens.empty)
|
||||
throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber);
|
||||
auto next2 = tokens.front;
|
||||
|
||||
ScriptToken typeSpecifier;
|
||||
|
||||
/* if there's two identifiers back to back, it is a type specifier. otherwise just a name */
|
||||
|
||||
if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) {
|
||||
// type ident;
|
||||
typeSpecifier = next;
|
||||
next = next2;
|
||||
// get past the type
|
||||
tokens.popFront();
|
||||
} else {
|
||||
// no type, just carry on with the next thing
|
||||
}
|
||||
|
||||
Expression initializer;
|
||||
auto identifier = tokens.front;
|
||||
auto identifier = next;
|
||||
if(identifier.type != ScriptToken.Type.identifier)
|
||||
throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber);
|
||||
|
||||
tokens.popFront();
|
||||
//tokens.popFront();
|
||||
|
||||
tryTermination:
|
||||
if(tokens.empty)
|
||||
|
@ -3104,16 +3304,18 @@ VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStrea
|
|||
tokens.popFront();
|
||||
decl.identifiers ~= identifier.str;
|
||||
decl.initializers ~= initializer;
|
||||
decl.typeSpecifiers ~= typeSpecifier.str;
|
||||
goto anotherVar;
|
||||
} else if(peek.str == termination) {
|
||||
decl.identifiers ~= identifier.str;
|
||||
decl.initializers ~= initializer;
|
||||
decl.typeSpecifiers ~= typeSpecifier.str;
|
||||
//tokens = tokens[1 .. $];
|
||||
// we're done!
|
||||
} else
|
||||
throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
|
||||
throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber);
|
||||
} else
|
||||
throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
|
||||
throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
|
||||
|
||||
return decl;
|
||||
}
|
||||
|
@ -3170,18 +3372,20 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin
|
|||
|
||||
// a ; should NOT be required here btw
|
||||
|
||||
auto e = new VariableDeclaration();
|
||||
e.identifiers ~= ident.str;
|
||||
e.initializers ~= exp;
|
||||
|
||||
exp.isMacro = token.str == "macro";
|
||||
|
||||
auto e = new FunctionDeclaration(null, ident.str, exp);
|
||||
|
||||
return e;
|
||||
|
||||
} else {
|
||||
tokens.pushFront(token); // put it back since everyone expects us to have done that
|
||||
goto case; // handle it like any other expression
|
||||
}
|
||||
|
||||
case "true":
|
||||
case "false":
|
||||
|
||||
case "json!{":
|
||||
case "#{":
|
||||
case "[":
|
||||
|
|
Loading…
Reference in New Issue