diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java index b2f0bfed..4f54e583 100644 --- a/src/main/java/carpet/commands/ScriptCommand.java +++ b/src/main/java/carpet/commands/ScriptCommand.java @@ -14,6 +14,8 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.block.Block; import net.minecraft.block.pattern.CachedBlockPosition; import net.minecraft.command.arguments.BlockPosArgumentType; @@ -31,6 +33,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.function.Supplier; @@ -40,6 +43,31 @@ import static net.minecraft.server.command.CommandSource.suggestMatching; public class ScriptCommand { + + private static CompletableFuture suggestCode(SuggestionsBuilder suggestionsBuilder) { + String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); + int strlen = previous.length(); + StringBuilder lastToken = new StringBuilder(); + for (int idx = strlen-1; idx >=0; idx--) + { + char ch = previous.charAt(idx); + if (Character.isLetterOrDigit(ch) || ch == '_') + { + lastToken.append(ch); + } + else + { + break; + } + } + if (lastToken.length() == 0) + return suggestionsBuilder.buildFuture(); + String prefix = lastToken.reverse().toString(); + String previousString = previous.substring(0,previous.length()-prefix.length()) ; + ExpressionInspector.suggestFunctions(previousString, prefix).forEach(text -> suggestionsBuilder.suggest(previousString+text)); + return suggestionsBuilder.buildFuture(); + } + public static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder b = literal("globals"). @@ -50,7 +78,7 @@ public class ScriptCommand executes( (cc) -> { CarpetServer.scriptServer.stopAll = false; return 1;}); LiteralArgumentBuilder l = literal("run"). requires((player) -> player.hasPermissionLevel(2)). - then(argument("expr", StringArgumentType.greedyString()). + then(argument("expr", StringArgumentType.greedyString()).suggests((cc, bb) -> suggestCode(bb)). executes((cc) -> compute( cc, StringArgumentType.getString(cc, "expr")))); @@ -113,6 +141,7 @@ public class ScriptCommand then(argument("from", BlockPosArgumentType.create()). then(argument("to", BlockPosArgumentType.create()). then(argument("expr", StringArgumentType.greedyString()). + suggests((cc, bb) -> suggestCode(bb)). executes( (cc) -> scriptScan( cc, BlockPosArgumentType.getBlockPos(cc, "origin"), diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 8f84fe7c..a5bfa90d 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -101,6 +101,7 @@ public class CarpetExpression private ServerCommandSource source; private BlockPos origin; private Expression expr; + Expression getExpr() {return expr;} private static long tickStart = 0L; private static boolean stopAll = false; @@ -766,6 +767,8 @@ public class CarpetExpression this.expr.addLazyFunction("harvest", -1, (c, t, lv) -> { + if (lv.size()<2) + throw new InternalExpressionException("harvest takes at least 2 parameters: entity and block, or position, to harvest"); CarpetContext cc = (CarpetContext)c; World world = cc.s.getWorld(); Value entityValue = lv.get(0).evalValue(cc); @@ -791,14 +794,12 @@ public class CarpetExpression this.expr.addLazyFunction("place_item", -1, (c, t, lv) -> { + if (lv.size()<2) + throw new InternalExpressionException("place_item takes at least 2 parameters: item and block, or position, to place onto"); CarpetContext cc = (CarpetContext) c; String itemString = lv.get(0).evalValue(c).getString(); BlockValue.VectorLocator locator = BlockValue.locateVec(cc, lv, 1); ItemStackArgument stackArg = NBTSerializableValue.parseItem(itemString); - if (stackArg == null) - { - throw new InternalExpressionException("unknown item: "+itemString); - } BlockPos where = new BlockPos(locator.vec); String facing = "up"; if (lv.size() > locator.offset) @@ -876,6 +877,8 @@ public class CarpetExpression this.expr.addLazyFunction("inventory_get", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); + if (inventoryLocator == null) + return (_c, _t) -> Value.NULL; if (lv.size() == inventoryLocator.offset) { List fullInventory = new ArrayList<>(); @@ -897,6 +900,8 @@ public class CarpetExpression this.expr.addLazyFunction("inventory_drop", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); + if (inventoryLocator == null) + return (_c, _t) -> Value.NULL; if (lv.size() == inventoryLocator.offset) throw new InternalExpressionException("slot number is required for inventory_drop"); int slot = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset).evalValue(c)).getLong(); @@ -947,16 +952,14 @@ public class CarpetExpression this.expr.addLazyFunction("inventory_find", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); + if (inventoryLocator == null) + return (_c, _t) -> Value.NULL; ItemStackArgument itemArg = null; if (lv.size() > inventoryLocator.offset) { Value secondArg = lv.get(inventoryLocator.offset+0).evalValue(c); if (!(secondArg instanceof NullValue)) - { itemArg = NBTSerializableValue.parseItem(secondArg.getString()); - if (itemArg == null) - throw new InternalExpressionException("Incorrect item format: "+secondArg.getString()); - } } int startIndex = 0; if (lv.size() > inventoryLocator.offset+1) @@ -980,7 +983,8 @@ public class CarpetExpression this.expr.addLazyFunction("inventory_set", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); - + if (inventoryLocator == null) + return (_c, _t) -> Value.NULL; if (lv.size() < inventoryLocator.offset+2) throw new InternalExpressionException("inventory_set requires at least slot number and new stack size, and optional new item"); int slot = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+0).evalValue(c)).getLong(); @@ -1019,7 +1023,8 @@ public class CarpetExpression this.expr.addLazyFunction("inventory_remove", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); - + if (inventoryLocator == null) + return (_c, _t) -> Value.NULL; if (lv.size() <= inventoryLocator.offset) throw new InternalExpressionException("inventory_set requires at least an item to be removed"); ItemStackArgument searchItem = NBTSerializableValue.parseItem(lv.get(inventoryLocator.offset).evalValue(c).getString()); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index d4f3a49c..f04693fe 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.Stack; import java.util.function.BiFunction; import java.util.function.Function; @@ -350,6 +351,7 @@ public class Expression implements Cloneable boolean isAnOperator(String opname) { return operators.containsKey(opname) || operators.containsKey(opname+"u");} private Map functions = new HashMap<>(); + Set getFunctionNames() {return functions.keySet();} //static final Map globalFunctions = new HashMap<>(); diff --git a/src/main/java/carpet/script/ExpressionInspector.java b/src/main/java/carpet/script/ExpressionInspector.java index a9edc887..0602c933 100644 --- a/src/main/java/carpet/script/ExpressionInspector.java +++ b/src/main/java/carpet/script/ExpressionInspector.java @@ -1,6 +1,13 @@ package carpet.script; +import carpet.CarpetServer; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; /** * sole purpose of this package is to provide public access to package-private methods of Expression and CarpetExpression @@ -17,4 +24,29 @@ public class ExpressionInspector { return e.getName(); } + + private static TreeSet scarpetFunctions; + private static TreeSet APIFunctions; + static + { + Set allFunctions = (new CarpetExpression("null", null, null)).getExpr().getFunctionNames(); + scarpetFunctions = new TreeSet<>(new Expression("null").getFunctionNames()); + APIFunctions = new TreeSet<>(allFunctions.stream().filter(s -> !scarpetFunctions.contains(s)).collect(Collectors.toSet())); + } + + public static List suggestFunctions(String previous, String prefix) + { + previous = previous.replace("\\'", ""); + int quoteCount = StringUtils.countMatches(previous,'\''); + if (quoteCount % 2 == 1) + return Collections.emptyList(); + int maxLen = prefix.length()<3 ? (prefix.length()*2+1) : 1234; + List scarpetMatches = scarpetFunctions.stream(). + filter(s -> s.startsWith(prefix) && s.length() <= maxLen).map(s -> s+"(").collect(Collectors.toList()); + scarpetMatches.addAll(APIFunctions.stream(). + filter(s -> s.startsWith(prefix) && s.length() <= maxLen).map(s -> s+"(").collect(Collectors.toList())); + scarpetMatches.addAll(CarpetServer.scriptServer.globalHost.globalVariables.keySet().stream(). + filter(s -> s.startsWith(prefix)).collect(Collectors.toList())); + return scarpetMatches; + } } diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 8e3a0efa..a7dfe4dc 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -51,7 +51,7 @@ public class NBTSerializableValue extends Value else if (e instanceof VillagerEntity) inv = ((VillagerEntity) e).getInventory(); if (inv == null) - throw new InternalExpressionException("Entity " + e + " has no inventory"); + return null; return new InventoryLocator(e, e.getBlockPos(), inv, offset + 1); } @@ -59,10 +59,10 @@ public class NBTSerializableValue extends Value { BlockPos pos = ((BlockValue) v1).getPos(); if (pos == null) - throw new InternalExpressionException("Block to acess inventory needs to be positioned in the world"); + throw new InternalExpressionException("Block to access inventory needs to be positioned in the world"); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) - throw new InternalExpressionException("Block at " + pos + " has no inventory"); + return null; return new InventoryLocator(pos, pos, inv, offset + 1); } else if (v1 instanceof ListValue) @@ -74,7 +74,7 @@ public class NBTSerializableValue extends Value NumericValue.asNumber(args.get(2)).getDouble()); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) - throw new InternalExpressionException("Block at " + pos + " has no inventory"); + return null; return new InventoryLocator(pos, pos, inv, offset + 1); } BlockPos pos = new BlockPos( @@ -83,7 +83,7 @@ public class NBTSerializableValue extends Value NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble()); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) - throw new InternalExpressionException("Block at " + pos + " has no inventory"); + return null; return new InventoryLocator(pos, pos, inv, offset + 3); } catch (IndexOutOfBoundsException e) @@ -109,7 +109,7 @@ public class NBTSerializableValue extends Value } catch (CommandSyntaxException e) { - return null; + throw new InternalExpressionException("Incorrect item: "+itemString); } }