package carpet.script.value; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Fluff; import carpet.script.LazyValue; import carpet.script.Tokenizer; import carpet.script.bundled.Module; import carpet.script.exception.BreakStatement; import carpet.script.exception.ContinueStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ReturnStatement; import carpet.script.exception.ThrowStatement; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; public class FunctionValue extends Value implements Fluff.ILazyFunction { private final Expression expression; private final Tokenizer.Token token; private final String name; private final LazyValue body; private Map outerState; private final List args; private final String varArgs; private static long variantCounter = 1; private long variant; private FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List args, String varArgs) { this.expression = expression; this.token = token; this.name = name; this.body = body; this.args = args; this.varArgs = varArgs; this.outerState = null; variant = 0L; } public FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List args, String varArgs, Map outerState) { this.expression = expression; this.token = token; this.name = name; this.body = body; this.args = args; this.varArgs = varArgs; this.outerState = outerState; variant = variantCounter++; } @Override public String getString() { return name; } public Module getModule() {return expression.module;} @Override public String getPrettyString() { List stringArgs= new ArrayList<>(args); if (outerState != null) stringArgs.addAll(outerState.entrySet().stream().map(e -> "outer("+e.getKey()+") = "+e.getValue().evalValue(null).getPrettyString()).collect(Collectors.toList())); return (name.equals("_")?"":name) +"("+String.join(", ",stringArgs)+")"; } public String fullName() {return (name.equals("_")?"":name)+(expression.module == null?"":"["+expression.module.getName()+"]");} @Override public boolean getBoolean() { return true; } @Override protected Value clone() { FunctionValue ret = new FunctionValue(expression, token, name, body, args, varArgs); ret.outerState = this.outerState; ret.variant = this.variant; return ret; } @Override public int hashCode() { return name.hashCode()+(int)variant; } @Override public boolean equals(Object o) { if (o instanceof FunctionValue) return name.equals(((FunctionValue) o).name) && variant == ((FunctionValue) o).variant; return false; } @Override public int compareTo(final Value o) { if (o instanceof FunctionValue) { int nameSame = this.name.compareTo(((FunctionValue) o).name); if (nameSame != 0) return nameSame; return (int) (variant-((FunctionValue) o).variant); } return getString().compareTo(o.getString()); } @Override public double readDoubleNumber() { return getNumParams(); } @Override public String getTypeString() { return "function"; } @Override public Value slice(long from, long to) { throw new InternalExpressionException("Cannot slice a function"); } @Override public int getNumParams() { return args.size(); } @Override public boolean numParamsVaries() { return varArgs != null; } public LazyValue callInContext(Context c, Integer type, List lazyParams) { try { return lazyEval(c, type, expression, token, lazyParams); } catch (ExpressionException exc) { exc.stack.add(this); throw exc; } catch (InternalExpressionException exc) { exc.stack.add(this); throw new ExpressionException(c, expression, token, exc.getMessage(), exc.stack); } catch (ArithmeticException exc) { throw new ExpressionException(c, expression, token, "Your math is wrong, "+exc.getMessage(), Collections.singletonList(this)); } } @Override public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lazyParams) { assertArgsOk(lazyParams, (fixedArgs) ->{ if (fixedArgs) // wrong number of args for fixed args { throw new ExpressionException(c, e, t, "Incorrect number of arguments for function "+name+ ". Should be "+args.size()+", not "+lazyParams.size()+" like "+args ); } else // too few args for varargs { List argList = new ArrayList<>(args); argList.add("... "+varArgs); throw new ExpressionException(c, e, t, "Incorrect number of arguments for function "+name+ ". Should be at least "+args.size()+", not "+lazyParams.size()+" like "+argList ); } }); Context newFrame = c.recreate(); if (outerState != null) outerState.forEach(newFrame::setVariable); for (int i=0; i val); } if (varArgs != null) { List extraParams = new ArrayList<>(); for (int i = args.size(), mx = lazyParams.size(); i < mx; i++) { extraParams.add(lazyParams.get(i).evalValue(c).reboundedTo(null)); // copy by value I guess } Value rest = ListValue.wrap(extraParams).bindTo(varArgs); // didn't we just copied that? newFrame.setVariable(varArgs, (cc, tt) -> rest); } Value retVal; boolean rethrow = false; try { retVal = body.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye } catch (BreakStatement | ContinueStatement exc) { throw new ExpressionException(c, e, t, "'continue' and 'break' can only be called inside loop function bodies"); } catch (ReturnStatement returnStatement) { retVal = returnStatement.retval; } catch (ThrowStatement throwStatement) // might not be really necessary { retVal = throwStatement.retval; rethrow = true; } if (rethrow) { throw new ThrowStatement(retVal); } Value otherRetVal = retVal; return (cc, tt) -> otherRetVal; } public Expression getExpression() { return expression; } public Tokenizer.Token getToken() { return token; } public List getArguments() { return args; } public String getVarArgs() {return varArgs; } @Override public Tag toTag(boolean force) { if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); return StringTag.of(getString()); } public void assertArgsOk(List list, Consumer feedback) { int size = list.size(); if (varArgs == null && args.size() != size) // wrong number of args for fixed args { feedback.accept(true); } else if (varArgs != null && args.size() > size) // too few args for varargs { feedback.accept(false); } } public static List resolveArgs(List lzargs, Context c, Integer t) { List args = new ArrayList<>(lzargs.size()); lzargs.forEach( v -> args.add( v.evalValue(c, t))); return args; } public static List lazify(List args) { List lzargs = new ArrayList<>(args.size()); args.forEach( v -> lzargs.add( (c, t) -> v)); return lzargs; } }