factompile

repository·main·Indexed 19 days ago

https://github.com/snagnar/factompiler

A compiler for the Facto programming language that generates Factorio circuit network blueprints. It allows developers to write high-level logic—including signals, memory cells, bundles, functions, and loops—which is then translated into optimized, wired combinator layouts and entities for the game.

Tokens
57.4K
Snippets
206
Records
273
Agent score
62%

What's inside factompile

  1. What is a Signal in Facto?

    main

    A Signal is the fundamental data unit in Facto, representing data flowing through Factorio's circuit networks. Every signal consists of two components:

    1. Type: The kind of signal (e.g., iron-plate, signal-A, water).
    2. Value: An integer count.

    You can declare a signal using a tuple syntax: (type, value).

    Signal my_signal = ("iron-plate", 50);
  2. Configure enum and boolean properties

    main

    When configuring entities, properties may require specific types:

    • Enum Properties: Use the integer value corresponding to the desired enum name.
    • Boolean Properties: Use 1 for true and 0 for false.
    • Literal (String) Enums: Use the exact string value (e.g., "whitelist").
    # Enum property (1 = COMPONENTS)
    Entity lamp = place("small-lamp", 0, 0, {color_mode: 1});
    
    # Boolean property
    Entity lamp2 = place("small-lamp", 0, 0, {use_colors: 1, always_on: 1});
  3. Implement state machines using condition : value syntax

    main

    Facto provides an efficient way to handle branching and state transitions using the condition : value syntax. This allows you to define a value based on whether a condition is met, acting as a concise if-then-else mechanism.

    In state machines, this is used to map current states and input signals to a next_state value.

    # Syntax: (condition : value_if_true) + (condition : value_if_false)
    # Or for transitions:
    Signal next_state = 
        ((current_state == 0 && start_signal > 0) : 1) +
        ((current_state == 1 && stop_signal > 0) : 2) +
        ((current_state == 0 && start_signal == 0) : 0);
  4. Manage signal types and propagation

    main

    Facto uses type propagation to maintain consistency. When performing operations between signals, the resulting signal inherits the type of the source. If you need to force a specific signal type (e.g., to avoid mixed-type warnings), use the pipe operator with a string literal: signal | "type-name".

    Type Access:

    • Use signal.type to retrieve the type name of a signal.
    Signal source = ("iron-plate", 100);
    # Inherit type
    Signal offset = 50 | source.type;
    # Explicitly set type to avoid mixed-type errors
    Signal aligned = (copper | "iron-plate") + iron;
  5. Performance considerations for library functions

    main

    When using the standard library, keep the following performance characteristics in mind:

    1. Inlining: All library functions are inlined at each call site. Calling the same function multiple times results in multiple copies of the combinators being generated.
    2. Memory instances: Functions that declare Memory create separate memory cells for every individual call. While this provides independent state (like counters), it can be memory-intensive if many instances are needed and state could have been shared.
    3. Combinator counts: Documented combinator counts are approximations and may change based on compiler optimizations.
  6. Optimize entity placement with the layout engine

    main

    Facto supports two modes of positioning entities:

    1. Fixed Positions: Use integer constants for precise, manual placement.
    2. Layout-Optimized Positions: When you provide a signal or expression as a coordinate, the compiler's layout engine automatically chooses a position to optimize wire connections for functional circuits.

    Example of layout optimization:

    Signal x_pos = some_signal;
    Entity lamp = place("small-lamp", x_pos, 0);  # Compiler chooses position
  7. Use conditional value syntax for logic

    main

    Facto uses a specific syntax for conditional values, which compiles efficiently to Factorio decider combinators in "copy input" mode.

    Syntax

    Signal <name> = <condition> : <value>;

    This outputs <value> when the condition is true, and 0 when false.

    Example: Clamping a value

    Signal speed = ("signal-S", 150);
    # Limit speed to maximum of 100
    Signal capped = (speed > 100) : 100;
    Signal passed = (speed <= 100) : speed;
    Signal safe_speed = capped + passed;

    Example: Selection (If-Then-Else)

    Signal flag = ("signal-F", 1);
    Signal value_a = ("signal-A", 100);
    Signal value_b = ("signal-B", 200);
    
    # Choose based on flag
    Signal result = ((flag > 0) : value_a) + ((flag == 0) : value_b);
    Signal result = condition : value;
  8. Use Bundles to manage multiple signals

    main

    A Bundle allows you to group multiple signals together to perform collective operations like any() or all().

    Example: Resource Monitor

    # Bundle of resources
    Bundle resources = { 
        ("iron-plate", 0), 
        ("copper-plate", 0), 
        ("coal", 0) 
    };
    
    # Light warning lamp if ANY resource drops below 100
    Signal any_low = any(resources) < 100;
    
    Entity warning = place("small-lamp", 0, 0);
    warning.enable = any_low > 0;
    • any(bundle): Returns true if any signal in the bundle meets the condition.
    • all(bundle): Returns true if all signals in the bundle meet the condition.
    Bundle resources = { ("iron-plate", 0), ("copper-plate", 0) };
    Signal any_low = any(resources) < 100;
  9. Use conditional values for efficient logic

    main

    The : operator (conditional value) is more efficient than using multiplication for conditional logic. Using : results in a single decider (copying the input), whereas multiplication requires a decider plus an arithmetic combinator.

    Best Practice: Use (cond) : value instead of (cond) * value for selection, clamping, and state machines.

    # Efficient Selection (if-then-else)
    Signal result = ((x > 0) : a) + ((x <= 0) : b);
    
    # Efficient Clamping
    Signal clamped = ((x < min) : min) + ((x > max) : max) + ((x >= min && x <= max) : x);
    
    # Efficient State Machines
    Signal next = ((state == 0 && start) : 1) + ((state == 1 && stop) : 2) + ((!change) : state);
  10. Use the `int` type for compile-time constants

    main

    Use the int type for numeric constants that should not be converted into signals (combinator outputs).

    • Signal x = 5 creates a constant combinator outputting signal-A: 5.
    • int x = 5 is just the number 5 used for calculations and does not create a combinator.

    Example:

    int multiplier = 5;
    Signal result = input * multiplier; # multiplier is just the number 5
    int threshold = 100;
    Signal result = input * threshold; # threshold is a constant, not a signal
  11. Planned Selector Combinator Syntax

    main

    Future versions of Facto may support selector combinators for advanced signal filtering, inspired by Factorio 2.0. These features will allow selecting signals by value (highest/lowest), selecting by index after sorting, counting unique signals, and retrieving stack sizes or quality grades. Note that these are currently not implemented.

    # Select the maximum value signal
    Signal highest = select_max(bundle);
    
    # Select by index (0 = first after sorting)
    Signal third_highest = select_index(bundle, 2, descending=true);
    
    # Count unique signals
    Signal count = count_signals(bundle);
    
    # Get stack sizes
    Bundle stacks = stack_size(items);
  12. Difference between int and Signal

    main

    When writing Facto code, it is important to distinguish between compile-time constants and runtime signals:

    • int: A compile-time constant. It does not create a combinator in Factorio; it is simply used as a number in calculations.
    • Signal: A runtime Factorio signal. It creates a constant combinator (or other logic) that outputs a value in the game world.