Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions LANGUAGE_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,37 @@ Signal copper = ("copper-plate", 50);
Signal flag = ("signal-A", 1);
```

#### Wire Color Pinning

Signals and bundles can be pinned to a specific wire color using the `.wire` attribute:

```facto
Signal iron = ("iron-plate", 100);
iron.wire = red; // pinned to red wire

Signal ctrl = ("signal-C", 1);
ctrl.wire = green; // pinned to green wire

Signal auto = ("signal-A", 50);
// no .wire assignment = automatic (default)
```

Wire color pinning works on any named Signal or Bundle, including computed values:

```facto
Signal a = ("signal-A", 10);
Signal b = ("signal-B", 20);
Signal sum = a + b;
sum.wire = red; // the arithmetic combinator's output is pinned to red

Bundle sensors = { ("signal-T", 0), ("signal-P", 0) };
sensors.wire = green; // the bundle's output is pinned to green
```

When `.wire` is set, the compiler adds a hard constraint ensuring all connections from the producing entity use the specified color. This is useful when connecting the compiled blueprint to external circuits on specific wires.

If `.wire` is not set, the compiler automatically assigns wire colors using its constraint solver (the default behavior). User-specified colors take priority over all automatic assignments.

**Type Literal Syntax:**

The type name in signal literals is a string:
Expand Down Expand Up @@ -1443,6 +1474,17 @@ Factorio has two circuit wire colors: **red** and **green**.

The compiler's **wire router** automatically assigns colors to avoid conflicts when multiple sources produce the same signal type to the same destination. Additionally, the compiler uses wire colors strategically for memory systems:

**Manual Wire Color Control:**

For input signals that interface with external circuits, you can pin specific wire colors using the signal literal 3-tuple form:

```facto
Signal sensor_input = ("signal-S", 0, red); # external sensor on red
Signal control_input = ("signal-C", 0, green); # external control on green
```

User-specified colors take priority over automatic assignment. The compiler will respect the annotation and build the rest of the wire color assignment around it.

**Memory Wire Color Strategy:**
- **RED wires**: Data signals and feedback loops (e.g., signal-A, signal-B, iron-plate)
- **GREEN wires**: Control signals (signal-W for memory write enable)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Save this to a `blink.facto`, run `factompile blink.facto`, copy the output, imp

## What Facto Does

Facto handles the tedious parts of circuit building so you can focus only on logic. The compiler takes care of placing combinators in a sensible layout, routing wires between them (choosing red vs green to avoid signal conflicts), and inserting relay poles when distances exceed 9 tiles. It catches type mismatches at compile time rather than leaving you to debug mysterious in-game behavior.
Facto handles the tedious parts of circuit building so you can focus only on logic. The compiler takes care of placing combinators in a sensible layout, routing wires between them (choosing red vs green to avoid signal conflicts), and inserting relay poles when distances exceed 9 tiles. You can also pin specific signals to red or green wires when interfacing with external circuits. It catches type mismatches at compile time rather than leaving you to debug mysterious in-game behavior.

What Facto doesn't do: it might not produce the most minimal, most compact circuit layouts. The goal is to make circuits that are efficient enough to run well in-game while being easy to read, write, and maintain. If you need absolute minimalism, hand-optimizing the generated blueprint is still an option.

Expand Down
8 changes: 4 additions & 4 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ def lower_program(
def emit_blueprint(
ir_operations: list[IRNode],
label: str = "DSL Generated",
signal_type_map: dict[str, Any] = None,
signal_type_map: dict[str, Any] | None = None,
*,
power_pole_type: str = None,
power_pole_type: str | None = None,
) -> tuple[Blueprint, ProgramDiagnostics]:
"""Convert IR operations to Factorio blueprint.

Expand Down Expand Up @@ -124,9 +124,9 @@ def emit_blueprint(
def emit_blueprint_string(
ir_operations: list[IRNode],
label: str = "DSL Generated",
signal_type_map: dict[str, Any] = None,
signal_type_map: dict[str, Any] | None = None,
*,
power_pole_type: str = None,
power_pole_type: str | None = None,
) -> tuple[str, ProgramDiagnostics]:
"""Convert IR operations to Factorio blueprint string.

Expand Down
13 changes: 13 additions & 0 deletions doc/02_quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ You'll use this pattern constantly. It's the foundation of conditional logic in

Sometimes you need to output a value using a different signal type. The projection operator (`|`) lets you do this: `value | "signal-type"`. For example, `counter.read() | "iron-plate"` outputs the counter's value on the `iron-plate` signal. See [Signals and Types](03_signals_and_types.md#the-projection-operator-) for full details.

### Note: Wire Color Pinning

When connecting compiled blueprints to external circuits, you can pin signals to a specific wire color using the `.wire` attribute:

```facto
Signal external_sensor = ("signal-S", 0);
Signal external_control = ("signal-C", 1);
external_sensor.wire = red; // arrives on red wire
external_control.wire = green; // arrives on green wire
```

This ensures your inputs connect on the correct wires. Without pinning, the compiler automatically assigns wire colors. See [Signals and Types](03_signals_and_types.md#pinning-wire-colors) for details.

---

## Saving to Files
Expand Down
34 changes: 34 additions & 0 deletions doc/03_signals_and_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ Signal z = 15; # Compiler assigns signal-C

The compiler allocates virtual signals (`signal-A`, `signal-B`, etc.) automatically. Perfect for intermediate calculations.

### Pinning Wire Colors

When interfacing with external circuits, you can pin signals to a specific wire color using the `.wire` attribute:

```facto
Signal sensor = ("signal-S", 0);
sensor.wire = red; // this signal uses the red wire

Signal control = ("signal-C", 1);
control.wire = green; // this signal uses the green wire

Signal automatic = ("signal-A", 50);
// no .wire = automatic assignment (default)
```

Wire color pinning works on any named Signal or Bundle, including computed values:

```facto
Signal a = ("signal-A", 10);
Signal b = ("signal-B", 20);
Signal sum = a + b;
sum.wire = red; // the arithmetic combinator's output is pinned to red

Bundle sensors = { ("signal-T", 0), ("signal-P", 0) };
sensors.wire = green; // the bundle's output is pinned to green
```

Wire color pinning ensures the producing entity connects using the specified wire color. This is particularly useful when:
- Connecting compiled blueprints to existing circuits with specific wire conventions
- Keeping data signals (red) separate from control signals (green)
- Interfacing with external sensors or controllers that output on specific wires

If `.wire` is not set, the compiler automatically assigns colors to avoid conflicts.

### The `int` Type

For compile-time constants that shouldn't become signals:
Expand Down
10 changes: 5 additions & 5 deletions doc/generate_entity_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def get_all_int_enums() -> dict[str, EnumInfo]:
and issubclass(obj, (IntEnum, Enum))
and hasattr(obj, "__members__")
):
members = {}
members: dict[str, int | str] = {}
for member_name, member in obj.__members__.items():
try:
val = member.value
Expand Down Expand Up @@ -297,7 +297,7 @@ def find_literal_for_type(type_hint: Any) -> tuple[tuple | None, EnumInfo | None
}

# Entity class name -> output description mapping
ENTITY_OUTPUT_DESCRIPTIONS = {
ENTITY_OUTPUT_DESCRIPTIONS: dict[str, dict[str, Any]] = {
"Container": {
"supports_output": True,
"description": "Item contents of the container",
Expand Down Expand Up @@ -572,7 +572,7 @@ def get_entity_properties(cls: type) -> list[PropertyInfo]:
# Default value
if fld.default is attrs.NOTHING:
default = "required"
elif isinstance(fld.default, attrs.Factory):
elif isinstance(fld.default, attrs.Factory): # type: ignore[arg-type]
default = "(factory)"
elif fld.default is None:
default = "None"
Expand Down Expand Up @@ -890,8 +890,8 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
if output_info.enable_properties:
lines.append("**Enable properties:**")
lines.append("")
for prop, desc in output_info.enable_properties.items():
lines.append(f"- `{prop}`: {desc}")
for prop_name, desc in output_info.enable_properties.items():
lines.append(f"- `{prop_name}`: {desc}")
lines.append("")

# DSL Examples
Expand Down
4 changes: 3 additions & 1 deletion dsl_compiler/grammar/facto.lark
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ memory_write_when: NAME "." "write" "(" expr "," WHEN_KW "=" expr ")"
// The ORDER of set/reset determines priority (first one wins in conflict)
SET_KW: "set"
RESET_KW: "reset"


memory_latch_write: NAME "." "write" "(" expr "," latch_kwargs ")"

// Two orderings for set/reset - order determines priority
Expand Down Expand Up @@ -214,7 +216,7 @@ bundle_all: ALL_KW "(" expr ")"

// Signal literal syntax: ("type", value) or just value
signal_literal: "(" type_literal "," expr ")" -> signal_with_type
| NUMBER -> signal_constant
| NUMBER -> signal_constant

// Type literal can be a string, a name, or a property access (for signal.type)
type_literal: STRING
Expand Down
Loading