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>
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:
- Executing all
runexpressions (includingrun main() with {}) - Then ALSO calling
main_lux()separately becausehas_mainwas 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.execto 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
- Add escape sequence support - At minimum
\",\\,\n,\t - Fix String.fromChar to return String, not Int
- Add raw string literals - Something like
r"..."or'''...'''for shell commands - Fix the double execution bug in the runtime (DONE)
- Support record type literals matching their declared type
- Add Int.parse and Float.parse for string-to-number conversion
- Consider a heredoc syntax for multi-line strings with special characters
- Fix string equality - Use strcmp in C backend for string ==
- Support
let _ =- Allow underscore as discard binding - Fix String.startsWith in C backend
- Fix list literals with recursion causing segfaults
Current Workarounds in grapho CLI
- Double output: FIXED in Lux c_backend.rs
- JSON output: Using key=value format instead of proper JSON
- Quotes in output: Avoided entirely or generated via shell
- Structured types: Using individual variables instead of records
- Numeric parsing: Keeping counts as strings throughout
- String comparison: Using
String.containswith helper functions instead of== - Discarding results: Using
let ignore = ...instead oflet _ = ... - Lists with recursion: Replaced with individual function calls