// See GazelleV_LeftArrowScriptParser // TODO: decide whether we allow calling methods/getting fields on // a null reference (just returning null), or whether we throw a // NullPointerException. Currently we just return null. Probably // that's pretty cool. Null propagation as a default, just like in // JavaX. Just more automatic! sclass GazelleV_LeftArrowScript { asclass Base is IHasTokenRangeWithSrc { TokenRangeWithSrc src; public void setTokenRangeWithSrc(TokenRangeWithSrc src) { this.src = src; } public TokenRangeWithSrc tokenRangeWithSrc() { ret src; } RuntimeException rethrowWithSrc(Throwable e) { if (src != null) throw rethrowAndAppendToMessage(e, squareBracketed(str(src))); else throw rethrow(e); } } interface Evaluable { public O get(VarContext ctx default new); } /*interface Cmd { // returns true if a return was issued public bool run(VarContext ctx default new); }*/ static new AtomicLong scriptIDCounter; static long scriptID() { ret incAtomicLong(scriptIDCounter); } sclass Script is Evaluable { long id = scriptID(); // just for printing Map functionDefs; Evaluable[] steps; public O get(VarContext ctx) { O result = null; for (step : steps) { ping(); result = step.get(ctx); // exiting from anything? var exiting = ctx.exitFromScript; if (exiting != null) { printVars ifdef ReturnFromScript_debug("Checking exitFromScript", +ctx, +exiting, script := this); // we're the exit point if (exiting == this) ctx.exitFromScript = null; // exit further break; } } ret result; } S toStringLong() { ret pnlToLines(steps); } toString { ret "Script " + n2(id); } FunctionDef getFunction(S name) { ret mapGet(functionDefs, name); } } // end of Script srecord noeq FunctionDef(S name, LS args, Evaluable body) > Base { public O call(VarContext ctx, O... args) { var ctx2 = new VarContext(ctx); int n = min(l(args), l(this.args)); for i to n: ctx2.put(this.args.get(i), args[i]); print ifdef GazelleV_LeftArrowScript_debug(ctx2 := ctx2.vars); ret body.get(ctx2); } } srecord noeq Assignment(S var, Evaluable expression) > Base is Evaluable { public O get(VarContext ctx) { O o = expression.get(ctx); ctx.set(var, o); ret o; } toString { ret var + " <- " + expression; } } persistable sclass NewObject > Base is Evaluable { Class c; L args; *(Class *c) {} *(Class *c, L *args) {} public O get(VarContext ctx) { ret callConstructor(c, mapToArray(args, arg -> arg.get(ctx))); } toString { ret "new " + formatFunctionCall(className(c), args); } } srecord noeq CallFunction(FunctionDef f, L args) > Base is Evaluable { public O get(VarContext ctx) { //ping(); ret f.call(ctx, mapToArray(args, a -> a.get(ctx)); } toString { ret formatFunctionCall(f.name, args); } } srecord noeq GetVar(S var) > Base is Evaluable { public O get(VarContext ctx) { ret ctx.get(var); } toString { ret var; } } srecord noeq Const(O value) > Base is Evaluable { public O get(VarContext ctx) { ret value; } toString { ret strOrClassName(value); } } srecord noeq GetStaticField(Field field) > Base is Evaluable { public O get(VarContext ctx) ctex { ret field.get(null); } } srecord noeq CallMethodOrGetField(Evaluable target, S name) > Base is Evaluable { public O get(VarContext ctx) { try { O object = target.get(ctx); if (object == null) null; // throw new NullPointerException(); // could optimize more for sure if (canCallWithVarargs(object, name)) ret call(object, name); ret _get(object, name); } catch e { throw rethrowWithSrc(e); } } } srecord CallMethod(Evaluable target, S methodName, L args) > Base is Evaluable { public O get(VarContext ctx) { ret call(target.get(ctx), methodName, mapToArray(args, arg -> arg.get(ctx))); } toString { ret target + "." + formatFunctionCall(methodName, args); } } srecord While(Evaluable condition, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { while (ping() && (Bool) condition.get(ctx)) { body.get(ctx); } // while loops don't return anything null; } } srecord ForEach(Evaluable collection, S var, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { var coll = collection.get(ctx); Iterator iterator; if (coll cast O[]) for (element : coll) { ping(); ctx.set(var, element); body.get(ctx); } else if (coll cast Iterable) { for (element : coll) { ping(); ctx.set(var, element); body.get(ctx); } } else if (coll == null) {} // ok else fail("Not iterable: " + className(coll)); ctx.unset(var); null; } } srecord IfThen(Evaluable condition, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { if ((Bool) condition.get(ctx)) ret body.get(ctx); else null; } } srecord ReturnFromScript(Script script, Evaluable value) > Base is Evaluable { public O get(VarContext ctx) { O result = value.get(ctx); printVars ifdef ReturnFromScript_debug("ReturnFromScript", +result, +ctx, +script); ctx.exitFromScript(script); ret result; } toString { ret formatFunctionCall ReturnFromScript(script, value); } } }