Skip to content

Instantly share code, notes, and snippets.

@raspberrypisig
Created December 8, 2025 07:50
Show Gist options
  • Select an option

  • Save raspberrypisig/ca86af23bea7cf39e7b40ffcbbe4c724 to your computer and use it in GitHub Desktop.

Select an option

Save raspberrypisig/ca86af23bea7cf39e7b40ffcbbe4c724 to your computer and use it in GitHub Desktop.

Based on the provided codebase, here are the architectural shortcomings of espforge. These issues range from rigidity in the hardware abstraction to fragility in the code generation logic.

1. Fragile "Transpilation" and String Manipulation

The bridge between the custom logic language ("Ruchy") and Rust is brittle. While it uses an AST parser (ruchy), the conversion to Rust code relies on post-processing string manipulation rather than semantic generation.

  • Evidence: In espforge/resolver/ruchy_bridge.rs, the format_rust_code function cleans up token streams using replacements like .replace(" . ", ".") and .replace(" :: ", "::").
  • Impact: This is prone to generating invalid syntax for complex expressions. It creates a disconnect where a user might write valid "Ruchy" code, but the generated Rust code fails to compile with obscure errors that don't map back to the source lines in the .ruchy file.

2. Hardcoded Hardware Resources & Blocking I/O

The platform abstraction layer is heavily hardcoded and lacks flexibility, limiting the tool to simple use cases.

  • Hardcoded Peripherals:
    • SPI: espforge/platform/spi.rs explicitly steals SPI2. If a chip variant uses SPI0 or SPI1 as the primary user bus, or if a user needs multiple SPI buses, this architecture fails.
    • I2C: espforge/components/i2c.rs and platform/i2c.rs assume I2C0 and hardcode the frequency to 100kHz. Users cannot configure 400kHz or standard/fast modes via the YAML.
  • Blocking Only: The platform wrappers (espforge/platform/mod.rs) explicitly import and use esp_hal::Blocking.
    • Impact: This makes espforge unsuitable for applications requiring Wi-Fi, BLE, or efficient multi-tasking, which generally require async (Embassy) support in the Rust ecosystem.

3. "Unsafe" Resource Management

The generated code relies heavily on unsafe stealing of peripherals, bypassing Rust's ownership safety guarantees at the HAL level.

  • Evidence: Files like espforge/platform/gpio.rs, i2c.rs, and spi.rs all use unsafe { AnyPin::steal(pin_number) } or unsafe { I2C0::steal() }.
  • Impact:
    • While the HardwareNibbler (espforge/nibblers/esp32.rs) attempts to validate pin ranges, it doesn't appear to robustly track exclusive usage across different component types (e.g., ensuring an I2C pin isn't also used as a generic GPIO output in the same config).
    • The generated Rust code panics or causes undefined behavior at runtime if resources conflict, rather than catching this at compile time.

4. Lack of Component Extensibility

The component system is "closed." New components cannot be added without recompiling the espforge binary.

  • Evidence: Component manifests (.ron files) and their Rust implementations are compiled into the binary via include_dir! in espforge/generate/mod.rs and espforge/build.rs.
  • Impact: Users cannot define local custom components in their project folder (e.g., a custom sensor driver). They are limited strictly to the components (Button, LED, I2C, SPI) shipped with the specific version of the CLI.

5. Monolithic main.rs Generation

The code generation strategy creates a "God function" inside main.rs.

  • Evidence: espforge/resolver/mod.rs aggregates all setup and loop logic into Vec<String> buffers (setup_code, loop_code) which are dumped directly into the main function in main.rs.tera.
  • Impact:
    • Scope Pollution: All variables are declared in the same scope. A variable defined in a logic block could accidentally shadow or conflict with internal variables generated by espforge.
    • Maintainability: The generated code is hard to read or debug for the end-user because it lacks modular structure (functions, structs, modules) for the user's logic.

6. Dependency on External CLI Tools

The tool is not self-contained and relies on shelling out to other processes.

  • Evidence: espforge/generate/mod.rs executes Command::new("esp-generate").
  • Impact: The user must have esp-generate installed and in their PATH. Version mismatches between espforge expectations and the installed esp-generate version could break the scaffolding process (e.g., if esp-generate changes its arguments or template structure).

7. Limited Logic/Control Flow in YAML

The YAML-based logic engine (espforge/resolver/actions/logic.rs) is an awkward DSL implemented via JSON/YAML structure.

  • Evidence: The IfActionStrategy manually constructs Rust if statements from YAML maps (condition, then).
  • Impact: Writing complex logic (nested loops, state machines, elaborate conditionals) in YAML is verbose and error-prone. This forces users to use "Ruchy" or raw Rust, but the integration between the YAML config variables and Ruchy code is loosely coupled (string matching).

8. Manual TOML Merging

The mechanism for merging dependencies is simplistic.

  • Evidence: espforge_templates/src/lib.rs contains manual logic to parse Cargo.toml and merge dependencies and dev-dependencies.
  • Impact: This parser is unlikely to handle complex Cargo.toml features correctly, such as workspace inheritance, platform-specific dependencies ([target.'cfg(...)'.dependencies]), or patch sections. It risks corrupting the build configuration of complex templates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment