7.9 KiB
Lux Language Issues — blu-site SSG Project
Issues discovered while building a static site generator in Lux. Each entry includes a reproduction case and the workaround used.
Issue 1: Html.render() / Html.document() not available in interpreter
Category: Missing feature Severity: High
The Html module only works in the JS backend, not the interpreter. All HTML must be generated via string concatenation.
Workaround: Build all HTML as concatenated strings.
Issue 2: String interpolation {} has no escape mechanism
Category: Missing feature → Fixed (added \{ and \} escape sequences)
Severity: High
Any { inside a string literal triggers interpolation mode, and there was no way to include literal braces. This breaks when generating JavaScript like function() { ... } or JSON like {}.
Reproduction:
let s = "function() { return 1; }" // Parse error: treats { } as interpolation
Fix: Added \{ and \} as escape sequences in the lexer (src/lexer.rs). Also fixed the formatter (src/formatter.rs) to re-escape { and } in string literals so that lux fmt doesn't break escaped braces. Now:
let s = "function() \{ return 1; \}" // Works, produces literal braces
Issue 3: Module-qualified constructors not supported in pattern matching
Category: Parser limitation Severity: High
Cannot use module.Constructor in match patterns. The parser expects => but finds ..
Reproduction:
import frontmatter
let result = frontmatter.parse(content);
match result {
frontmatter.ParseResult(front, body) => ... // ERROR: Expected =>, found .
}
Workaround: Consolidated everything into a single file. Alternatively, use import foo.* (wildcard imports), though this can cause name conflicts.
Issue 4: Tuple field access (.0, .1) not supported
Category: Missing feature Severity: High
The parser's field access only works on Records (with named fields), not tuples. The . operator expects an identifier.
Reproduction:
let pair = ("hello", 42);
let x = pair.0; // ERROR: Expected identifier
Workaround: Use ADTs (algebraic data types) with accessor functions:
type Pair = | Pair(String, Int)
fn first(p: Pair): String = match p { Pair(a, _) => a }
Issue 5: Multi-line function arguments cause parse errors
Category: Parser limitation Severity: High
Function call arguments cannot span multiple lines. The parser sees \n where it expects , or ).
Reproduction:
let result = someFunction(
arg1,
arg2
); // ERROR: Expected ,, found \n
Workaround: Keep all function arguments on a single line, or use let bindings for intermediate values:
let a = arg1;
let b = arg2;
let result = someFunction(a, b);
Issue 6: Multi-line lambdas in function call arguments fail
Category: Parser limitation Severity: High
Lambda bodies that span multiple lines inside function call arguments cause parse errors.
Reproduction:
List.map(items, fn(x: Int): Int =>
x + 1 // ERROR: Expected ,, found \n
);
Workaround: Either keep lambdas on one line or extract to named functions:
fn addOne(x: Int): Int = x + 1
List.map(items, addOne)
Issue 7: Effectful callbacks in List.map/forEach/fold
Category: Type checker limitation → Fixed Severity: Medium
The type checker requires pure callbacks for higher-order List functions, but effectful callbacks are often needed (e.g., reading files in a map operation).
Fix: Added effect propagation in src/typechecker.rs: callback arguments with effect annotations now propagate their effects to the enclosing function's inferred effect set, in infer_call, infer_effect_op (module access path), and infer_effect_op (effect op path).
Issue 8: String.indexOf and String.lastIndexOf broken in C backend
Category: C backend bug → Fixed Severity: Medium
Type registrations were added previously, but C compilation of code using String.indexOf/lastIndexOf failed with type errors. Three root causes:
- Global
letbindings always declared asstatic LuxIntregardless of value type Option<Int>inner type not tracked through function parameters, causing match extraction to default toLuxStringindexOf/lastIndexOfstored ints as(void*)(intptr_t)but extraction expected boxed pointers (inconsistent withparseInt)
Fix: Fixed in src/codegen/c_backend.rs:
emit_global_letnow infers type from value expression- Added
var_option_inner_typesmap; function params withOption<T>annotations are tracked indexOf/lastIndexOfnow uselux_box_intconsistently; extraction dereferences via*(LuxInt*)
Issue 9: No List.sort / List.sortBy
Category: Missing feature Severity: Medium
No built-in sorting for lists. Must implement manually.
Workaround: Insertion sort via List.fold:
fn sortByDateDesc(items: List<Page>): List<Page> =
List.fold(items, [], sortInsert)
fn insertByDate(sorted: List<Page>, item: Page): List<Page> = {
match List.head(sorted) {
None => [item],
Some(first) =>
if pgDate(item) >= pgDate(first) then
List.concat([item], sorted)
else
match List.tail(sorted) {
Some(rest) => List.concat([first], insertByDate(rest, item)),
None => [first, item]
}
}
}
Issue 10: No HashMap/Map type
Category: Missing feature Severity: Medium
No associative data structure available. Tag grouping required manual list scanning.
Workaround: Use List<(key, value)> with List.filter/List.find for lookups. O(n) per lookup.
Issue 11: No regex support
Category: Missing feature Severity: Medium
Inline markdown parsing (bold, italic, links, code) required manual character-by-character scanning with index tracking.
Workaround: Recursive processInlineFrom(text, i, len, acc) function scanning character by character.
Issue 12: No multiline string literals
Category: Missing feature Severity: Medium
HTML templates require very long single-line strings with many + concatenations and \" escapes.
Workaround: Concatenate multiple single-line strings with +.
Issue 13: Json.parse returns Result<Json, String>
Category: Documentation gap Severity: Low
Not immediately obvious that Json.parse wraps the result in Ok/Err. Error message "Json.get expects Json, got Constructor" is confusing.
Workaround: Pattern match on Ok(j) / Err(_):
let json = match Json.parse(raw) { Ok(j) => j, Err(_) => ... };
Issue 14: No File.copy
Category: Missing feature → Fixed Severity: Low
Fix: Added File.copy(source, dest) to types.rs, interpreter.rs (using std::fs::copy), and C backend (fread/fwrite buffer copy).
Issue 15: No file globbing
Category: Missing feature Severity: Low
Must manually scan directories and filter by extension.
Workaround: File.readDir(dir) + List.filter(entries, fn(e) => String.endsWith(e, ".md")).
Summary
| # | Issue | Severity | Status |
|---|---|---|---|
| 1 | Html module interpreter-only | High | Open |
| 2 | No \{ \} string escapes |
High | Fixed |
| 3 | Module-qualified pattern matching | High | Open |
| 4 | Tuple field access .0 .1 |
High | Open |
| 5 | Multi-line function arguments | High | Open |
| 6 | Multi-line lambdas in calls | High | Open |
| 7 | Effectful callbacks in List HOFs | Medium | Fixed |
| 8 | String.indexOf/lastIndexOf C backend | Medium | Fixed |
| 9 | No List.sort | Medium | Open |
| 10 | No HashMap/Map | Medium | Open |
| 11 | No regex | Medium | Open |
| 12 | No multiline strings | Medium | Open |
| 13 | Json.parse return type docs | Low | Open |
| 14 | No File.copy | Low | Fixed |
| 15 | No file globbing | Low | Open |