Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/prompts/lex_lang.lex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# `lex_stdlib` already made for stdlib signatures.

fn reference() -> Str {
"## Lex Language — Core Reference\n\nLex is a typed-effect functional language. Files use `.lex`; the default toolchain is `lex`.\n\n### Syntax quick-start\n\n```lex\n# Function with explicit types (all signatures are explicit)\nfn add(x :: Int, y :: Int) -> Int\n examples {\n add(0, 0) => 0,\n add(2, 3) => 5,\n }\n{ x + y }\n\n# Effectful function — effects come BEFORE the return type\nfn save(path :: Str, data :: Str) -> [fs_write] Result[Unit, Str] {\n fs.write(path, data)\n}\n\n# Pure function — NO effect annotation at all\nfn double(n :: Int) -> Int { n * 2 }\n\n# Let binding — uses `:=`, not `=`\nlet result := add(1, 2)\n\n# Algebraic data type\ntype Shape = Circle(Float) | Rect { w :: Float, h :: Float }\n\n# Match (must be exhaustive)\nmatch shape {\n Circle(r) => 3.14159 * r * r,\n Rect { w, h } => w * h,\n}\n\n# Result / Option idioms\nmatch http.get(url) {\n Ok(resp) => resp.body,\n Err(e) => str.concat(\"error: \", e),\n}\n\n# Import\nimport \"std.list\" as list\nimport \"./util\" as util\n```\n\n**No `else if`** — the single most common mistake. Lex only has `if/else`; nest: `else { if cond { ... } else { ... } }`. Full pitfalls table: `lex_guide(\"pitfalls\")`.\n\n### Effect discipline (most important rule)\n- Declare the **narrowest** effect set that matches the body.\n- Pure functions: no effect annotation — `fn f(x :: T) -> T`.\n- Never add `[io]` \"just in case\"; the checker will reject unused effects.\n- Common effects: `io`, `net`, `llm`, `sql`, `fs_read`, `fs_write`, `env`, `proc`, `time`, `concurrent`.\n- Fine-grained path scopes: `[fs_write(\"/tmp/out/\")]`, `[net(\"api.example.com\")]`.\n\n### examples {} blocks\nPure functions SHOULD have an `examples {}` block — they're folded into the SigId and become free regression tests:\n```lex\nfn clamp(n :: Int, lo :: Int, hi :: Int) -> Int\n examples {\n clamp(5, 0, 10) => 5,\n clamp(-1, 0, 10) => 0,\n clamp(99, 0, 10) => 10,\n }\n{ if n < lo { lo } else { if n > hi { hi } else { n } } }\n```\n\n### Stdlib modules\nlist, str, io, http, sql, fs, env, conc, json, regex, int, float, bytes, crypto, and more — call `lex_stdlib(module)` (e.g. `lex_stdlib(\"bytes\")`) for a module's real function names and signatures, straight from the compiler's own builtin registry. Don't guess a stdlib call's name or return type, and don't probe it with a throwaway file — ask first.\n\n### Iteration loop\n1. Write or edit the `.lex` file — the `write` tool auto-runs `lex check` and returns an error if it fails.\n2. **If `write` returns an error, fix the code and call `write` again immediately.** Keep iterating until write succeeds. Never stop after a write error.\n3. Read the structured error (`rule_tag` + `rule_explanation`) to understand what to fix.\n4. `lex_run fn_name` — run a specific function.\n5. `lex_test` — run the test suite.\n\n### Decide once, then write code\nWhen a spec is ambiguous or a design choice has multiple valid answers, reason about it *once*, pick the interpretation that's consistent with any concrete examples/tests you were given, and move straight to `write`. Do not re-derive or re-verify the same conclusion more than once — if you notice yourself writing \"let me reconsider\" or \"hold on\" a second time about the same question, stop: you already decided, go call `write`. `lex check`'s error messages are the cheap way to find out you were wrong; a long chain of self-doubt in your own response is not — it only spends your output budget without writing anything.\n\nMore detail on demand, never by guessing or probing: `lex_guide(topic)` for \"pitfalls\", \"list-idioms\", or \"anti-patterns\"; `lex_stdlib(module)` for stdlib signatures; `lex_cli_help(command)` for a `lex` CLI subcommand's real arguments and examples. For everything else: call `load_guidelines` tool or run `lex agent-guidelines`."
"## Lex Language — Core Reference\n\nLex is a typed-effect functional language. Files use `.lex`; the default toolchain is `lex`.\n\n### Syntax quick-start\n\n```lex\n# Function with explicit types (all signatures are explicit)\nfn add(x :: Int, y :: Int) -> Int\n examples {\n add(0, 0) => 0,\n add(2, 3) => 5,\n }\n{ x + y }\n\n# Effectful function — effects come BEFORE the return type\nfn save(path :: Str, data :: Str) -> [fs_write] Result[Unit, Str] {\n fs.write(path, data)\n}\n\n# Pure function — NO effect annotation at all\nfn double(n :: Int) -> Int { n * 2 }\n\n# Let binding — uses `:=`, not `=`\nlet result := add(1, 2)\n\n# Algebraic data type — a record-shaped variant's payload is a record\n# type, written like any other single argument: in parens. There is no\n# bare `Rect { ... }` form — every variant is VariantName(payload).\ntype Shape = Circle(Float) | Rect({ w :: Float, h :: Float })\n\n# Match (must be exhaustive) — same rule: ({ ... }) to destructure the\n# record payload, not a bare { ... } after the variant name.\nmatch shape {\n Circle(r) => 3.14159 * r * r,\n Rect({ w, h }) => w * h,\n}\n\n# Records: construct with a BARE literal, never the type name.\n# `Company { name: \"x\" }` does not parse — write `{ name: \"x\" }` and let\n# the return type / annotation tell the checker which record type it is.\ntype Company = { name :: Str, mission :: Str }\nfn incorporate(name :: Str, mission :: Str) -> Company { { name: name, mission: mission } }\n\n# Result / Option idioms\nmatch http.get(url) {\n Ok(resp) => resp.body,\n Err(e) => str.concat(\"error: \", e),\n}\n\n# Import\nimport \"std.list\" as list\nimport \"./util\" as util\n```\n\n**No `else if`** — the single most common mistake. Lex only has `if/else`; nest: `else { if cond { ... } else { ... } }`. Full pitfalls table: `lex_guide(\"pitfalls\")`.\n\n### Effect discipline (most important rule)\n- Declare the **narrowest** effect set that matches the body.\n- Pure functions: no effect annotation — `fn f(x :: T) -> T`.\n- Never add `[io]` \"just in case\"; the checker will reject unused effects.\n- Common effects: `io`, `net`, `llm`, `sql`, `fs_read`, `fs_write`, `env`, `proc`, `time`, `concurrent`.\n- Fine-grained path scopes: `[fs_write(\"/tmp/out/\")]`, `[net(\"api.example.com\")]`.\n\n### examples {} blocks\nPure functions SHOULD have an `examples {}` block — they're folded into the SigId and become free regression tests:\n```lex\nfn clamp(n :: Int, lo :: Int, hi :: Int) -> Int\n examples {\n clamp(5, 0, 10) => 5,\n clamp(-1, 0, 10) => 0,\n clamp(99, 0, 10) => 10,\n }\n{ if n < lo { lo } else { if n > hi { hi } else { n } } }\n```\n\n### Stdlib modules\nlist, str, io, http, sql, fs, env, conc, json, regex, int, float, bytes, crypto, and more — call `lex_stdlib(module)` (e.g. `lex_stdlib(\"bytes\")`) for a module's real function names and signatures, straight from the compiler's own builtin registry. Don't guess a stdlib call's name or return type, and don't probe it with a throwaway file — ask first.\n\n### Iteration loop\n1. Write or edit the `.lex` file — the `write` tool auto-runs `lex check` and returns an error if it fails.\n2. **If `write` returns an error, fix the code and call `write` again immediately.** Keep iterating until write succeeds. Never stop after a write error.\n3. Read the structured error (`rule_tag` + `rule_explanation`) to understand what to fix.\n4. `lex_run fn_name` — run a specific function.\n5. `lex_test` — run the test suite.\n\n### Decide once, then write code\nWhen a spec is ambiguous or a design choice has multiple valid answers, reason about it *once*, pick the interpretation that's consistent with any concrete examples/tests you were given, and move straight to `write`. Do not re-derive or re-verify the same conclusion more than once — if you notice yourself writing \"let me reconsider\" or \"hold on\" a second time about the same question, stop: you already decided, go call `write`. `lex check`'s error messages are the cheap way to find out you were wrong; a long chain of self-doubt in your own response is not — it only spends your output budget without writing anything.\n\nMore detail on demand, never by guessing or probing: `lex_guide(topic)` for \"pitfalls\", \"list-idioms\", or \"anti-patterns\"; `lex_stdlib(module)` for stdlib signatures; `lex_cli_help(command)` for a `lex` CLI subcommand's real arguments and examples. For everything else: call `load_guidelines` tool or run `lex agent-guidelines`."
}

fn topic_names() -> List[Str] {
Expand All @@ -34,7 +34,7 @@ fn topic(name :: Str) -> Option[Str] {
}

fn pitfalls_topic() -> Str {
"### Common syntax pitfalls\n| What you might write | Correct Lex |\n|---|---|\n| `x: Int` | `x :: Int` |\n| `let x = e` | `let x := e` |\n| `-> T` with no effects | `-> T` (omit `[]` entirely for pure fns) |\n| `-> [] T` | invalid — write `-> T` |\n| Exceptions / throw | use `Result[T, E]` — no exceptions in Lex |\n| `else if cond { }` | `else { if cond { } }` — **Lex has NO `else if`**. Always nest: `else { if ... { } else { } }` |\n| `True` / `False` | `true` / `false` — booleans are lowercase (unlike Haskell) |\n| `list.concat(a, b, c)` | `list.concat(a, list.concat(b, c))` — concat takes exactly 2 args |\n| `//` comment | `#` comment — Lex uses `#`, not `//` or `/*` |\n| `list.concat(...)` without import | add `import \"std.list\" as list` — every stdlib module must be explicitly imported |\n| `import \"abi\" as abi` / `import \"abi.lex\" as abi` / `import \"src/abi.lex\" as abi` | `import \"./abi\" as abi` (or `\"../src/abi\"` from a sibling dir) — a **local file** needs an explicit `./` or `../` prefix and NO `.lex` extension. A bare or slash-shaped string with no `./`/`../` prefix is looked up as a **package** dependency (from `lex.toml`), not a path, and fails with \"package ... not found\" or \"unknown identifier\" — it is never inferred from a matching filename |\n| `fn f(x) examples{...} match t {` | function body MUST be `{ }` wrapped: `fn f(x) examples{...} { match t { ... } }` |\n| `type T = \\| A \\| B` | NO leading pipe: `type T = A \\| B` — first variant has no `\\|` prefix |\n| `pair.0` / `pair.1` | tuples have no field access — destructure: `match pair { (a, b) => a }` |\n| `while` / `for` loops | Lex is purely functional — use recursion or `list.fold` |\n| `let x := 1` then `let x := 2` | `let` bindings are immutable — cannot reassign; use a recursive helper or fold |\n| `a && b` / `a \\|\\| b` | use `a and b` / `a or b` — Lex uses keywords, not `&&`/`\\|\\|` |\n| `list.find(...)` | does not exist — use `list.filter` + `list.head`, or `list.fold` |\n| `list.zip(a, b)` | does not exist — use `list.enumerate(a)` to get `List[(Int, T)]`, or compare as strings: `str.join(a, \",\") == str.join(b, \",\")` |\n| `str.to_str(n)` | does not exist on `str` — convert Int to Str with `int.to_str(n)` from `std.int` |\n| `fn (kv :: (A, B)) -> T` in a call | tuple type in lambda param causes parse error; match inside body instead: `fn (kv :: (A, B)) -> T { match kv { (a, b) => ... } }` |\n| `match xs { [] => ..., [h, ..t] => ... }` | list pattern matching is NOT supported — use `if list.is_empty(xs) { ... } else { let h := list.head(xs); let t := list.tail(xs); ... }` |\n| `fn helper(...)` inside a function body | local named functions are NOT allowed — extract every helper to the top level |\n| `list.join(parts, sep)` | does not exist — use `str.join(parts, sep)` from `std.str` |\n| `VBool(bool)` / `x :: bool` | type names are uppercase: `Bool`, `Int`, `Str`, `Float` — never `bool`, `int`, `str` |\n| `let a := x,` (comma after let) | `let` bindings inside a block need NO separator — just newlines. Commas go between match arms only. |\n\n**No `else if` — this is the single most common mistake.** Lex only has `if/else`. Chain conditions by nesting:\n```lex\n# WRONG — will not parse:\nif a { x } else if b { y } else { z }\n\n# CORRECT — always nest else { if ... }:\nif a { x } else { if b { y } else { z } }\n```"
"### Common syntax pitfalls\n| What you might write | Correct Lex |\n|---|---|\n| `x: Int` | `x :: Int` |\n| `let x = e` | `let x := e` |\n| `-> T` with no effects | `-> T` (omit `[]` entirely for pure fns) |\n| `-> [] T` | invalid — write `-> T` |\n| Exceptions / throw | use `Result[T, E]` — no exceptions in Lex |\n| `else if cond { }` | `else { if cond { } }` — **Lex has NO `else if`**. Always nest: `else { if ... { } else { } }` |\n| `True` / `False` | `true` / `false` — booleans are lowercase (unlike Haskell) |\n| `list.concat(a, b, c)` | `list.concat(a, list.concat(b, c))` — concat takes exactly 2 args |\n| `//` comment | `#` comment — Lex uses `#`, not `//` or `/*` |\n| `list.concat(...)` without import | add `import \"std.list\" as list` — every stdlib module must be explicitly imported |\n| `import \"abi\" as abi` / `import \"abi.lex\" as abi` / `import \"src/abi.lex\" as abi` | `import \"./abi\" as abi` (or `\"../src/abi\"` from a sibling dir) — a **local file** needs an explicit `./` or `../` prefix and NO `.lex` extension. A bare or slash-shaped string with no `./`/`../` prefix is looked up as a **package** dependency (from `lex.toml`), not a path, and fails with \"package ... not found\" or \"unknown identifier\" — it is never inferred from a matching filename |\n| `fn f(x) examples{...} match t {` | function body MUST be `{ }` wrapped: `fn f(x) examples{...} { match t { ... } }` |\n| `type T = \\| A \\| B` | NO leading pipe: `type T = A \\| B` — first variant has no `\\|` prefix |\n| `pair.0` / `pair.1` | tuples have no field access — destructure: `match pair { (a, b) => a }` |\n| `{ field = value }` | record fields are separated by `:`, not `=` — `{ field: value }` |\n| `TypeName { field: value }` (e.g. `Company { name: \"x\" }`) | records are constructed as a **bare** literal, `{ field: value, ... }` — the type name is NEVER written at construction, only in a type annotation or return type. `TypeName { ... }` does not parse as record construction (it is read as an ADT variant lookup and fails, often with a confusing \"unknown_variant\" or \"expected RParen\" error) |\n| `Rect { w: 1.0, h: 2.0 }` as an ADT variant (e.g. `type Shape = Circle(Float) \\| Rect { w :: Float, h :: Float }`) | a record-shaped variant's payload still needs parens like any other variant argument: declare `Rect({ w :: Float, h :: Float })`, construct `Rect({ w: 1.0, h: 2.0 })`, match `Rect({ w, h }) => ...` — there is no bare `Rect { ... }` form for a variant either |\n| `while` / `for` loops | Lex is purely functional — use recursion or `list.fold` |\n| `let x := 1` then `let x := 2` | `let` bindings are immutable — cannot reassign; use a recursive helper or fold |\n| `a && b` / `a \\|\\| b` | use `a and b` / `a or b` — Lex uses keywords, not `&&`/`\\|\\|` |\n| `list.find(...)` | does not exist — use `list.filter` + `list.head`, or `list.fold` |\n| `list.zip(a, b)` | does not exist — use `list.enumerate(a)` to get `List[(Int, T)]`, or compare as strings: `str.join(a, \",\") == str.join(b, \",\")` |\n| `str.to_str(n)` | does not exist on `str` — convert Int to Str with `int.to_str(n)` from `std.int` |\n| `fn (kv :: (A, B)) -> T` in a call | tuple type in lambda param causes parse error; match inside body instead: `fn (kv :: (A, B)) -> T { match kv { (a, b) => ... } }` |\n| `match xs { [] => ..., [h, ..t] => ... }` | list pattern matching is NOT supported — use `if list.is_empty(xs) { ... } else { let h := list.head(xs); let t := list.tail(xs); ... }` |\n| `fn helper(...)` inside a function body | local named functions are NOT allowed — extract every helper to the top level |\n| `list.join(parts, sep)` | does not exist — use `str.join(parts, sep)` from `std.str` |\n| `VBool(bool)` / `x :: bool` | type names are uppercase: `Bool`, `Int`, `Str`, `Float` — never `bool`, `int`, `str` |\n| `let a := x,` (comma after let) | `let` bindings inside a block need NO separator — just newlines. Commas go between match arms only. |\n\n**No `else if` — this is the single most common mistake.** Lex only has `if/else`. Chain conditions by nesting:\n```lex\n# WRONG — will not parse:\nif a { x } else if b { y } else { z }\n\n# CORRECT — always nest else { if ... }:\nif a { x } else { if b { y } else { z } }\n```"
}

fn list_idioms_topic() -> Str {
Expand Down
Loading