From 3852c0c619d3ce05968d2f8385250c2534cda69a Mon Sep 17 00:00:00 2001 From: Reinder Noordmans Date: Sat, 24 Jan 2026 18:50:55 +0100 Subject: [PATCH 01/33] feat: add struct grammar rules to sfml --- src/main/antlr/sfml/SFML.g4 | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/antlr/sfml/SFML.g4 b/src/main/antlr/sfml/SFML.g4 index 3c2e70f78..a68fa01d9 100644 --- a/src/main/antlr/sfml/SFML.g4 +++ b/src/main/antlr/sfml/SFML.g4 @@ -6,10 +6,28 @@ package ca.teamdman.langs; public boolean INCLUDE_UNUSED = false; // we want syntax highlighting to not break on unexpected tokens } -program : name? trigger* EOF; +program : name? structDefinition* letStatement* trigger* EOF; name: NAME string ; +// +// STRUCT DEFINITIONS +// + +structDefinition : STRUCT identifier structBody END ; +structBody : structField* ; +structField : identifier COLON structFieldValue ; +structFieldValue : sidequalifier slotqualifier? // composite: TOP SIDE SLOTS 0 + | slotqualifier + | label // label must come before resourceIdDisjunction to match strings correctly + | resourceIdDisjunction + | number + ; + +letStatement : LET identifier EQ_SYMBOL structInstantiation ; +structInstantiation : identifier LBRACE structFieldAssignment (COMMA structFieldAssignment)* COMMA? RBRACE ; +structFieldAssignment : identifier COLON structFieldValue ; + // // TRIGGERS // @@ -141,7 +159,9 @@ setOp : OVERALL // // IO HELPERS // -labelAccess : label (COMMA label)* roundrobin? sidequalifier? slotqualifier?; +labelAccess : label (COMMA label)* roundrobin? sidequalifier? slotqualifier? #DirectLabelAccess + | identifier USING identifier sidequalifier? slotqualifier? #StructLabelAccess + ; roundrobin : ROUND ROBIN BY (LABEL | BLOCK); label : (identifier) #RawLabel @@ -150,7 +170,7 @@ label : (identifier) #RawLabel emptyslots : EMPTY (SLOTS | SLOT) IN ; -identifier : (IDENTIFIER | REDSTONE | GLOBAL | SECOND | SECONDS | TOP | BOTTOM | LEFT | RIGHT | FRONT | BACK) ; +identifier : (IDENTIFIER | REDSTONE | GLOBAL | SECOND | SECONDS | TOP | BOTTOM | LEFT | RIGHT | FRONT | BACK | INPUT | OUTPUT | LABEL | STRUCT | LET | USING | SLOT | SLOTS | SIDE | BLOCK) ; // GENERAL string: STRING ; @@ -252,6 +272,13 @@ DO : D O ; END : E N D ; NAME : N A M E ; +// STRUCT SYMBOLS +STRUCT : S T R U C T ; +LET : L E T ; +USING : U S I N G ; +LBRACE : '{' ; +RBRACE : '}' ; + // GENERAL SYMBOLS // used by triggers and as a set operator EVERY : E V E R Y ; From 1aa745ed75bfc1c99f4daea8a398359108711b91 Mon Sep 17 00:00:00 2001 From: Reinder Noordmans Date: Sat, 24 Jan 2026 18:51:02 +0100 Subject: [PATCH 02/33] feat: add struct ast classes --- .../sfml/ast/CompositeFieldValue.java | 27 ++++++++++ .../ca/teamdman/sfml/ast/LetStatement.java | 15 ++++++ .../ca/teamdman/sfml/ast/StructAccess.java | 18 +++++++ .../teamdman/sfml/ast/StructDefinition.java | 39 ++++++++++++++ .../ca/teamdman/sfml/ast/StructField.java | 15 ++++++ .../teamdman/sfml/ast/StructFieldValue.java | 9 ++++ .../ca/teamdman/sfml/ast/StructInstance.java | 53 +++++++++++++++++++ 7 files changed, 176 insertions(+) create mode 100644 src/main/java/ca/teamdman/sfml/ast/CompositeFieldValue.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/LetStatement.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/StructAccess.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/StructDefinition.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/StructField.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/StructFieldValue.java create mode 100644 src/main/java/ca/teamdman/sfml/ast/StructInstance.java diff --git a/src/main/java/ca/teamdman/sfml/ast/CompositeFieldValue.java b/src/main/java/ca/teamdman/sfml/ast/CompositeFieldValue.java new file mode 100644 index 000000000..f06e428c0 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/CompositeFieldValue.java @@ -0,0 +1,27 @@ +package ca.teamdman.sfml.ast; + +/** + * Represents a composite struct field value that combines side and slot qualifiers. + * Example: TOP SIDE SLOTS 0 + */ +public record CompositeFieldValue( + SideQualifier sides, + NumberRangeSet slots +) implements StructFieldValue { + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (!sides.equals(SideQualifier.NULL)) { + sb.append(sides.sides().stream() + .map(Side::toString) + .reduce((a, b) -> a + ", " + b) + .orElse("")); + sb.append(" SIDE"); + } + if (!slots.equals(NumberRangeSet.MAX_RANGE)) { + if (!sb.isEmpty()) sb.append(" "); + sb.append("SLOTS ").append(slots); + } + return sb.toString(); + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/LetStatement.java b/src/main/java/ca/teamdman/sfml/ast/LetStatement.java new file mode 100644 index 000000000..9d2eb38bb --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/LetStatement.java @@ -0,0 +1,15 @@ +package ca.teamdman.sfml.ast; + +/** + * Represents a let statement that binds a struct instance to a variable name. + * Example: let smelter = Furnace { label: "my furnaces" } + */ +public record LetStatement( + String variableName, + StructInstance instance +) implements ASTNode { + @Override + public String toString() { + return "let " + variableName + " = " + instance; + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/StructAccess.java b/src/main/java/ca/teamdman/sfml/ast/StructAccess.java new file mode 100644 index 000000000..ef01a9c45 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/StructAccess.java @@ -0,0 +1,18 @@ +package ca.teamdman.sfml.ast; + +/** + * Represents access to a struct field using the USING keyword. + * Example: smelter using input + * + * This is used to track the original source of a struct field access + * before it is resolved into a concrete LabelAccess. + */ +public record StructAccess( + String variableName, + String fieldName +) implements ASTNode { + @Override + public String toString() { + return variableName + " using " + fieldName; + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/StructDefinition.java b/src/main/java/ca/teamdman/sfml/ast/StructDefinition.java new file mode 100644 index 000000000..628c507b2 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/StructDefinition.java @@ -0,0 +1,39 @@ +package ca.teamdman.sfml.ast; + +import java.util.List; +import java.util.Optional; + +/** + * Represents a struct definition in the program. + * Example: + * struct Furnace + * input: TOP SIDE SLOTS 0 + * fuel: BOTTOM SIDE SLOTS 1 + * output: BOTTOM SIDE SLOTS 2 + * end + */ +public record StructDefinition( + String name, + List fields +) implements ASTNode { + + /** + * Gets a field by name from this struct definition. + */ + public Optional getField(String fieldName) { + return fields.stream() + .filter(f -> f.name().equals(fieldName)) + .findFirst(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("struct ").append(name).append("\n"); + for (StructField field : fields) { + sb.append(" ").append(field).append("\n"); + } + sb.append("end"); + return sb.toString(); + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/StructField.java b/src/main/java/ca/teamdman/sfml/ast/StructField.java new file mode 100644 index 000000000..8bc0a6579 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/StructField.java @@ -0,0 +1,15 @@ +package ca.teamdman.sfml.ast; + +/** + * Represents a field within a struct definition. + * Example: input: TOP SIDE SLOTS 0 + */ +public record StructField( + String name, + StructFieldValue value +) implements ASTNode { + @Override + public String toString() { + return name + ": " + value; + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/StructFieldValue.java b/src/main/java/ca/teamdman/sfml/ast/StructFieldValue.java new file mode 100644 index 000000000..d3987b323 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/StructFieldValue.java @@ -0,0 +1,9 @@ +package ca.teamdman.sfml.ast; + +/** + * Marker interface for values that can appear in struct field definitions. + * Used for type safety in struct field value assignments. + */ +public sealed interface StructFieldValue extends ASTNode + permits SideQualifier, NumberRangeSet, ResourceIdSet, Label, Number, CompositeFieldValue { +} diff --git a/src/main/java/ca/teamdman/sfml/ast/StructInstance.java b/src/main/java/ca/teamdman/sfml/ast/StructInstance.java new file mode 100644 index 000000000..55b743086 --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/StructInstance.java @@ -0,0 +1,53 @@ +package ca.teamdman.sfml.ast; + +import java.util.Map; +import java.util.Optional; + +/** + * Represents an instantiation of a struct with overrides. + * Example: Furnace { label: "my furnaces" } + */ +public record StructInstance( + String variableName, + StructDefinition definition, + Map overrides +) implements ASTNode { + + /** + * Resolves a field value, checking overrides first, then the definition. + */ + public Optional resolveField(String fieldName) { + // Check overrides first + if (overrides.containsKey(fieldName)) { + return Optional.of(overrides.get(fieldName)); + } + // Fall back to definition + return definition.getField(fieldName).map(StructField::value); + } + + /** + * Gets the label for this struct instance. + * The label must be provided as an override since it binds to actual blocks. + */ + public Optional