Files
grapho/docs/LUX-LIMITATIONS.md
Brandon Lucas 117e6af528 Implement self-contained grapho architecture with four data types
Major rewrite of grapho CLI to support:
- Type 1 (Config): grapho init <repo-url> clones NixOS config
- Type 2 (Sync): Isolated Syncthing on port 8385 (separate from system)
- Type 3 (Backup): Restic integration with systemd timer
- Type 4 (Server): Mount point for central server data

New features:
- Welcome flow on first run (detects ~/.config/grapho/grapho.toml)
- grapho setup wizard creates directory structure
- grapho sync/backup/server subcommands
- grapho status shows all four data types
- grapho doctor checks system health

Added modules/grapho.nix NixOS module:
- Configures isolated Syncthing (ports 8385, 22001, 21028)
- Sets up grapho-backup systemd service and timer
- Creates directory structure via tmpfiles
- Optional NFS server mount

Updated flake.nix:
- Export grapho NixOS module
- Add grapho CLI package (nix build .#grapho)

Documented additional Lux language limitations:
- String == comparison broken in C backend
- let _ = pattern not supported
- List literals with recursion cause segfaults

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-16 06:12:58 -05:00

6.5 KiB

Lux Language Limitations (grapho CLI)

This document tracks limitations encountered while developing the grapho CLI in Lux, to help improve the language.

Fixed Issues

1. Double Execution Bug (FIXED)

Severity: Critical Status: Fixed in Lux c_backend.rs

When using let result = run main() with {} to invoke the main function, the entire program was executing twice.

Root Cause: In c_backend.rs:3878-3907, the generated C code was:

  1. Executing all run expressions (including run main() with {})
  2. Then ALSO calling main_lux() separately because has_main was true

Fix: Added tracking of whether main was already called via a run expression, and skip the separate main_lux() call if so.


String Handling Issues

2. No Escape Sequences in String Literals

Severity: High Status: Confirmed

Lux does not support backslash escape sequences like \", \n, \t in string literals.

// This FAILS - backslash causes parse error
Console.print("Hello \"World\"")  // ERROR: Unexpected character: '\'

// This FAILS
Console.print("Line1\nLine2")  // ERROR: Unexpected character: '\'

Impact: Cannot include quotes in strings, cannot create multi-line strings, cannot output JSON with proper formatting.

Workaround:

  • Use shell commands via Process.exec to generate quoted output
  • Use String.fromChar('"') for quotes (but this had issues too)
  • For JSON output, use key=value format instead

3. Dollar Sign in Strings Causes Parse Error

Severity: Medium Status: Confirmed

The $ character in strings triggers the string interpolation lexer, even inside shell command strings.

// This FAILS
execQuiet("jq -n --arg x '$foo' ...")  // ERROR: Unexpected character: '$'

Impact: Cannot use shell variable syntax or jq arguments in command strings.

Workaround: Avoid $ in strings, or construct commands differently.

4. String.fromChar Returns Int, Not String

Severity: Medium Status: Bug

String.fromChar('"') appears to return an Int instead of a String, causing C compilation errors.

let q = String.fromChar('"')  // Compiles but C code is wrong
Console.print(q + "hello")    // C error: int + string

Impact: Cannot use character literals to build strings.

Workaround: Use execQuiet("printf '%s' '\"'") to get a quote character.


Type System Issues

5. Record Type Definitions Don't Work as Expected

Severity: Medium Status: Needs Investigation

Defining a record type and then creating values of that type doesn't work:

type ComponentStatus = {
    name: String,
    status: HealthStatus,
    message: String,
    fix: String
}

fn checkNb(): ComponentStatus with {Process} = {
    // ...
    { name: "nb", status: Healthy, message: "ok", fix: "" }
    // ERROR: Cannot unify { name: String, ... } with ComponentStatus
}

Impact: Cannot use structured types for cleaner code organization.

Workaround: Avoid record types, use multiple return values via tuples or restructure code.

6. Int.parse Doesn't Exist or Has Wrong Signature

Severity: Low Status: Confirmed

There's no obvious way to parse a string to an integer.

let count = Int.parse(someString)  // ERROR: Unknown effect operation

Impact: Cannot convert string output from shell commands to numbers.

Workaround: Keep numbers as strings, use shell for numeric comparisons.


C Backend Issues

7. String Equality Comparison Generates Incorrect C Code

Severity: High Status: Bug

Using == to compare strings generates C code that compares pointers instead of string contents.

let result = execQuiet("echo yes")
if result == "yes" then ...  // C code: (result == "yes") - pointer comparison!

Impact: String comparisons fail in compiled binaries.

Workaround: Use String.contains for comparison:

fn isYes(s: String): Bool = String.contains(s, "yes")
if result |> isYes then ...

8. String.startsWith Not Available in C Backend

Severity: Medium Status: Bug

String.startsWith works in interpreter but generates undefined function calls in C.

String.startsWith(s, "prefix")  // C error: lux_string__startsWith undefined

Workaround: Use String.contains instead.

9. let _ = expr Pattern Not Supported

Severity: Low Status: Bug

The underscore wildcard pattern for discarding results doesn't work.

let _ = Process.exec("...")  // ERROR: Expected identifier

Workaround: Use a named binding:

let ignore = Process.exec("...")

10. List Literals and Recursion Cause Segfaults

Severity: High Status: Bug

Combining list literals with recursive functions can cause segmentation faults in compiled binaries while working fine in interpreter.

// This crashes when compiled:
let dirs = ["a", "b", "c"]
fn processDirs(dirs: List<String>): Unit =
    match List.head(dirs) {
        Some(d) => { ...; match List.tail(dirs) { Some(rest) => processDirs(rest), ... } }
        None => ()
    }

Workaround: Avoid list literals with recursive processing. Inline the operations:

fn processA(): Unit = ...
fn processB(): Unit = ...
fn processC(): Unit = ...
// Call each individually

Suggestions for Lux

  1. Add escape sequence support - At minimum \", \\, \n, \t
  2. Fix String.fromChar to return String, not Int
  3. Add raw string literals - Something like r"..." or '''...''' for shell commands
  4. Fix the double execution bug in the runtime (DONE)
  5. Support record type literals matching their declared type
  6. Add Int.parse and Float.parse for string-to-number conversion
  7. Consider a heredoc syntax for multi-line strings with special characters
  8. Fix string equality - Use strcmp in C backend for string ==
  9. Support let _ = - Allow underscore as discard binding
  10. Fix String.startsWith in C backend
  11. Fix list literals with recursion causing segfaults

Current Workarounds in grapho CLI

  1. Double output: FIXED in Lux c_backend.rs
  2. JSON output: Using key=value format instead of proper JSON
  3. Quotes in output: Avoided entirely or generated via shell
  4. Structured types: Using individual variables instead of records
  5. Numeric parsing: Keeping counts as strings throughout
  6. String comparison: Using String.contains with helper functions instead of ==
  7. Discarding results: Using let ignore = ... instead of let _ = ...
  8. Lists with recursion: Replaced with individual function calls