From 156fc7eb213098d24c9804f05184946e8f82e3a5 Mon Sep 17 00:00:00 2001 From: gnembon <41132274+gnembon@users.noreply.github.com> Date: Sat, 22 Mar 2025 15:17:18 +0100 Subject: [PATCH] improved explain, still hidden --- .../carpet/api/settings/SettingsManager.java | 3 +- .../java/carpet/script/CarpetExpression.java | 11 +- .../java/carpet/script/CarpetScriptHost.java | 16 +- .../carpet/script/CarpetScriptServer.java | 28 +-- src/main/java/carpet/script/Expression.java | 202 ++++++++++++++---- .../java/carpet/script/ScriptCommand.java | 71 +++--- src/main/java/carpet/script/ScriptHost.java | 10 +- src/main/java/carpet/script/Token.java | 5 + src/main/java/carpet/script/Tokenizer.java | 10 +- .../carpet/script/utils/AppStoreManager.java | 4 +- 10 files changed, 246 insertions(+), 114 deletions(-) diff --git a/src/main/java/carpet/api/settings/SettingsManager.java b/src/main/java/carpet/api/settings/SettingsManager.java index dc7637e2..2b4f341b 100644 --- a/src/main/java/carpet/api/settings/SettingsManager.java +++ b/src/main/java/carpet/api/settings/SettingsManager.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import carpet.script.Expression; import com.google.common.collect.Sets; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; @@ -331,7 +332,7 @@ public class SettingsManager { { if (RuleHelper.getBooleanValue(rule) || (rule.type() == String.class && !rule.value().equals("false"))) { - CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> CommandHelper.canUseCommand(s, rule.value()), false, false, true, null); + CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> CommandHelper.canUseCommand(s, rule.value()), false, false, true, null, Expression.LoadOverride.DEFAULT); } else { CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true); } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 466e9128..9ca41018 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -20,6 +20,7 @@ import carpet.script.value.Value; import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; +import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nullable; import java.util.List; @@ -93,8 +94,9 @@ public class CarpetExpression return scriptServer.events.handleEvents.getWhileDisabled(() -> this.expr.executeAndEvaluate( context, Vanilla.ScriptServer_scriptOptimizations(scriptServer.server), + host.loadOverrides, Vanilla.ScriptServer_scriptDebugging(scriptServer.server) ? CarpetScriptServer.LOG::info : null - ).getBoolean()); + ).getLeft().getBoolean()); } catch (ExpressionException e) { @@ -110,7 +112,7 @@ public class CarpetExpression } } - public Value scriptRunCommand(ScriptHost host, BlockPos pos) + public Pair scriptRunCommand(ScriptHost host, BlockPos pos) { CarpetScriptServer scriptServer = (CarpetScriptServer) host.scriptServer(); if (scriptServer.stopAll) @@ -137,6 +139,7 @@ public class CarpetExpression return scriptServer.events.handleEvents.getWhileDisabled(() -> this.expr.executeAndEvaluate( context, Vanilla.ScriptServer_scriptOptimizations(scriptServer.server), + host.loadOverrides, Vanilla.ScriptServer_scriptDebugging(scriptServer.server) ? CarpetScriptServer.LOG::info : null )); } @@ -154,7 +157,7 @@ public class CarpetExpression } } - public List explain(ScriptHost host, @Nullable String method, @Nullable String style, BlockPos pos) + public List explain(ScriptHost host, @Nullable String code, @Nullable String method, @Nullable String style, BlockPos pos) { CarpetScriptServer scriptServer = (CarpetScriptServer) host.scriptServer(); try @@ -174,7 +177,7 @@ public class CarpetExpression Value playerValue = new EntityValue(e).bindTo("p"); context.with("p", (cc, tt) -> playerValue); } - return scriptServer.events.handleEvents.getWhileDisabled(() -> this.expr.explain(context, method, style)); + return scriptServer.events.handleEvents.getWhileDisabled(() -> this.expr.explain(context, code, method, style)); } catch (ExpressionException e) { diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index a1091199..c9c0314d 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -78,9 +78,9 @@ public class CarpetScriptHost extends ScriptHost public AppStoreManager.StoreNode storeSource; boolean hasCommand; - private CarpetScriptHost(CarpetScriptServer server, @Nullable Module code, boolean perUser, ScriptHost parent, Map config, Map argTypes, Predicate commandValidator, boolean isRuleApp) + private CarpetScriptHost(CarpetScriptServer server, @Nullable Module code, boolean perUser, ScriptHost parent, Map config, Map argTypes, Predicate commandValidator, boolean isRuleApp, Expression.LoadOverride override) { - super(code, server, perUser, parent); + super(code, server, perUser, parent, override); this.saveTimeout = 0; persistenceRequired = true; if (parent == null && code != null) // app, not a global host @@ -99,9 +99,9 @@ public class CarpetScriptHost extends ScriptHost storeSource = null; } - public static CarpetScriptHost create(CarpetScriptServer scriptServer, @Nullable Module module, boolean perPlayer, CommandSourceStack source, Predicate commandValidator, boolean isRuleApp, AppStoreManager.StoreNode storeSource) + public static CarpetScriptHost create(CarpetScriptServer scriptServer, @Nullable Module module, boolean perPlayer, CommandSourceStack source, Predicate commandValidator, boolean isRuleApp, AppStoreManager.StoreNode storeSource, Expression.LoadOverride override) { - CarpetScriptHost host = new CarpetScriptHost(scriptServer, module, perPlayer, null, Collections.emptyMap(), new HashMap<>(), commandValidator, isRuleApp); + CarpetScriptHost host = new CarpetScriptHost(scriptServer, module, perPlayer, null, Collections.emptyMap(), new HashMap<>(), commandValidator, isRuleApp, override); // parse code and convert to expression if (module != null) { @@ -111,7 +111,7 @@ public class CarpetScriptHost extends ScriptHost CarpetExpression ex = new CarpetExpression(host.main, module.code(), source, new BlockPos(0, 0, 0)); ex.getExpr().asATextSource(); host.storeSource = storeSource; - ex.scriptRunCommand(host, BlockPos.containing(source.getPosition())); + host.root = ex.scriptRunCommand(host, BlockPos.containing(source.getPosition())).getRight(); } catch (CarpetExpressionException e) { @@ -132,6 +132,10 @@ public class CarpetScriptHost extends ScriptHost host.storeSource = null; } } + else + { + host.root = Expression.ExpressionNode.ofConstant(Value.NULL, new Token()); + } return host; } @@ -253,7 +257,7 @@ public class CarpetScriptHost extends ScriptHost @Override protected ScriptHost duplicate() { - return new CarpetScriptHost(scriptServer(), main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp); + return new CarpetScriptHost(scriptServer(), main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp, loadOverrides); } @Override diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 5345ceae..6b79c557 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -121,7 +121,7 @@ public class CarpetScriptServer extends ScriptServer tickStart = 0L; stopAll = false; holyMoly = server.getCommands().getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName).collect(Collectors.toSet()); - globalHost = CarpetScriptHost.create(this, null, false, null, p -> true, false, null); + globalHost = CarpetScriptHost.create(this, null, false, null, p -> true, false, null, Expression.LoadOverride.DEFAULT); } public void initializeForWorld() @@ -130,7 +130,7 @@ public class CarpetScriptServer extends ScriptServer { for (String moduleName : listAvailableModules(false)) { - addScriptHost(server.createCommandSourceStack(), moduleName, null, true, true, false, null); + addScriptHost(server.createCommandSourceStack(), moduleName, null, true, true, false, null, Expression.LoadOverride.DEFAULT); } } CarpetEventServer.Event.START.onTick(server); @@ -232,8 +232,8 @@ public class CarpetScriptServer extends ScriptServer return name == null ? globalHost : modules.get(name); } - public boolean addScriptHost(CommandSourceStack source, String name, @Nullable Predicate commandValidator, - boolean perPlayer, boolean autoload, boolean isRuleApp, AppStoreManager.StoreNode installer) + public int addScriptHost(CommandSourceStack source, String name, @Nullable Predicate commandValidator, + boolean perPlayer, boolean autoload, boolean isRuleApp, AppStoreManager.StoreNode installer, Expression.LoadOverride override) { Runnable token = Carpet.startProfilerSection("Scarpet load"); if (commandValidator == null) @@ -247,7 +247,7 @@ public class CarpetScriptServer extends ScriptServer { if (isRuleApp) { - return false; + return 0; } removeScriptHost(source, name, false, isRuleApp); reload = true; @@ -256,17 +256,17 @@ public class CarpetScriptServer extends ScriptServer if (module == null) { Carpet.Messenger_message(source, "r Failed to add " + name + " app: App not found"); - return false; + return 0; } CarpetScriptHost newHost; try { - newHost = CarpetScriptHost.create(this, module, perPlayer, source, commandValidator, isRuleApp, installer); + newHost = CarpetScriptHost.create(this, module, perPlayer, source, commandValidator, isRuleApp, installer, override); } catch (LoadException e) { Carpet.Messenger_message(source, "r Failed to add " + name + " app" + (e.getMessage() == null ? "" : ": " + e.getMessage())); - return false; + return 0; } modules.put(name, newHost); @@ -278,7 +278,7 @@ public class CarpetScriptServer extends ScriptServer if (autoload && !newHost.persistenceRequired) { removeScriptHost(source, name, false, false); - return false; + return 0; } String action = (installer != null) ? (reload ? "reinstalled" : "installed") : (reload ? "reloaded" : "loaded"); @@ -292,7 +292,7 @@ public class CarpetScriptServer extends ScriptServer if (isCommandAdded == null) // error should be dispatched { removeScriptHost(source, name, false, isRuleApp); - return false; + return 0; } else if (isCommandAdded) { @@ -330,7 +330,7 @@ public class CarpetScriptServer extends ScriptServer token.run(); long end = System.nanoTime(); LOG.info("App " + name + " loaded in " + (end - start) / 1000000 + " ms"); - return true; + return 1; } public boolean isInvalidCommandRoot(String appName) @@ -458,11 +458,11 @@ public class CarpetScriptServer extends ScriptServer } private record TransferData(boolean perUser, Predicate commandValidator, - boolean isRuleApp) + boolean isRuleApp, Expression.LoadOverride override) { private TransferData(CarpetScriptHost host) { - this(host.perUser, host.commandValidator, host.isRuleApp); + this(host.perUser, host.commandValidator, host.isRuleApp, host.loadOverrides); } } @@ -473,7 +473,7 @@ public class CarpetScriptServer extends ScriptServer apps.keySet().forEach(s -> removeScriptHost(server.createCommandSourceStack(), s, false, false)); CarpetEventServer.Event.clearAllBuiltinEvents(); init(); - apps.forEach((s, data) -> addScriptHost(server.createCommandSourceStack(), s, data.commandValidator, data.perUser, false, data.isRuleApp, null)); + apps.forEach((s, data) -> addScriptHost(server.createCommandSourceStack(), s, data.commandValidator, data.perUser, false, data.isRuleApp, null, data.override)); } public void reAddCommands() diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 7150e02c..3105d295 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -36,6 +36,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nullable; import java.math.BigInteger; @@ -95,6 +96,9 @@ public class Expression @Nullable private LazyValue ast = null; + @Nullable + private ExpressionNode root = null; + /** * script specific operatos and built-in functions */ @@ -1070,7 +1074,7 @@ public class Expression */ public Expression(String expression) { - this.expression = expression.stripTrailing().replaceAll("\\r\\n?", "\n").replaceAll("\\t", " "); + this.expression = stripExpression(expression); Operators.apply(this); ControlFlow.apply(this); Functions.apply(this); @@ -1084,6 +1088,11 @@ public class Expression } } + private String stripExpression(String expression) + { + return expression.stripTrailing().replaceAll("\\r\\n?", "\n").replaceAll("\\t", " "); + } + private List shuntingYard(Context c, List tokens) { @@ -1238,13 +1247,29 @@ public class Expression } } - public Value executeAndEvaluate(Context c, boolean optimize, @Nullable Consumer logger) + public enum LoadOverride { + DEFAULT("clean"), CANONICAL("canonical"), OPTIMIZED("optimized"), FUNCTIONAL("functional"), FUNCTIONAL_OPTIMIZED("functional_optimized"); + public String equivalent; + LoadOverride(String equivalent) { + this.equivalent = equivalent; + } + } + + public Pair executeAndEvaluate(Context c, boolean optimize, LoadOverride override, @Nullable Consumer logger) { if (ast == null) { - ast = getAST(c, optimize, logger); + boolean functions = false; + if (override != LoadOverride.DEFAULT) { + optimize = override == LoadOverride.OPTIMIZED || override == LoadOverride.FUNCTIONAL_OPTIMIZED; + functions = override == LoadOverride.FUNCTIONAL || override == LoadOverride.FUNCTIONAL_OPTIMIZED; + } + + Pair ret = getAST(c, optimize, functions, logger); + ast = ret.getRight(); + root = ret.getLeft(); } - return evaluatePartial(() -> ast, c, Context.Type.NONE); + return Pair.of(evaluatePartial(() -> ast, c, Context.Type.NONE), root); } public Value evaluatePartial(Supplier exprProvider, Context c, Context.Type expectedType) @@ -1301,30 +1326,30 @@ public class Expression return new ExpressionNode(new LazyValue.Constant(val), Collections.emptyList(), token); } - public List tokensRecursive(Expression expression) { + public List tokensRecursive(Expression expression, List reference, Map, List> referencePointers){ List tokens = new ArrayList<>(); switch (token.type) { case FUNCTION -> { tokens.add(token); - tokens.add(token.morphedInto(Token.TokenType.OPEN_PAREN, "(")); + tokens.add(findToken(token, Token.TokenType.OPEN_PAREN, "(", reference, referencePointers)); if (!args.isEmpty()) { for (ExpressionNode arg : args) { - List argTokens = arg.tokensRecursive(expression); + List argTokens = arg.tokensRecursive(expression, reference, referencePointers); tokens.addAll(argTokens); - tokens.add(argTokens.getLast().morphedInto(Token.TokenType.COMMA, ",").disguiseAs(", ", null)); + tokens.add(findToken(argTokens.getLast(), Token.TokenType.COMMA, ",", reference, referencePointers).disguiseAs(", ", null)); } tokens.removeLast(); } - tokens.add(tokens.getLast().morphedInto(Token.TokenType.CLOSE_PAREN, ")")); + tokens.add(findToken(tokens.getLast(), Token.TokenType.CLOSE_PAREN, ")", reference, referencePointers)); } case OPERATOR -> { - tokens.addAll(createOperatorArgumentTokens(expression, args.get(0))); - tokens.add(token.disguiseAs(" "+token.surface+" ", null)); - tokens.addAll(createOperatorArgumentTokens(expression, args.get(1))); + tokens.addAll(createOperatorArgumentTokens(expression, args.get(0), reference, referencePointers)); + tokens.add(token.disguiseAs(token.surface.equals(";") ? (token.surface+" ") : (" "+token.surface+" "), null)); + tokens.addAll(createOperatorArgumentTokens(expression, args.get(1), reference, referencePointers)); } case UNARY_OPERATOR -> { tokens.add(token.disguiseAs(" "+StringUtils.chop(token.surface),null)); - tokens.addAll(createOperatorArgumentTokens(expression, args.get(0))); + tokens.addAll(createOperatorArgumentTokens(expression, args.get(0), reference, referencePointers)); } case VARIABLE, LITERAL, HEX_LITERAL, CONSTANT -> tokens.add(token); case STRINGPARAM -> tokens.add(token.disguiseAs("'"+token.surface+"'", null)); @@ -1336,7 +1361,29 @@ public class Expression return tokens; } - public List createOperatorArgumentTokens(Expression expression, ExpressionNode operand) { + public Token findToken(Token previous, Token.TokenType type, String expectedSurface, List reference, Map, List> referencePointers) + { + List indices = referencePointers.get(Pair.of(previous.lineno, previous.linepos)); + if (indices == null) + { + return previous.morphedInto(type, expectedSurface); + } + for (int index : indices) + { + if (index + 1 == reference.size()) + { + continue; + } + Token token = reference.get(index + 1); + if (token.type == type) + { + return token.morphedInto(type, expectedSurface); + } + } + return previous.morphedInto(type, expectedSurface); + } + + public List createOperatorArgumentTokens(Expression expression, ExpressionNode operand, List reference, Map, List> referencePointers) { boolean needsBrackets = false; var operators = expression.operators; if (operators.containsKey(operand.token.surface) && operators.containsKey(token.surface)) { @@ -1344,15 +1391,15 @@ public class Expression needsBrackets = true; } } - List argumentTokens = operand.tokensRecursive(expression); + List argumentTokens = operand.tokensRecursive(expression, reference, referencePointers); if (!needsBrackets) { return argumentTokens; } List bracketed = new ArrayList<>(); - bracketed.add(argumentTokens.getFirst().morphedInto(Token.TokenType.OPEN_PAREN, "(")); + bracketed.add(findToken(argumentTokens.getFirst(), Token.TokenType.OPEN_PAREN, "(", reference, referencePointers)); bracketed.get(0).swapPlace(argumentTokens.getFirst()); bracketed.addAll(argumentTokens); - bracketed.add(argumentTokens.getLast().morphedInto(Token.TokenType.CLOSE_PAREN, ")")); + bracketed.add(findToken(argumentTokens.getLast(), Token.TokenType.CLOSE_PAREN, ")", reference, referencePointers)); return bracketed; } } @@ -1367,24 +1414,32 @@ public class Expression case UNARY_OPERATOR -> { ExpressionNode node = nodeStack.pop(); LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, node.op, null).evalValue(c, t); - nodeStack.push(new ExpressionNode(result, Collections.singletonList(node), token)); + ExpressionNode newNode = new ExpressionNode(result, Collections.singletonList(node), token); + token.node = newNode; + nodeStack.push(newNode); } case OPERATOR -> { ExpressionNode v1 = nodeStack.pop(); ExpressionNode v2 = nodeStack.pop(); LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, v2.op, v1.op).evalValue(c, t); - nodeStack.push(new ExpressionNode(result, List.of(v2, v1), token)); + ExpressionNode newNode = new ExpressionNode(result, List.of(v2, v1), token); + token.node = newNode; + nodeStack.push(newNode); } case VARIABLE -> { Value constant = getConstantFor(token.surface); if (constant != null) { token.morph(Token.TokenType.CONSTANT, token.surface); - nodeStack.push(new ExpressionNode(LazyValue.ofConstant(constant), Collections.emptyList(), token)); + ExpressionNode newNode = new ExpressionNode(LazyValue.ofConstant(constant), Collections.emptyList(), token); + token.node = newNode; + nodeStack.push(newNode); } else { - nodeStack.push(new ExpressionNode(((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t)), Collections.emptyList(), token)); + ExpressionNode newNode = new ExpressionNode(((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t)), Collections.emptyList(), token); + token.node = newNode; + nodeStack.push(newNode); } } case FUNCTION -> { @@ -1419,12 +1474,17 @@ public class Expression nodeStack.pop(); } List params = p.stream().map(n -> n.op).collect(Collectors.toList()); - nodeStack.push(new ExpressionNode( + ExpressionNode newNode = new ExpressionNode( (c, t) -> f.lazyEval(c, t, this, token, params).evalValue(c, t), p, token - )); + ); + token.node = newNode; + nodeStack.push(newNode); + } + case OPEN_PAREN -> { + token.node = ExpressionNode.PARAMS_START; + nodeStack.push(ExpressionNode.PARAMS_START); } - case OPEN_PAREN -> nodeStack.push(ExpressionNode.PARAMS_START); case LITERAL -> { Value number; try @@ -1436,11 +1496,15 @@ public class Expression throw new ExpressionException(context, this, token, "Not a number"); } //token.morph(Token.TokenType.CONSTANT, token.surface); - nodeStack.push(ExpressionNode.ofConstant(number, token)); + ExpressionNode newNode = ExpressionNode.ofConstant(number, token); + token.node = newNode; + nodeStack.push(newNode); } case STRINGPARAM -> { //token.morph(Token.TokenType.CONSTANT, token.surface); - nodeStack.push(ExpressionNode.ofConstant(new StringValue(token.surface), token)); + ExpressionNode newNode = ExpressionNode.ofConstant(new StringValue(token.surface), token); + token.node = newNode; + nodeStack.push(newNode); } case HEX_LITERAL -> { Value hexNumber; @@ -1453,7 +1517,9 @@ public class Expression throw new ExpressionException(context, this, token, "Not a number"); } //token.morph(Token.TokenType.CONSTANT, token.surface); - nodeStack.push(ExpressionNode.ofConstant(hexNumber, token)); + ExpressionNode newNode = ExpressionNode.ofConstant(hexNumber, token); + token.node = newNode; + nodeStack.push(newNode); } default -> throw new ExpressionException(context, this, token, "Unexpected token '" + token.surface + "'"); } @@ -1461,7 +1527,7 @@ public class Expression return nodeStack.pop(); } - private LazyValue getAST(Context context, boolean optimize, @Nullable Consumer logger) + private Pair getAST(Context context, boolean optimize, boolean functional, @Nullable Consumer logger) { Tokenizer tokenizer = new Tokenizer(context, this, expression, allowComments, allowNewlineSubstitutions); // stripping lousy but acceptable semicolons @@ -1470,19 +1536,22 @@ public class Expression List rpn = shuntingYard(context, cleanedTokens); validate(context, rpn); ExpressionNode root = RPNToParseTree(rpn, context); - if (!optimize) + if (!optimize && !functional) { - return root.op; + return Pair.of(root, root.op); } Context optimizeOnlyContext = new Context.ContextForErrorReporting(context); // flipping to full functional representation makes it little underperforming, might be related // to the fact that operators are running from a bigger pool or function execution is slower - optimizeTree(root, optimizeOnlyContext, logger, false); - return extractOp(optimizeOnlyContext, root, Context.Type.NONE); + optimizeTree(root, optimizeOnlyContext, logger, optimize, functional); + if (!optimize) { + return Pair.of(root, root.op); + } + return Pair.of(root, extractOp(optimizeOnlyContext, root, Context.Type.NONE)); } - private void optimizeTree(ExpressionNode root, Context optimizeOnlyContext, @Nullable Consumer logger, boolean toFunctional) { + private void optimizeTree(ExpressionNode root, Context optimizeOnlyContext, @Nullable Consumer logger, boolean optimize, boolean toFunctional) { if (logger != null) { logger.accept("Input code size for " + getModuleName() + ": " + treeSize(root) + " nodes, " + treeDepth(root) + " deep"); @@ -1503,7 +1572,7 @@ public class Expression prevTreeSize = treeSize(root); prevTreeDepth = treeDepth(root); } - boolean optimized = compactTree(root, Context.Type.NONE, 0, logger, toFunctional); + boolean optimized = compactTree(root, Context.Type.NONE, 0, logger, optimize, toFunctional); if (!optimized) { break; @@ -1514,7 +1583,7 @@ public class Expression logger.accept("Compacted from " + prevTreeSize + " nodes, " + prevTreeDepth + " code depth to " + treeSize(root) + " nodes, " + treeDepth(root) + " code depth"); } } - while (true) + while (optimize) { if (logger != null) { @@ -1535,10 +1604,48 @@ public class Expression } } - public List explain(Context context, @Nullable String method, @Nullable String style) + public List explain(Context context, @Nullable String code, @Nullable String method, @Nullable String style) { + if (code == null) + { + ExpressionNode node; + if (method != null) + { + FunctionValue function = context.host.getFunction(method); + if (function == null) { + throw new ExpressionException(context, this, "Unknown function " + method); + } + node = function.getToken().node; + if (node == null) { + throw new ExpressionException(context, this, "Function " + method + " is not a compiled function"); + } + } else + { + node = context.host.root; + } + if (node == null || node.token.type == null) { + throw new ExpressionException(context, this, "No code to explain"); + } + + // grab source reference + Tokenizer tokenizer = new Tokenizer(context, this, context.host.main == null ? "" : stripExpression(context.host.main.code()) , true, true); + List input = tokenizer.parseTokens(); + List cleanedTokens = Tokenizer.postProcess(input); + + Map, List> tokenPointers = new HashMap<>(); + for (int i = 0; i < cleanedTokens.size(); i++) { + Token token = cleanedTokens.get(i); + tokenPointers.computeIfAbsent(Pair.of(token.lineno, token.linepos), k -> new ArrayList<>()).add(i); + } + + return node.tokensRecursive(this, cleanedTokens, tokenPointers); + } + if (style == null) { + style = context.host.loadOverrides.equivalent; + } + //todo convert to explain - Tokenizer tokenizer = new Tokenizer(context, this, expression, true, true); + Tokenizer tokenizer = new Tokenizer(context, this, stripExpression(code), true, true); // stripping lousy but acceptable semicolons List input = tokenizer.parseTokens(); if (style.equalsIgnoreCase("raw")) { @@ -1552,15 +1659,22 @@ public class Expression List rpn = shuntingYard(context, cleanedTokens); validate(context, rpn); ExpressionNode root = RPNToParseTree(rpn, context); - List parsedTokens = root.tokensRecursive(this); + + Map, List> tokenPointers = new HashMap<>(); + for (int i = 0; i < cleanedTokens.size(); i++) { + Token token = cleanedTokens.get(i); + tokenPointers.computeIfAbsent(Pair.of(token.lineno, token.linepos), k -> new ArrayList<>()).add(i); + } + + List parsedTokens = root.tokensRecursive(this, cleanedTokens, tokenPointers); if (style.equalsIgnoreCase("canonical")) { return parsedTokens; } Context optimizeOnlyContext = new Context.ContextForErrorReporting(context); // pure functional - optimizeTree(root, optimizeOnlyContext, null, style.equalsIgnoreCase("functional")); - List compileTimeOptimized = root.tokensRecursive(this); + optimizeTree(root, optimizeOnlyContext, null, style.contains("optimized"), style.contains("functional")); + List compileTimeOptimized = root.tokensRecursive(this, cleanedTokens, tokenPointers); return compileTimeOptimized; } @@ -1576,7 +1690,7 @@ public class Expression } - private boolean compactTree(ExpressionNode node, Context.Type expectedType, int indent, @Nullable Consumer logger, boolean toFunctional) + private boolean compactTree(ExpressionNode node, Context.Type expectedType, int indent, @Nullable Consumer logger, boolean optimize, boolean toFunctional) { // ctx is just to report errors, not values evaluation boolean optimized = false; @@ -1596,13 +1710,13 @@ public class Expression Context.Type requestedType = operation.staticType(expectedType); for (ExpressionNode arg : node.args) { - if (compactTree(arg, requestedType, indent + 1, logger, toFunctional)) + if (compactTree(arg, requestedType, indent + 1, logger, optimize, toFunctional)) { optimized = true; } } - if (expectedType != Context.Type.MAPDEF && symbol.equals("->") && node.args.size() == 2) + if (optimize && expectedType != Context.Type.MAPDEF && (symbol.equals("->") || symbol.equals("define") ) && node.args.size() == 2) { String rop = node.args.get(1).token.surface; ExpressionNode returnNode = null; @@ -1648,7 +1762,7 @@ public class Expression { String operator = pair.getKey(); String function = pair.getValue(); - if ((symbol.equals(operator) || symbol.equals(function)) && !node.args.isEmpty()) + if (optimize && (symbol.equals(operator) || symbol.equals(function)) && !node.args.isEmpty()) { boolean leftOptimizable = operators.get(operator).isLeftAssoc(); ExpressionNode optimizedChild = node.args.get(leftOptimizable ? 0 : (node.args.size() - 1)); diff --git a/src/main/java/carpet/script/ScriptCommand.java b/src/main/java/carpet/script/ScriptCommand.java index 0b0a84ee..202a6bab 100644 --- a/src/main/java/carpet/script/ScriptCommand.java +++ b/src/main/java/carpet/script/ScriptCommand.java @@ -294,18 +294,18 @@ public class ScriptCommand LiteralArgumentBuilder a = literal("load").requires(Vanilla::ServerPlayer_canScriptACE). then(argument("app", StringArgumentType.word()). suggests((cc, bb) -> suggest(ss(cc).listAvailableModules(true), bb)). - executes((cc) -> - { - boolean success = ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null); - return success ? 1 : 0; - }). + executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null, Expression.LoadOverride.DEFAULT)). + //then(literal("canonical").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null, Expression.LoadOverride.CANONICAL))). + //then(literal("optimized").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null, Expression.LoadOverride.OPTIMIZED))). + //then(literal("functional").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null, Expression.LoadOverride.FUNCTIONAL))). + //then(literal("functional_optimized").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, true, false, false, null, Expression.LoadOverride.FUNCTIONAL_OPTIMIZED))). then(literal("global"). - executes((cc) -> - { - boolean success = ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null); - return success ? 1 : 0; - } - ) + executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null, Expression.LoadOverride.DEFAULT)) + //. + //then(literal("canonical").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null, Expression.LoadOverride.CANONICAL))). + //then(literal("optimized").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null, Expression.LoadOverride.OPTIMIZED))). + //then(literal("functional").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null, Expression.LoadOverride.FUNCTIONAL))). + //then(literal("functional_optimized").executes((cc) -> ss(cc).addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), null, false, false, false, null, Expression.LoadOverride.FUNCTIONAL_OPTIMIZED))) ) ); LiteralArgumentBuilder f = literal("unload").requires(Vanilla::ServerPlayer_canScriptACE). @@ -366,25 +366,24 @@ public class ScriptCommand })); LiteralArgumentBuilder x = literal("explain").requires(Vanilla::ServerPlayer_canScriptACE). - then(argument("style", StringArgumentType.word()).suggests((cc, bb) -> suggest(List.of("raw", "clean", "functional", "canonical", "optimized"), bb)). - executes((cc) -> explain( - cc, - null, null, StringArgumentType.getString(cc, "style"))). - then(literal("run").then(argument("expr", StringArgumentType.greedyString()).suggests(ScriptCommand::suggestCode). - executes((cc) -> explain( - cc, - StringArgumentType.getString(cc, "expr"), null, StringArgumentType.getString(cc, "style"))))). + executes((cc) -> explain(cc, null, null, null)). + then(literal("expression"). + then(argument("style", StringArgumentType.word()).suggests((cc, bb) -> suggest(List.of("raw", "clean", "functional", "canonical", "optimized", "functional_optimized"), bb)). + then(argument("expr", StringArgumentType.greedyString()).suggests(ScriptCommand::suggestCode). + executes((cc) -> explain(cc, StringArgumentType.getString(cc, "expr"), null, StringArgumentType.getString(cc, "style"))) + ) + ) + ). then(literal("invoke"). then(argument("call", StringArgumentType.word()).suggests((cc, bb) -> suggest(suggestFunctionCalls(cc), bb)). - executes((cc) -> explain( - cc, - null, - StringArgumentType.getString(cc, "call"), - StringArgumentType.getString(cc, "style") - )) + executes((cc) -> explain(cc, null, StringArgumentType.getString(cc, "call"), null)) ) - ) - ); + ). + then(literal("source"). + then(argument("style", StringArgumentType.word()).suggests((cc, bb) -> suggest(List.of("raw", "clean", "functional", "canonical", "optimized", "functional_optimized"), bb)). + executes((cc) -> explain(cc, null, null, StringArgumentType.getString(cc, "style"))) + ) + ); dispatcher.register(literal("script"). requires(Vanilla::ServerPlayer_canScriptGeneral). @@ -559,17 +558,19 @@ public class ScriptCommand CarpetScriptHost host = getHost(context); return handleCall(source, host, () -> { CarpetExpression ex = new CarpetExpression(host.main, expr, source, new BlockPos(0, 0, 0)); - return ex.scriptRunCommand(host, BlockPos.containing(source.getPosition())); + return ex.scriptRunCommand(host, BlockPos.containing(source.getPosition())).getLeft(); }); } - private static int explain(CommandContext context, @Nullable String expr, @Nullable String method, String style) throws CommandSyntaxException + private static int explain(CommandContext context, @Nullable String expr, @Nullable String method, @Nullable String style) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); CarpetScriptHost host = getHost(context); return handleCall(source, host, () -> { - CarpetExpression ex = new CarpetExpression(host.main, expr == null ? ( host.main == null ? "" : host.main.code() ) : expr, source, BlockPos.ZERO); - List results = ex.explain(host, method, style, BlockPos.containing(source.getPosition())); + // expression is irrelevant as we pass approprirate strings for parsing directly + // we only really need access to operators and functions? + CarpetExpression ex = new CarpetExpression(host.main, "", source, BlockPos.ZERO); + List results = ex.explain(host, expr != null ? expr : (style != null? (host.main != null ? host.main.code() : "") :null), method, style, BlockPos.containing(source.getPosition())); prettyPrintTokens(source, results); return NumericValue.of(results.size()); }); @@ -584,6 +585,7 @@ public class ScriptCommand } List lines = new ArrayList<>(indents.keySet()); Collections.sort(lines); + int lineMax = String.valueOf(lines.getLast()).length(); Deque indentsStack = new ArrayDeque<>(lines); List elements = new ArrayList<>(); @@ -609,7 +611,8 @@ public class ScriptCommand } int lastPos = indents.getOrDefault(token.lineno, 0); - elements.add(Carpet.Messenger_compose("w " + " ".repeat(lastPos))); + elements.add(Carpet.Messenger_compose("fi " + " ".repeat(lineMax - String.valueOf(token.lineno).length()) + token.lineno)); + elements.add(Carpet.Messenger_compose("g | " + " ".repeat(lastPos))); } elements.add(tokenToComponent(token)); } @@ -625,7 +628,7 @@ public class ScriptCommand } if (token.comment.isEmpty()) { - return Carpet.Messenger_compose(styleForToken(token) + " " + surface); + return Carpet.Messenger_compose(styleForToken(token) + " " + surface);//, "^gi " + String.format("%s: line %s, pos %s", token.surface, token.lineno, token.linepos)); } return Carpet.Messenger_compose(styleForToken(token)+"u " + surface, "^gi " + token.comment); } @@ -642,7 +645,7 @@ public class ScriptCommand case Token.TokenType.VARIABLE -> "y"; case Token.TokenType.STRINGPARAM -> "c"; default -> // marker - "f"; + "r"; }; } diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index d067d2ab..4dd06d5f 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -37,6 +37,9 @@ public abstract class ScriptHost private final ScriptServer scriptServer; protected boolean inTermination = false; public boolean strict; + public Expression.LoadOverride loadOverrides; + @Nullable + public Expression.ExpressionNode root; private final Set deprecations = new HashSet<>(); @@ -143,13 +146,14 @@ public abstract class ScriptHost public ErrorSnooper errorSnooper = null; - protected ScriptHost(@Nullable Module code, ScriptServer scriptServer, boolean perUser, ScriptHost parent) + protected ScriptHost(@Nullable Module code, ScriptServer scriptServer, boolean perUser, ScriptHost parent, Expression.LoadOverride loadOverrides) { this.parent = parent; this.main = code; this.perUser = perUser; this.user = null; this.strict = false; + this.loadOverrides = loadOverrides; this.scriptServer = scriptServer; ModuleData moduleData = new ModuleData(code); initializeModuleGlobals(moduleData); @@ -231,6 +235,7 @@ public abstract class ScriptHost protected abstract void runModuleCode(Context c, Module module); // this should be shell out in the executor + @Nullable public FunctionValue getFunction(String name) { return getFunction(main, name); @@ -253,6 +258,7 @@ public abstract class ScriptHost return ret; } + @Nullable private FunctionValue getFunction(Module module, String name) { ModuleData local = getModuleData(module); @@ -286,6 +292,7 @@ public abstract class ScriptHost return target.globalFunctions.get(name); } + @Nullable private ModuleData findModuleDataFromFunctionImports(String name, ModuleData source, int ttl) { if (ttl > 64) @@ -460,6 +467,7 @@ public abstract class ScriptHost this.moduleData.forEach((key, value) -> host.moduleData.put(key, new ModuleData(key, value))); // fixing imports host.moduleData.forEach((module, data) -> data.setImportsBasedOn(host, this.moduleData.get(data.parent))); + host.root = this.root; } public synchronized void handleExpressionException(String msg, ExpressionException exc) diff --git a/src/main/java/carpet/script/Token.java b/src/main/java/carpet/script/Token.java index adce7e0e..c0fa53e8 100644 --- a/src/main/java/carpet/script/Token.java +++ b/src/main/java/carpet/script/Token.java @@ -35,6 +35,10 @@ public class Token implements Comparable { public int ordinal = 0; public int linepos; public int lineno; + + @Nullable + public Expression.ExpressionNode node = null; + public static final Token NONE = new Token(); public Token morphedInto(TokenType newType, String newSurface) { @@ -44,6 +48,7 @@ public class Token implements Comparable { created.pos = pos; created.linepos = linepos; created.lineno = lineno; + created.node = node; created.ordinal = ordinal + 1; return created; } diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java index abd3e4fa..75fd2646 100644 --- a/src/main/java/carpet/script/Tokenizer.java +++ b/src/main/java/carpet/script/Tokenizer.java @@ -388,14 +388,8 @@ public class Tokenizer else { token.type = Token.TokenType.OPERATOR; - if (token.surface.equals(";")) - { - token.disguiseAs(token.surface + " ", null); - } - else - { - token.disguiseAs(" " + token.surface + " ", null); - } + token.disguiseAs(token.surface.equals(";") ? (token.surface + " ") : (" " + token.surface + " "), null); + } } if (expression != null && context != null && previousToken != null && diff --git a/src/main/java/carpet/script/utils/AppStoreManager.java b/src/main/java/carpet/script/utils/AppStoreManager.java index 963cd549..f7536faa 100644 --- a/src/main/java/carpet/script/utils/AppStoreManager.java +++ b/src/main/java/carpet/script/utils/AppStoreManager.java @@ -2,6 +2,7 @@ package carpet.script.utils; import carpet.script.CarpetScriptHost; import carpet.script.CarpetScriptServer; +import carpet.script.Expression; import carpet.script.exception.InternalExpressionException; import carpet.script.external.Carpet; import carpet.script.external.Vanilla; @@ -283,8 +284,7 @@ public class AppStoreManager { return 0; } - boolean success = Vanilla.MinecraftServer_getScriptServer(source.getServer()).addScriptHost(source, nodeInfo.name().replaceFirst("\\.sc$", ""), null, true, false, false, nodeInfo.source()); - return success ? 1 : 0; + return Vanilla.MinecraftServer_getScriptServer(source.getServer()).addScriptHost(source, nodeInfo.name().replaceFirst("\\.sc$", ""), null, true, false, false, nodeInfo.source(), Expression.LoadOverride.DEFAULT); } /**