diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index 7f8f9e219..155476d49 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -3436,4 +3436,97 @@ public static void conditional_output_inspection(GameTestHelper helper) { assertTrue(rightChest.getStackInSlot(0).getCount() == 32, "Dirt did not arrive"); }); } -} + + @GameTest(template = "3x3x1") + public static void input_where(GameTestHelper helper) { + // Only the right chest should move items + BlockPos managerPos = new BlockPos(1, 2, 0); + helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get()); + // Create positions + BlockPos leftPos = new BlockPos(2, 2, 0); + BlockPos rightPos = new BlockPos(0, 2, 0); + BlockPos abovePos = new BlockPos(1, 3, 0); + // Set blocks + helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + helper.setBlock(abovePos, SFMBlocks.TEST_BARREL_BLOCK.get()); + + var leftChest = getItemHandler(helper, leftPos); + var rightChest = getItemHandler(helper, rightPos); + var topChest = getItemHandler(helper, abovePos); + + leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 1), false); + + rightChest.insertItem(0, new ItemStack(Blocks.DIRT, 1), false); + rightChest.insertItem(1, new ItemStack(Blocks.STONE, 1), false); + + ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos); + manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get())); + manager.setProgram(""" + EVERY 20 TICKS DO + INPUT FROM a WHERE > 0 stone + OUTPUT dirt TO b + END + """.stripTrailing().stripIndent()); + // set the labels + LabelPositionHolder.empty() + .add("a", helper.absolutePos(leftPos)) + .add("a", helper.absolutePos(rightPos)) + .add("b", helper.absolutePos(abovePos)) + .save(manager.getDisk().get()); + + succeedIfManagerDidThingWithoutLagging(helper, manager, () -> { + assertTrue(leftChest.getStackInSlot(0).getCount() == 1, "Dirt moved"); + assertTrue(rightChest.getStackInSlot(0).getCount() == 0, "Dirt did not move"); + assertTrue(topChest.getStackInSlot(0).getCount() == 1, "Dirt did not move"); + + helper.succeed(); + }); + } + + @GameTest(template = "3x3x1") + public static void output_where(GameTestHelper helper) { + // Only the right chest should get items + BlockPos managerPos = new BlockPos(1, 2, 0); + helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get()); + // Create positions + BlockPos leftPos = new BlockPos(2, 2, 0); + BlockPos rightPos = new BlockPos(0, 2, 0); + BlockPos abovePos = new BlockPos(1, 3, 0); + // Set blocks + helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + helper.setBlock(abovePos, SFMBlocks.TEST_BARREL_BLOCK.get()); + + var leftChest = getItemHandler(helper, leftPos); + var rightChest = getItemHandler(helper, rightPos); + var topChest = getItemHandler(helper, abovePos); + + rightChest.insertItem(1, new ItemStack(Blocks.STONE, 1), false); + + topChest.insertItem(0, new ItemStack(Blocks.DIRT, 2), false); + + ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos); + manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get())); + manager.setProgram(""" + EVERY 20 TICKS DO + INPUT FROM a + OUTPUT RETAIN 1 dirt TO EACH b WHERE > 0 stone + END + """.stripTrailing().stripIndent()); + // set the labels + LabelPositionHolder.empty() + .add("b", helper.absolutePos(leftPos)) + .add("b", helper.absolutePos(rightPos)) + .add("a", helper.absolutePos(abovePos)) + .save(manager.getDisk().get()); + + succeedIfManagerDidThingWithoutLagging(helper, manager, () -> { + assertTrue(leftChest.getStackInSlot(0).getCount() == 0, "Dirt moved"); + assertTrue(rightChest.getStackInSlot(0).getCount() == 1, "Dirt did not move"); + assertTrue(topChest.getStackInSlot(0).getCount() == 1, "Dirt did not move"); + + helper.succeed(); + }); + } +} \ No newline at end of file diff --git a/src/gametest/resources/data/sfm/structures/3x3x1.nbt b/src/gametest/resources/data/sfm/structures/3x3x1.nbt new file mode 100644 index 000000000..55845be4c Binary files /dev/null and b/src/gametest/resources/data/sfm/structures/3x3x1.nbt differ diff --git a/src/main/antlr/SFML.g4 b/src/main/antlr/SFML.g4 index af741d452..b560814c4 100644 --- a/src/main/antlr/SFML.g4 +++ b/src/main/antlr/SFML.g4 @@ -25,18 +25,18 @@ interval: TICK #Tick // block : statement* ; -statement : inputstatement #InputStatementStatement - | outputstatement #OutputStatementStatement - | ifstatement #IfStatementStatement - | forgetstatement #ForgetStatementStatement +statement : inputStatement + | outputStatement + | ifStatement + | forgetStatement ; // IO STATEMENT -forgetstatement : FORGET label? (COMMA label)* COMMA?; -inputstatement : INPUT inputmatchers? resourceexclusion? FROM EACH? labelaccess +forgetStatement : FORGET label? (COMMA label)* COMMA?; +inputStatement : INPUT inputmatchers? resourceexclusion? FROM EACH? labelaccess | FROM EACH? labelaccess INPUT inputmatchers? resourceexclusion? ; -outputstatement : OUTPUT outputmatchers? resourceexclusion? TO EACH? labelaccess +outputStatement : OUTPUT outputmatchers? resourceexclusion? TO EACH? labelaccess | TO EACH? labelaccess OUTPUT outputmatchers? resourceexclusion? ; inputmatchers : movement; // separate for different defaults @@ -74,7 +74,7 @@ rangeset : range (COMMA range)*; range : number (DASH number)? ; -ifstatement : IF boolexpr THEN block (ELSE IF boolexpr THEN block)* (ELSE block)? END; +ifStatement : IF boolexpr THEN block (ELSE IF boolexpr THEN block)* (ELSE block)? END; boolexpr : TRUE #BooleanTrue | FALSE #BooleanFalse | LPAREN boolexpr RPAREN #BooleanParen @@ -112,12 +112,20 @@ setOp : OVERALL // // IO HELPERS // -labelaccess : label (COMMA label)* roundrobin? sidequalifier? slotqualifier?; -roundrobin: ROUND ROBIN BY (LABEL | BLOCK); +labelaccess : label (COMMA label)* roundrobin? sidequalifier? slotqualifier? (WHERE where)?; +roundrobin : ROUND ROBIN BY (LABEL | BLOCK); label : (IDENTIFIER|REDSTONE) #RawLabel | string #StringLabel ; +where : LPAREN where RPAREN # WhereParen + | NOT where # WhereNegation + | where AND where # WhereConjunction + | where OR where # WhereDisjunction + | resourcecomparison # WhereComparison + ; + + resourceid : (IDENTIFIER|REDSTONE) (COLON (IDENTIFIER|REDSTONE)? (COLON (IDENTIFIER|REDSTONE)? (COLON (IDENTIFIER|REDSTONE)?)?)?)? # Resource | string # StringResource ; @@ -204,7 +212,7 @@ DO : D O ; WORLD : W O R L D ; PROGRAM : P R O G R A M ; END : E N D ; -NAME : N A M E ; +NAME : N A M E; // GENERAL SYMBOLS // used by triggers and as a set operator diff --git a/src/main/java/ca/teamdman/sfm/client/ProgramSyntaxHighlightingHelper.java b/src/main/java/ca/teamdman/sfm/client/ProgramSyntaxHighlightingHelper.java index 7ce84495e..01c81c625 100644 --- a/src/main/java/ca/teamdman/sfm/client/ProgramSyntaxHighlightingHelper.java +++ b/src/main/java/ca/teamdman/sfm/client/ProgramSyntaxHighlightingHelper.java @@ -100,6 +100,7 @@ private static ChatFormatting getColour(Token token) { case SFMLLexer.TRUE: case SFMLLexer.FALSE: case SFMLLexer.FORGET: + case SFMLLexer.WHERE: return ChatFormatting.BLUE; case SFMLLexer.IDENTIFIER: case SFMLLexer.STRING: diff --git a/src/main/java/ca/teamdman/sfm/common/net/ServerboundContainerExportsInspectionRequestPacket.java b/src/main/java/ca/teamdman/sfm/common/net/ServerboundContainerExportsInspectionRequestPacket.java index d499f02fd..99de6db48 100644 --- a/src/main/java/ca/teamdman/sfm/common/net/ServerboundContainerExportsInspectionRequestPacket.java +++ b/src/main/java/ca/teamdman/sfm/common/net/ServerboundContainerExportsInspectionRequestPacket.java @@ -167,7 +167,8 @@ public static String buildInspectionResults( ? EnumSet.noneOf(Direction.class) : EnumSet.of(direction)), NumberRangeSet.MAX_RANGE, - RoundRobin.disabled() + RoundRobin.disabled(), + WhereStatement.ALWAYS_TRUE ), new ResourceLimits( resourceLimitList.stream().distinct().toList(), diff --git a/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java b/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java index c9e846c91..22c38622c 100644 --- a/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java +++ b/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java @@ -154,6 +154,46 @@ public void forEachCapability( } } + public void forCapabilityOfBlock( + ProgramContext programContext, + DirectionQualifier directionQualifier, + Pair labelPosPair, + CapabilityConsumer consumer + ) { + CableNetwork network = programContext.getNetwork(); + + var label = labelPosPair.getFirst(); + var blockPos = labelPosPair.getSecond(); + + for (Direction dir : (Iterable) directionQualifier.stream()::iterator) { + // Get capability from the network + Optional maybeCap = network + .getCapability(CAPABILITY_KIND, blockPos, dir, programContext.getLogger()) + .resolve(); + if (maybeCap.isPresent()) { + programContext + .getLogger() + .debug(x -> x.accept(Constants.LocalizationKeys.LOG_RESOURCE_TYPE_GET_CAPABILITIES_CAP_PRESENT.get( + displayAsCapabilityClass(), + blockPos, + dir + ))); + CAP cap = maybeCap.get(); + consumer.accept(label, blockPos, dir, cap); + + } else { + // Log error + programContext + .getLogger() + .error(x -> x.accept(Constants.LocalizationKeys.LOG_RESOURCE_TYPE_GET_CAPABILITIES_CAP_NOT_PRESENT.get( + displayAsCapabilityClass(), + blockPos, + dir + ))); + } + } + } + public Stream getStacksInSlots( CAP cap, NumberRangeSet slots diff --git a/src/main/java/ca/teamdman/sfm/common/util/SFMUtils.java b/src/main/java/ca/teamdman/sfm/common/util/SFMUtils.java index 490570ed9..1e5edcd4f 100644 --- a/src/main/java/ca/teamdman/sfm/common/util/SFMUtils.java +++ b/src/main/java/ca/teamdman/sfm/common/util/SFMUtils.java @@ -155,7 +155,8 @@ public static Optional getInputStatementForSl labelAccess.directions(), inputStatement.labelAccess() .slots(), - RoundRobin.disabled() + RoundRobin.disabled(), + labelAccess.where() ), inputStatement.resourceLimits(), inputStatement.each())); } @@ -178,7 +179,8 @@ public static InputStatement getInputStatementForStack( new NumberRangeSet( new NumberRange[]{new NumberRange(slot, slot)} ), - RoundRobin.disabled() + RoundRobin.disabled(), + WhereStatement.ALWAYS_TRUE ); Limit limit = new Limit( new ResourceQuantity( diff --git a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java index 52b3a1340..bb11141b1 100644 --- a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java +++ b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java @@ -23,7 +23,7 @@ public List> getNodesUnderCursor(int cursorPos) .stream() .filter(pair -> pair.getSecond() != null) .filter(pair -> pair.getSecond().start.getStartIndex() <= cursorPos - && pair.getSecond().stop.getStopIndex() >= cursorPos) + && pair.getSecond().stop.getStopIndex() >= cursorPos) .collect(Collectors.toList()); } @@ -106,8 +106,8 @@ public Label visitRawLabel(SFMLParser.RawLabelContext ctx) { var label = new Label(ctx.getText()); if (label.name().length() > Program.MAX_LABEL_LENGTH) { throw new IllegalArgumentException("Label name cannot be longer than " - + Program.MAX_LABEL_LENGTH - + " characters."); + + Program.MAX_LABEL_LENGTH + + " characters."); } USED_LABELS.add(label); AST_NODE_CONTEXTS.add(new Pair<>(label, ctx)); @@ -119,8 +119,8 @@ public Label visitStringLabel(SFMLParser.StringLabelContext ctx) { var label = new Label(visitString(ctx.string()).value()); if (label.name().length() > Program.MAX_LABEL_LENGTH) { throw new IllegalArgumentException("Label name cannot be longer than " - + Program.MAX_LABEL_LENGTH - + " characters."); + + Program.MAX_LABEL_LENGTH + + " characters."); } USED_LABELS.add(label); AST_NODE_CONTEXTS.add(new Pair<>(label, ctx)); @@ -154,8 +154,8 @@ public ASTNode visitTimerTrigger(SFMLParser.TimerTriggerContext ctx) { // get default min interval int minInterval = timerTrigger.usesOnlyForgeEnergyResourceIO() - ? SFMConfig.getOrDefault(SFMConfig.COMMON.timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIO) - : SFMConfig.getOrDefault(SFMConfig.COMMON.timerTriggerMinimumIntervalInTicks); + ? SFMConfig.getOrDefault(SFMConfig.COMMON.timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIO) + : SFMConfig.getOrDefault(SFMConfig.COMMON.timerTriggerMinimumIntervalInTicks); // validate interval if (time.getTicks() < minInterval) { @@ -188,8 +188,8 @@ public ASTNode visitBooleanRedstone(SFMLParser.BooleanRedstoneContext ctx) { .getManager() .getLevel() .getBestNeighborSignal(programContext - .getManager() - .getBlockPos()), + .getManager() + .getBlockPos()), (long) finalNum ), ctx.getText() @@ -243,21 +243,7 @@ public Interval visitSeconds(SFMLParser.SecondsContext ctx) { } @Override - public InputStatement visitInputStatementStatement(SFMLParser.InputStatementStatementContext ctx) { - InputStatement input = (InputStatement) visit(ctx.inputstatement()); - AST_NODE_CONTEXTS.add(new Pair<>(input, ctx)); - return input; - } - - @Override - public OutputStatement visitOutputStatementStatement(SFMLParser.OutputStatementStatementContext ctx) { - OutputStatement output = (OutputStatement) visit(ctx.outputstatement()); - AST_NODE_CONTEXTS.add(new Pair<>(output, ctx)); - return output; - } - - @Override - public InputStatement visitInputstatement(SFMLParser.InputstatementContext ctx) { + public InputStatement visitInputStatement(SFMLParser.InputStatementContext ctx) { var labelAccess = visitLabelaccess(ctx.labelaccess()); var matchers = visitInputmatchers(ctx.inputmatchers()); var exclusions = visitResourceexclusion(ctx.resourceexclusion()); @@ -268,7 +254,7 @@ public InputStatement visitInputstatement(SFMLParser.InputstatementContext ctx) } @Override - public OutputStatement visitOutputstatement(SFMLParser.OutputstatementContext ctx) { + public OutputStatement visitOutputStatement(SFMLParser.OutputStatementContext ctx) { var labelAccess = visitLabelaccess(ctx.labelaccess()); var matchers = visitOutputmatchers(ctx.outputmatchers()); var exclusions = visitResourceexclusion(ctx.resourceexclusion()); @@ -287,11 +273,21 @@ public LabelAccess visitLabelaccess(SFMLParser.LabelaccessContext ctx) { } else { directionQualifier = (DirectionQualifier) visit(directionQualifierCtx); } + + var whereCtx = ctx.where(); + WhereStatement where; + if (whereCtx == null) { + where = WhereStatement.ALWAYS_TRUE; + } else { + where = (WhereStatement) visit(whereCtx); + } + LabelAccess labelAccess = new LabelAccess( ctx.label().stream().map(this::visit).map(Label.class::cast).collect(Collectors.toList()), directionQualifier, visitSlotqualifier(ctx.slotqualifier()), - visitRoundrobin(ctx.roundrobin()) + visitRoundrobin(ctx.roundrobin()), + where ); AST_NODE_CONTEXTS.add(new Pair<>(labelAccess, ctx)); return labelAccess; @@ -301,12 +297,12 @@ public LabelAccess visitLabelaccess(SFMLParser.LabelaccessContext ctx) { public RoundRobin visitRoundrobin(@Nullable SFMLParser.RoundrobinContext ctx) { if (ctx == null) return RoundRobin.disabled(); return ctx.BLOCK() != null - ? new RoundRobin(RoundRobin.Behaviour.BY_BLOCK) - : new RoundRobin(RoundRobin.Behaviour.BY_LABEL); + ? new RoundRobin(RoundRobin.Behaviour.BY_BLOCK) + : new RoundRobin(RoundRobin.Behaviour.BY_LABEL); } @Override - public IfStatement visitIfstatement(SFMLParser.IfstatementContext ctx) { + public IfStatement visitIfStatement(SFMLParser.IfStatementContext ctx) { var conditions = ctx .boolexpr() .stream() @@ -348,13 +344,6 @@ public IfStatement visitIfstatement(SFMLParser.IfstatementContext ctx) { return nestedStatement; } - @Override - public IfStatement visitIfStatementStatement(SFMLParser.IfStatementStatementContext ctx) { - IfStatement ifStatement = visitIfstatement(ctx.ifstatement()); - AST_NODE_CONTEXTS.add(new Pair<>(ifStatement, ctx)); - return ifStatement; - } - @Override public BoolExpr visitBooleanTrue(SFMLParser.BooleanTrueContext ctx) { BoolExpr boolExpr = new BoolExpr(__ -> true, "TRUE"); @@ -540,14 +529,7 @@ public NumberRangeSet visitSlotqualifier(@Nullable SFMLParser.SlotqualifierConte } @Override - public ASTNode visitForgetStatementStatement(SFMLParser.ForgetStatementStatementContext ctx) { - ForgetStatement statement = (ForgetStatement) visit(ctx.forgetstatement()); - AST_NODE_CONTEXTS.add(new Pair<>(statement, ctx)); - return statement; - } - - @Override - public ForgetStatement visitForgetstatement(SFMLParser.ForgetstatementContext ctx) { + public ForgetStatement visitForgetStatement(SFMLParser.ForgetStatementContext ctx) { Set