Files
lux/projects/blu-site/ISSUES.md
Brandon Lucas bac63bab2a feat: add blu-site static site generator and fix language issues
Build a complete static site generator in Lux that faithfully clones
blu.cx (elmstatic). Generates 14 post pages, section indexes, tag pages,
and a home page with snippets grid from markdown content.

Language fixes discovered during development:
- Add \{ and \} escape sequences in string literals (lexer)
- Register String.indexOf and String.lastIndexOf in type checker
- Fix formatter to preserve brace escapes in string literals
- Improve LSP hover to show documentation for let bindings and functions

ISSUES.md documents 15 Lux language limitations found during the project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 15:43:05 -05:00

7.7 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 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).

Reproduction:

fn readFile(path: String): String with {File} = File.read(path)
let contents = List.map(paths, fn(p: String): String => readFile(p));
// ERROR: Effect mismatch: expected {File}, got {}

Workaround: Use manual recursion instead of List.map/forEach:

fn mapRead(paths: List<String>): List<String> with {File} =
    match List.head(paths) {
        None => [],
        Some(p) => {
            let content = readFile(p);
            match List.tail(paths) {
                Some(rest) => List.concat([content], mapRead(rest)),
                None => [content]
            }
        }
    }

Issue 8: String.indexOf and String.lastIndexOf missing from type checker

Category: Type checker gap → Fixed Severity: Medium

These functions exist in the interpreter but were not registered in the type checker, causing "Module 'String' has no member" errors.

Fix: Added type registrations in src/types.rs:

  • String.indexOf(String, String) -> Option<Int>
  • String.lastIndexOf(String, String) -> Option<Int>

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 Severity: Low

Must shell out to copy files/directories.

Workaround: Process.exec("cp -r static/* _site/").


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 Open
8 String.indexOf/lastIndexOf types 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 Open
15 No file globbing Low Open