// 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 { // Base = any script element with a reference to its source code replace EXITCHECK with if (ctx.exiting()) null;. 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); } } // Evaluable = a script element that can be evaluated interface Evaluable extends IF0 { public O get(VarContext ctx default new); public default LASValueDescriptor returnType() { null; } public default Evaluable optimize() { this; } } // Base + Evaluable + explicitly stored return type asclass BaseEvaluable > Base is Evaluable { settable LASValueDescriptor returnType; } /*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 > Base 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; result = ctx.returnValue; ctx.returnValue(null); ret result; } // otherwise exit further null; } } 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, S[] args, Evaluable body) > Base { *(S *name, LS args, Evaluable *body) { this.args = toStringArray(args); } 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[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; } } srecord noeq AssignmentToOuterVar(S var, Evaluable expression) > Base is Evaluable { public O get(VarContext ctx) { var parent = ctx.parent(); assertNotNull("No outer variable context", parent); O o = expression.get(ctx); parent.set(var, o); ret o; } toString { ret "outer " + var + " <- " + expression; } } persistable sclass NewObject > Base is Evaluable { Class c; Evaluable[] args; *(Class *c) {} *(Class *c, Evaluable[] *args) {} public O get(VarContext ctx) { ret preciseNuObject(c, mapToArrayOrNull(args, arg -> arg.get(ctx))); } toString { ret "new " + formatFunctionCall(className(c), args); } } srecord noeq CallFunction(FunctionDef f, Evaluable[] args) > Base is Evaluable { public O get(VarContext ctx) { var evaledArgs = mapToArrayOrNull(args, a -> a.get(ctx)); EXITCHECK ret f.call(ctx, evaledArgs); } toString { ret formatFunctionCall(f.name, args); } } srecord noeq GetVar(S var) > BaseEvaluable { 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); } public LASValueDescriptor returnType() { ret new LASValueDescriptor.KnownValue(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); // TODO: better error message when neither field nor method found ret _get(object, name); } catch e { throw rethrowWithSrc(e); } } } sclass GetVarContext > Base is Evaluable { public O get(VarContext ctx) { ret ctx; } } srecord noeq ThrowMethodNotFoundException(CallMethod instruction) > Base is Evaluable { public O get(VarContext ctx) { fail("Method not found: " + instruction); } } srecord noeq ThrowNullPointerException(CallMethod instruction) > Base is Evaluable { public O get(VarContext ctx) { fail("Null pointer exception: " + instruction); } } srecord noeq CallMethod(Evaluable target, S methodName, Evaluable[] args) > Base is Evaluable { public O get(VarContext ctx) { ret /*call*/newPreciseCall(target.get(ctx), methodName, mapToArrayOrNull(args, arg -> arg.get(ctx))); } toString { ret target + "." + formatFunctionCall(methodName, args); } public Evaluable optimize() { var targetType = target.returnType(); if (targetType.knownValue()) { O o = targetType.value(); if (o == null) ret new ThrowNullPointerException(this); Class[] argTypes = new[l(args)]; for i over args: { var type = args[i].returnType(); if (!type.javaClassIsExact()) this; argTypes[i] = type.javaClass(); } // TODO: varargs var method = findMethod_precise_onTypes(o, methodName, argTypes); if (method == null) ret new ThrowMethodNotFoundException(this); ret new DirectMethodCallOnKnownTarget(o instanceof Class ? null : o, method, args); } this; } } srecord noeq LambdaDef(Class intrface, S[] args, Evaluable body) > Base is Evaluable { Method implementedMethod; // !customConstructor *(Class *intrface, S[] *args, Evaluable *body) { implementedMethod = findSingleInterfaceMethodOrFail(intrface); if (implementedMethod.getParameterCount() != l(args)) fail("Bad parameter count for lambda: " + implementedMethod + " vs: " + joinWithComma(args)); } // We have to create the proxy when the lambda definition is // evaluated since we have to put the context in there public O get(VarContext ctx) { ret proxyFromInvocationHandler(intrface, (proxy, method, actualArgs) -> { ping(); if (method.getDeclaringClass() == intrface) { var ctx2 = new VarContext(ctx); var argNames = args; // We already know that the count matches. for i over args: ctx2.put(argNames[i], actualArgs[i]); ret body.get(ctx2); } else ret handleObjectMethodsInProxyInvocationHandler( this, implementedMethod, method, proxy, actualArgs); }); } } abstract srecord noeq CurriedLambdaBase(Class intrface, Evaluable[] curriedArgs) > Base is Evaluable { Method implementedMethod; // !customConstructor *(Class *intrface, Evaluable[] *curriedArgs) { implementedMethod = findSingleInterfaceMethodOrFail(intrface); } public O get(VarContext ctx) { O[] curriedArguments = mapToArrayOrNull(curriedArgs, arg -> arg.get(ctx)); ret proxyFromInvocationHandler(intrface, (proxy, method, actualArgs) -> { // Comparing a Method is more expensive than just // comparing the class (should be enough to distinguish // methods since we are dealing with a single-method class). // tl;dr So far we are getting away with this so all is good ^^ if (method.getDeclaringClass() == intrface) ret forwardCall(ctx, concatMethodArgs(curriedArguments, actualArgs)); else ret handleObjectMethodsInProxyInvocationHandler( this, implementedMethod, method, proxy, actualArgs); }); } abstract O forwardCall(VarContext ctx, O[] args); } sclass CurriedMethodLambda > CurriedLambdaBase { O target; S targetMethod; *(Class intrface, O *target, S *targetMethod, Evaluable[] curriedArgs) { super(intrface, curriedArgs); } O forwardCall(VarContext ctx, O[] args) { ret call(target, targetMethod, args); } } sclass CurriedScriptFunctionLambda > CurriedLambdaBase { FunctionDef f; *(Class intrface, FunctionDef *f, Evaluable[] curriedArgs) { super(intrface, curriedArgs); } O forwardCall(VarContext ctx, O[] args) { ret f.call(ctx, args); } } sclass CurriedConstructorLambda > CurriedLambdaBase { Constructor[] ctors; *(Class intrface, Constructor[] *ctors, Evaluable[] curriedArgs) { super(intrface, curriedArgs); } O forwardCall(VarContext ctx, O[] args) { ret preciseNuObject(ctors, args); } } srecord noeq DirectMethodCallOnKnownTarget(O target, Method method, Evaluable[] args) > Base is Evaluable { public O get(VarContext ctx) { ret invokeMethod(method, target, mapToArrayOrNull(args, arg -> arg.get(ctx))); } toString { ret (target == null ? "" : target + ".") + formatFunctionCall(str(method), args); } public LASValueDescriptor returnType() { ret LASValueDescriptor.fromClass(method.getReturnType()); } } srecord noeq While(Evaluable condition, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { while (!ctx.exiting() && (Bool) condition.get(ctx)) { body.get(ctx); } // while loops don't return anything null; } } abstract srecord noeq ForEachBase(Evaluable collection, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { var coll = collection.get(ctx); Iterator iterator; L out; try { if (coll cast O[]) { out = emptyList(coll.length); for (element : coll) { EXITCHECK processElement(ctx, out, element); } } else if (coll cast Iterable) { out = emptyList(coll); for (element : coll) { EXITCHECK processElement(ctx, out, element); } } else if (coll == null) out = new L; else fail("Not iterable: " + className(coll)); } finally { loopDone(ctx); } ret out; } abstract void processElement(VarContext ctx, L out, O o); abstract void loopDone(VarContext ctx); } persistable sclass ForEach > ForEachBase { S var; *(Evaluable *collection, S *var, Evaluable *body) {} void processElement(VarContext ctx, L out, O o) { ctx.set(var, o); out.add(body.get(ctx)); } void loopDone(VarContext ctx) { ctx.unset(var); } } persistable sclass ForPairs > ForEachBase { S varA, varB; *(Evaluable *collection, Evaluable *body, S *varA, S *varB) {} void processElement(VarContext ctx, L out, O o) { Pair p = cast o; ctx.set(varA, p.a); ctx.set(varB, p.b); out.add(body.get(ctx)); } void loopDone(VarContext ctx) { ctx.unset(varA); ctx.unset(varB); } } srecord noeq ForKeyValue(Evaluable map, Evaluable body, S varA, S varB) > Base is Evaluable { public O get(VarContext ctx) { Map theMap = (Map) map.get(ctx); L out; try { if (theMap != null) { out = emptyList(theMap.size()); for (entry : theMap.entrySet()) { EXITCHECK ctx.set(varA, entry.getKey()); ctx.set(varB, entry.getValue()); out.add(body.get(ctx)); } } else out = new L; } finally { ctx.unset(varA); ctx.unset(varB); } ret out; } } persistable sclass ForIndex { Evaluable collection, body; S varIndex, varElement; *(Evaluable *collection, Evaluable *body, S *varIndex, S *varElement) {} public O get(VarContext ctx) { ret new ForIndex_instance().get(ctx); } } sclass ForIndex_instance > ForEachBase { S varIndex, varElement; int index; *(Evaluable *collection, Evaluable *body, S *varIndex, S *varElement) {} void processElement(VarContext ctx, L out, O o) { ctx.set(varIndex, index++); ctx.set(varElement, o); out.add(body.get(ctx)); } void loopDone(VarContext ctx) { ctx.unset(varIndex); ctx.unset(varElement); } } srecord noeq IfThen(Evaluable condition, Evaluable body, Evaluable elseBranch) > Base is Evaluable { IfThen(Evaluable condition, Evaluable body) { this.condition = condition; this.body = body; } public O get(VarContext ctx) { if ((Bool) condition.get(ctx)) ret body.get(ctx); else if (elseBranch != null) ret elseBranch.get(ctx); else null; } } srecord noeq 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); ctx.returnValue(result); null; } toString { ret formatFunctionCall ReturnFromScript(script, value); } } srecord noeq RepeatN(Evaluable n, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { long count = ((Number) n.get(ctx)).longValue(); repeat count { EXITCHECK body.get(ctx); } null; } } persistable srecord BoolAnd(Evaluable a, Evaluable b) > Base is Evaluable { public O get(VarContext ctx) { if (!((Bool) a.get(ctx))) false; ret b.get(ctx); } } persistable srecord BoolOr(Evaluable a, Evaluable b) > Base is Evaluable { public O get(VarContext ctx) { if (((Bool) a.get(ctx))) true; ret b.get(ctx); } } srecord noeq TempBlock(Evaluable tempExpr, Evaluable body) > Base is Evaluable { public O get(VarContext ctx) { temp (AutoCloseable) tempExpr.get(ctx); ret body.get(ctx); } } } // end of GazelleV_LeftArrowScript