# Jade — The AI-native programming language > Full documentation for the Jade programming language, as raw markdown, > concatenated into one page for LLM ingestion. > Canonical site: https://www.jadelang.org ## Contents 1. Jade Documentation 2. Quick Start 3. Variables 4. Types 5. Expressions 6. Operators 7. Control Flow 8. Functions 9. Async / Await 10. Structs 11. Imports 12. Exceptions 13. LLM Integration 14. Standard Library 15. CLI Reference 16. Changelog --- # Jade Documentation Jade is a programming language written in Rust. It supports value types (`int`, `float`, `bool`, `str`, arrays, dicts, and user-defined `struct`s), `let` variable bindings, `fn` function definitions with `return`, anonymous closures (`|x| x * 2`), first-class functions, recursion, `if`/`elif`/`else` control flow, `while` loops, `for` loops over arrays, `try`/`catch`/`raise` exception handling, `struct` definitions with field access and mutation, `extend` blocks for methods, `interface` definitions, multi-file `use` imports, `print` and `len` built-in functions, f-string interpolation, the pipe operator (`|>`), `prompt` declarations and LLM inference via `?`, and a full operator set. The standard library covers math, string manipulation, file I/O, HTTP, JSON, shell commands, environment variables, path utilities, and random number generation — each available via `::` notation imports such as `use std::math`. ## Installation ### macOS and Linux (recommended) The fastest way to install Jade is with the official install script. Open a terminal and run: ```bash curl -fsSL https://jadelang.org/install.sh | sh ``` The script detects your OS and architecture, downloads the correct prebuilt binary from the [latest release](https://github.com/joericks1998/jade/releases/latest), and installs it to `/usr/local/bin/jade`. To install to a different location, set the `JADE_INSTALL_DIR` environment variable before running the script: ```bash JADE_INSTALL_DIR=~/.local/bin curl -fsSL https://jadelang.org/install.sh | sh ``` ### Windows Download `jade-windows-x86_64.exe` from the [latest release](https://github.com/joericks1998/jade/releases/latest), rename it to `jade.exe`, and place it somewhere on your `PATH`. ### Build from Source Requires Rust 1.70 or later — install via [rustup.rs](https://rustup.rs). ```bash git clone https://github.com/joericks1998/jade cd jade cargo build --release cp target/release/jade /usr/local/bin/jade ``` ### Verify ```bash jade --help ``` This prints the list of available subcommands. See the [CLI Reference](cli) for full details. --- # Quick Start Create a file named `hello.jde`: ```jade let x = 10 let y = 32 let answer = x + y print(answer) ``` Run it: ```bash jade hello.jde ``` Output: ``` 42 ``` You can also use `--verbose` to print every variable's final value after execution: ```bash jade hello.jde --verbose ``` Output: ``` answer = 42 x = 10 y = 32 ``` :::note Variables are printed in alphabetical order when using `--verbose`. The `print` built-in function prints a single value to stdout and can be called anywhere in the program. ::: --- # Variables Variables are declared with the `let` keyword. Every variable must be initialized at declaration. ```jade let x = 42 let y = x + 1 let z = x * y - 10 ``` Variable names must start with a letter or underscore and may contain letters, digits, and underscores. ## Rules - Variables can hold any runtime value: `int`, `float`, `bool`, `str`, arrays, `struct` instances, and `fn` function values. - A variable may be referenced in any expression declared after it. - Referencing an undeclared variable is a runtime error (`UndefinedVariable`). - Semicolons are optional — Jade inserts them automatically at line boundaries. - Variables declared inside a function body are local to that call frame and are not visible outside. --- # Types Jade has nine runtime value types. | Type | Description | Status | |------|-------------|--------| | `int` | 64-bit signed integer (`i64`) | Implemented | | `float` | 64-bit floating point (`f64`) | Implemented | | `bool` | Boolean `true` or `false` | Implemented | | `fn` | First-class function value | Implemented | | `struct` | User-defined record type with named fields | Implemented | | `str` | UTF-8 string with indexing and concatenation | Implemented | | `array` | Heterogeneous mutable array with index access | Implemented | | `dict` | String-keyed mutable hash map | Implemented | | `nil` | Absence of value | Implemented | ## Type Coercion Rules Jade does not implicitly coerce types except in specific arithmetic and comparison contexts: | Rule | Example | Result | |------|---------|--------| | Arithmetic: `int op float` → float | `1 + 0.5` | `1.5` (float) | | Ordering: `int < float` allowed | `1 < 2.5` | `true` | | Strict equality: no cross-type coercion | `1 == 1.0` | `TypeError` | | Bool ordering: `false` = 0, `true` = 1 | `false < true` | `true` | | Bitwise: integer operands only | `1.0 & 2` | `TypeError` | | Logical: bool operands only | `1 && true` | `TypeError` | :::note Arithmetic promotion converts an `int` to `float` when the other operand is a `float`. Equality never promotes — comparing `1` to `1.0` is always a `TypeError`. ::: ## Nil `nil` is Jade's single "absence of value" type. A function that reaches the end of its body without returning, a bare `return`, and most mutating built-ins (`arr.push(x)`, `fs.write(...)`) all evaluate to `nil`. ### Three spellings `nil`, `None`, and `null` are interchangeable spellings of the **same** value. They all evaluate to the one `nil`, compare equal to each other, and can be used anywhere a literal is expected (including default parameter values and type annotations). ```jade let a = nil let b = None let c = null print(a == b) // true print(b == c) // true fn greet(name = null) { // default parameter return name } print(greet()) // nil ``` :::note The three spellings are aliases, not distinct types — there is no separate `null` type. JSON `null` also decodes to this same `nil`. ::: ## Dict A `dict` is a mutable, string-keyed hash map. Keys are strings; values can be any Jade type. Dicts are created with curly-brace literal syntax and accessed with square-bracket indexing. ### Creating a dict ```jade let d = {"name": "jade", "version": 1} let empty = {} ``` Bare identifiers are also accepted as keys — the identifier's string value becomes the key: ```jade let key = "hello" let greet = {key: "world"} print(greet["hello"]) // world ``` ### Reading and writing values ```jade print(d["name"]) // jade d["version"] = 2 // update existing key d["stable"] = true // add new key ``` ### Length ```jade print(len(d)) // number of key-value pairs ``` ### Value semantics Assigning a dict to a new variable copies it. Mutations to the copy do not affect the original: ```jade let d2 = d d2["name"] = "copy" print(d["name"]) // jade (unchanged) print(d2["name"]) // copy ``` ### Nested dicts ```jade let outer = {"inner": {"x": 42}} let inner = outer["inner"] print(inner["x"]) // 42 ``` :::note Dict keys are always strings. Accessing a key that does not exist produces a runtime error. ::: --- # Expressions An expression produces a value. Jade's runtime types are `int` (64-bit signed integer), `float` (64-bit floating point), `bool`, `str` (UTF-8 string), arrays, user-defined `struct`s, and `fn` (function values). Expressions can be nested arbitrarily using parentheses or evaluated left-to-right following standard operator precedence. ## Integer Literals ```jade let a = 0 let b = 1000000 ``` Integer literals are 64-bit signed integers (`i64`). ## Float Literals ```jade let pi = 3.14 let half = 0.5 ``` Float literals require at least one digit on each side of the decimal point. `3.14` is valid; bare `.5` is not. ## Boolean Literals ```jade let yes = true let no = false ``` The keywords `true` and `false` produce `bool` values. ## Identifiers A variable name used in an expression evaluates to the variable's current value. ```jade let base = 8 let doubled = base * 2 ``` ## Parenthesized Expressions Any expression can be wrapped in parentheses to override default precedence: ```jade let a = (2 + 3) * 4 let b = -(3 + 4) ``` ## Function Calls A function value followed by a parenthesized argument list calls that function: ```jade fn add(a, b) { return a + b } let sum = add(3, 4) let nested = add(add(1, 2), 3) ``` See [Functions](functions) for the full reference. ## Binary Expressions Two values combined with an operator: ```jade let sum = 3 + 4 let diff = 10 - 3 let prod = 6 * 7 let quot = 20 / 4 let rem = 10 % 3 let bits = 255 & 15 let mask = 1 << 4 let flag = 1 < 2 && 3 > 0 ``` Expressions associate left-to-right within the same precedence level: ```jade let x = 10 - 3 - 2 ``` This evaluates as `(10 - 3) - 2 = 5`. ## Unary Expressions Jade has three unary prefix operators: - `~` — bitwise NOT (integers only) - `!` — logical NOT (booleans only) - `-` — arithmetic negation (integers and floats) ```jade let inv = ~0 let neg = -5 let nflag = !true ``` ## String Literals String literals may be delimited by double quotes (`"…"`) or single quotes (`'…'`) — both forms are identical. Triple-quoted strings (`"""…"""` or `'''…'''`) span multiple lines. The `+` operator concatenates two strings. ```jade let hello = "hello" let world = 'world' let hw = hello + " " + world let multi = """ line one line two """ let also_multi = ''' line one line two ''' ``` Strings support indexing with `[i]` to extract a single character as a one-character string. Indexes are zero-based. Out-of-range indexes raise `IndexOutOfBounds`. ```jade let s = "hello" let h = s[0] ``` ## F-String Interpolation An f-string is prefixed with `f` before the opening quote. Any expression inside `{ }` is evaluated and its value is converted to a string and embedded in place. Both quote styles are supported. ```jade let name = "Jade" let n = 42 let msg = f"hello, {name}! answer is {n}" let msg2 = f'hello, {name}! answer is {n}' ``` Triple-quoted f-strings are written as `f"""…"""` or `f'''…'''` and behave the same way. ## Array Literals An array is written as a comma-separated list inside square brackets. Arrays may be empty and may hold values of any type. ```jade let a = [1, 2, 3] let empty = [] let mixed = [1, 2.0, true, "hello"] ``` Elements are accessed with `arr[i]` (zero-based). Elements can be assigned with `arr[i] = expr`. Arrays have reference semantics — assigning an array creates an alias that shares the same backing store. ## Pipe Operator The `|>` operator passes the left-hand value as the first argument to the right-hand function. Pipes chain left-to-right. ```jade fn double(x) { return x * 2 } let n = 5 |> double // double(5) → 10 let m = 3 |> double |> double // double(double(3)) → 12 ``` When the right-hand side is a call expression, the left value is inserted as the first argument before those already listed: ```jade fn add(a, b) { return a + b } let r = 5 |> add(3) // add(5, 3) → 8 ``` --- # Operators ## Arithmetic | Operator | Name | Example | Result | |----------|------|---------|--------| | `+` | Addition | `3 + 4` | `7` | | `-` | Subtraction | `10 - 3` | `7` | | `*` | Multiplication | `3 * 4` | `12` | | `/` | Division | `10 / 4` | `2` (int÷int) / `2.5` (float) | | `%` | Remainder (modulus) | `10 % 3` | `1` | | `-` | Unary negation | `-5` | `-5` | ## Bitwise | Operator | Name | Example | Result | |----------|------|---------|--------| | `&` | Bitwise AND | `6 & 3` | `2` | | `\|` | Bitwise OR | `5 \| 2` | `7` | | `^` | Bitwise XOR | `7 ^ 3` | `4` | | `~` | Bitwise NOT (unary) | `~0` | `-1` | | `<<` | Left shift | `1 << 4` | `16` | | `>>` | Right shift | `64 >> 2` | `16` | ## Logical | Operator | Name | Example | Result | |----------|------|---------|--------| | `&&` | Logical AND (short-circuit) | `true && false` | `false` | | `\|\|` | Logical OR (short-circuit) | `false \|\| true` | `true` | | `!` | Logical NOT (unary) | `!true` | `false` | `&&` and `||` are short-circuit: the right operand is not evaluated if the left operand determines the result. Both operands must be `bool`; mixing types produces a `TypeError`. ## Comparison | Operator | Name | Example | Result | |----------|------|---------|--------| | `==` | Equal | `3 == 3` | `true` | | `!=` | Not equal | `3 != 4` | `true` | | `<` | Less than | `1 < 2` | `true` | | `>` | Greater than | `2 > 1` | `true` | | `<=` | Less than or equal | `2 <= 2` | `true` | | `>=` | Greater than or equal | `3 >= 2` | `true` | Equality (`==`, `!=`) requires both operands to be the same type — mixing `int` and `float` produces a `TypeError`. Ordering operators (`<`, `>`, `<=`, `>=`) allow `int` vs `float` comparisons by promoting the integer to float. ## Pipe | Operator | Name | Description | |----------|------|-------------| | `\|>` | Pipe | Pass left-hand value as the first argument to the right-hand function | The pipe operator threads a value through a chain of function calls left-to-right. `x |> f` is equivalent to `f(x)`. When the right-hand side is a call expression with arguments, the piped value is inserted as the *first* argument: `5 |> add(3)` is equivalent to `add(5, 3)`. ```jade fn double(x) { return x * 2 } fn add(a, b) { return a + b } // Simple pipe let n = 5 |> double // 10 // Chained pipes — left-associative let m = 3 |> double |> double // 12 // Pipe with extra arguments (value inserted as first arg) let r = 5 |> add(3) // add(5, 3) = 8 // Pipe to print "hello" |> print // Pipe with arithmetic on the left let x = (2 + 3) |> double // 10 ``` Pipes are left-associative and have lower precedence than all other operators, so the entire expression to the left of `|>` is fully evaluated before being passed to the function on the right. ## Precedence Operators bind from tightest to loosest in this order: 1. Unary: `~`, `!`, `-` 2. Multiplicative: `*`, `/`, `%` 3. Additive: `+`, `-` 4. Shifts: `<<`, `>>` 5. Bitwise AND: `&` 6. Bitwise XOR: `^` 7. Bitwise OR: `|` 8. Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=` 9. Logical AND: `&&` 10. Logical OR: `||` 11. Pipe: `|>` (lowest — entire left expression is the piped value) ```jade let x = 2 + 3 * 4 let y = 1 << 2 & 15 let z = 1 < 2 && 3 > 0 ``` ## Runtime Errors The following operations produce runtime errors: - Division by zero: `x / 0` - Remainder by zero: `x % 0` - Invalid shift amount (negative or ≥ 64): `1 << 64` - Type mismatch: `1 + true`, `1.0 & 2`, `1 == 1.0` --- # Control Flow Jade provides four control flow constructs: `if`/`elif`/`else` for conditional branching, `while` for condition-driven loops, and `for` for iterating over arrays. All conditions must be `bool`. ## Overview The `if` statement tests a condition expression and executes the *then* block when the condition is `true`. An optional `else` block executes when the condition is `false`. If no `else` is provided and the condition is `false`, the statement does nothing. The `while` statement repeatedly evaluates its condition and executes its body as long as the condition remains `true`. When the condition becomes `false`, execution continues with the statement after the closing `}`. If the condition is `false` on the first check, the body never executes. Both constructs require the condition to evaluate to a `bool` value. Providing any other type — an `int`, `float`, or `fn` value — raises a `TypeError`. There is no implicit truthiness for non-boolean types. Control flow statements can appear at the top level or inside function bodies. A `return` inside a branch or loop body exits the enclosing function. Using `return` outside of a function body raises a `ReturnOutsideFunction` error. ## if / elif / else ### if without else ```jade if { } ``` ### if with else ```jade if { } else { } ``` ### if / elif / else ```jade if { } elif { } else { } ``` Any number of `elif` branches may follow the initial `if`. Each branch is tested in order; the first one whose condition is `true` executes, and the rest are skipped. The final `else` is optional and runs only when every preceding condition is `false`. ```jade fn classify(x) { if x > 0 { return 1 } elif x < 0 { return -1 } else { return 0 } } fn grade(score) { if score >= 90 { return 4 } elif score >= 80 { return 3 } elif score >= 70 { return 2 } elif score >= 60 { return 1 } else { return 0 } } ``` :::note The `else` keyword must appear on the same line as the closing `}` of the `then` block, or on the next line. Because the lexer does not insert a semicolon after `}`, writing `} else {` on one line or splitting across lines both work correctly. The same applies to `} elif {`. ::: ## Basic Examples ### Returning different values based on a condition ```jade fn max(a, b) { if a > b { return a } else { return b } } let m1 = max(3, 7) let m2 = max(10, 2) ``` ### if without else — early return pattern ```jade fn clamp(x, lo, hi) { if x < lo { return lo } if x > hi { return hi } return x } let clamped_lo = clamp(1, 5, 10) let clamped_mid = clamp(7, 5, 10) let clamped_hi = clamp(15, 5, 10) ``` Multiple `if` statements without `else` create an early-exit chain. `clamp(1, 5, 10)` hits the first condition and returns `5`. `clamp(7, 5, 10)` skips both and falls through to `return x`, returning `7`. `clamp(15, 5, 10)` hits the second condition and returns `10`. ### Nested if/else — sign function ```jade fn sign(x) { if x > 0 { return 1 } else { if x < 0 { return -1 } else { return 0 } } } ``` ## Type Rules | Operation | Condition Type | Result | |-----------|---------------|--------| | `if ` — condition is `true` | `bool` | Then block executes | | `if ` — condition is `false` | `bool` | Else block executes (if present) | | `if ` — condition is not a `bool` | `int`, `float`, or `fn` | `TypeError` | | `return` inside a branch | Any | Propagates return value up to enclosing function | :::note The condition must be exactly a `bool`. There is no implicit truthiness: `if 1 { … }` is a `TypeError`, not a truthy integer check. ::: ## while Loops The `while` statement is Jade's iteration construct. It evaluates a `bool` condition before each iteration and executes the loop body as long as the condition is `true`. Loop termination is controlled entirely by changing the values that the condition expression tests — typically by using bare assignment (`i = i + 1`) to update a variable that was declared before the loop. ### Syntax ```jade while { } ``` :::note The condition and the opening `{` must be on the same line. Because the lexer inserts a semicolon after any line ending in an integer, float, identifier, `true`, `false`, or `)` token, writing the condition and brace on separate lines would insert an unexpected semicolon. Write `while i < 5 {` on one line. ::: ### Counting up to a limit ```jade let i = 0 while i < 5 { i = i + 1 } ``` Bare assignment updates `i` on each iteration. When `i` reaches `5`, the condition becomes `false` and the loop exits. After the loop, `i` holds `5`. ### Accumulating a sum ```jade let n = 10 let sum = 0 let i = 1 while i <= n { sum = sum + i i = i + 1 } ``` After the loop, `sum` holds `55` (the sum of integers 1 through 10). ### Condition false from the start ```jade let never = 99 while never < 0 { never = never + 1 } ``` The condition is `false` on the first check, so the body never executes. `never` remains `99`. ### Iterative factorial ```jade fn factorial(n) { let result = 1 let i = 1 while i <= n { result = result * i i = i + 1 } return result } let f5 = factorial(5) ``` `factorial(5)` returns `120`. ### Nested while loops ```jade let total = 0 let i = 0 while i < 3 { let j = 0 while j < 3 { total = total + 1 j = j + 1 } i = i + 1 } ``` The inner loop runs to completion on each iteration of the outer loop. After both loops finish, `total` holds `9`. ## for Loops The `for` statement iterates over an array, binding each element to a loop variable in turn. It is the idiomatic way to process every element of an array without managing an index manually. ```jade for in { } ``` - `` — a name bound to each element on each iteration. It is visible throughout the loop body. - `` — any expression that evaluates to an `array`. The array is fully evaluated once before iteration begins. ### Basic iteration ```jade let nums = [1, 2, 3, 4, 5] for n in nums { print(n) } // 1 // 2 // 3 // 4 // 5 ``` ### Inline array literal ```jade for x in [10, 20, 30] { print(x) } ``` ### Accumulating a result ```jade let total = 0 let nums = [1, 2, 3, 4, 5] for n in nums { total = total + n } // total is 15 ``` ### for inside a function ```jade fn sum_array(arr) { let total = 0 for x in arr { total = total + x } return total } let s = sum_array([10, 20, 30]) // 60 ``` :::note The iterable expression must evaluate to an `array`. Attempting to iterate over any other type (such as a string or dict) produces a `TypeError`. ::: :::note **REPL limitation:** `for` loops are not supported in `jade repl`. The REPL uses the tree-walk evaluator, which does not implement `for`. Use `jade run` or `jade build` to run programs that contain `for` loops. ::: --- # Functions Jade functions are defined with `fn`, accept zero or more parameters, and return a value either with an explicit `return` statement or via implicit return (the last bare expression in the body). Functions are first-class values and can be passed to other functions or assigned to variables. ## Overview A function is a named, reusable block of statements. It is introduced with the `fn` keyword, followed by a name, a parenthesized parameter list, and a brace-delimited body. Calling a function evaluates its body in a new scope and returns a value. There are two ways to return a value from a function: - **Explicit return:** `return ` exits immediately and produces that value. - **Implicit return:** if the last statement in the body is a bare expression (no `return` keyword), that expression's value is returned automatically. If execution reaches the end of the body without hitting a `return` statement and without a final bare expression, the function returns `nil`. A bare `return` with no expression also produces `nil`. Functions are first-class values in Jade. A function definition binds the name to a `fn` value in the current environment, just like `let` binds a name to an integer or float. That value can be stored in a variable, passed as an argument, and called through any expression that evaluates to a function. ## Syntax ### Function Definition ```jade fn (, , ...) { return } ``` - `` — an identifier naming the function; binds it in the enclosing scope. - `` — zero or more parameter names separated by commas; each becomes a local variable inside the body. - `` — any sequence of statements. If the last statement is a bare expression, its value is returned implicitly. - `return ` — exits immediately and produces the given value. A bare `return` produces `nil`. ### Function Call ```jade (, , ...) ``` - `` — any expression that evaluates to a function value. - `` — zero or more argument expressions evaluated left-to-right in the caller's scope. ## Basic Examples ### A function with two parameters ```jade fn add(a, b) { return a + b } let sum = add(3, 4) ``` ### Implicit return (last expression) ```jade fn double(x) { x * 2 } print(double(5)) // 10 ``` The last statement is the bare expression `x * 2`. Because it is not a `let`, `if`, or other statement, its value is automatically returned. This is equivalent to writing `return x * 2`. ### A function with no parameters ```jade fn get_answer() { return 42 } let answer = get_answer() ``` The empty parameter list `()` is required even when there are no parameters. ### Chaining calls ```jade fn square(x) { return x * x } let chained = add(square(2), square(3)) ``` Call expressions can be nested. `square(2)` evaluates to `4`, `square(3)` evaluates to `9`, and `add(4, 9)` returns `13`. ## Advanced Examples ### Recursion — factorial ```jade fn factorial(n) { if n == 0 { return 1 } return n * factorial(n - 1) } let f5 = factorial(5) ``` Functions can call themselves. `factorial(5)` computes `5 * 4 * 3 * 2 * 1 = 120`. Mutual recursion also works because function definitions are bound before either is called. ### First-class functions — higher-order functions ```jade fn double(x) { return x * 2 } fn apply(f, x) { return f(x) } fn compose(f, g, x) { return f(g(x)) } let f = double let a = f(5) let b = apply(double, 6) let d = compose(double, double, 3) ``` `double` is assigned to `f`, which can then be called as `f(5)`. `apply` receives a function as its first argument and calls it with the second. `compose(double, double, 3)` returns `12`. ### Fibonacci — two recursive calls ```jade fn fib(n) { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) } let fib10 = fib(10) ``` `fib(10)` returns `55`. ## Error Conditions | Error | Trigger | Example | |-------|---------|---------| | `NotCallable` | Calling a non-function value | `let x = 5` then `let y = x(1)` | | `ArityMismatch` | Wrong number of arguments | `fn add(a, b) { return a + b }` then `add(1)` | | `NestedFunction` | Defining a function inside another function body | `fn outer() { fn inner() { return 1 } return 2 }` | | `ReturnOutsideFunction` | Using `return` at the top level | `return 1` | | `UndefinedVariable` | Referencing a name not in scope | `fn f() { return z }` where `z` is not defined | ## Built-in Functions Jade provides a small set of global built-in functions that are always in scope. | Function | Signature | Description | |----------|-----------|-------------| | `print` | `print(value)` | Writes `value` to stdout followed by a newline. Accepts any type. | | `write` | `write(str)` | Writes a string to stdout **without** a trailing newline and flushes immediately. | | `len` | `len(value)` | Returns the number of elements in a `str`, `array`, or `dict`. | | `input` | `input()` / `input(prompt)` | Reads one line from stdin and returns it as a `str`. If `prompt` is given, prints it to stdout (no newline) before reading. Returns `""` on EOF. | ```jade // print vs write print("hello") // hello\n write("hello") // hello (no newline, flushed immediately) // len on different types let s = "jade" print(len(s)) // 4 let arr = [1, 2, 3] print(len(arr)) // 3 // input — read from stdin let name = input("Enter your name: ") print("Hello, " + name) // input with no prompt let line = input() ``` ## Closures A closure is an anonymous function written inline using the `|params| body` syntax. It captures a snapshot of the variables visible at the point it is created, so it can reference those variables even when called later from a different scope. ### Syntax ```jade |, , ...| // single-expression body |, , ...| { } // block body || // zero-parameter closure ``` ### Basic examples ```jade // Single-expression body — implicitly returns the expression let double = |x| x * 2 print(double(5)) // 10 // Multiple parameters let add = |x, y| x + y print(add(3, 4)) // 7 // Zero parameters let greet = || "hello" print(greet()) // hello ``` ### Capturing outer variables All variables visible at closure creation are captured by copy. The closure carries its own snapshot — mutations inside the closure do not affect the outer scope. ```jade let multiplier = 3 let triple = |x| x * multiplier print(triple(5)) // 15 ``` ### Block body ```jade let abs_val = |x| { if x < 0 { return -x } return x } print(abs_val(-7)) // 7 print(abs_val(4)) // 4 ``` ### Closures as arguments Closures are first-class values and can be passed to higher-order functions: ```jade fn apply(f, x) { return f(x) } let result = apply(|x| x * x, 6) // 36 ``` :::note The `|` symbol that opens a closure is only treated as a closure delimiter when it appears at the start of an expression (primary position). In all other positions it remains the bitwise OR operator. The empty-param form uses `||`, which is distinct from the logical OR operator because `||` only appears as logical OR in an infix position, never at the start of an expression. ::: --- # Async / Await Jade supports concurrent LLM inference through `async fn` definitions and `await` expressions. Multiple prompt dereferences can be in-flight at the same time, reducing total wall-clock time when a program sends several independent prompts. ## Overview By default, a `?p` dereference blocks until the model responds. When many independent prompts need answers, blocking on each one in sequence is wasteful. `async fn` lets you express that a function's body may be deferred — the call returns a *future* immediately, and execution of the body proceeds concurrently with the rest of the program. The result is only demanded when you `await` it. Under `jade run`, async functions are dispatched onto the Tokio runtime. Multiple async calls run concurrently: network round-trips to the LLM overlap instead of stacking. Under the tree-walk evaluator (REPL), async functions execute synchronously one at a time, and a warning is printed to stderr. ## Defining an `async fn` Prefix `fn` with `async` to mark a function as asynchronous. The rest of the syntax is identical to a regular function. ```jade async fn () { return } ``` Calling an `async fn` does *not* run its body immediately. Instead it returns a **future** — a pending computation. The body begins running concurrently in the background (under `jade run`). ```jade async fn fetch(q) { prompt p = q return await ?p } // Both calls start immediately — bodies run concurrently. let a = fetch("What is the capital of France?") let b = fetch("What is the capital of Germany?") // await blocks here, but only until each result is ready. print(await a) // Paris print(await b) // Berlin ``` :::note Nested `async fn` definitions (an `async fn` inside another function body) are rejected at parse time with a `NestedFunction` error. ::: ## The `await` Expression `await` is a prefix expression that blocks the current context until the future resolves, then produces the future's value. ```jade let result = await ``` `` must evaluate to a future (the return value of an `async fn` call). Applying `await` to any other value raises `NotAFuture`. Awaiting the same future twice raises `DoubleAwait`. ### Awaiting inside an async function You can `await` a prompt dereference directly inside an `async fn` body to suspend that task until the model responds, while other tasks continue running. ```jade async fn summarize(text) { prompt p = "Summarize in one sentence: " + text return await ?p } ``` ### Awaiting at the top level `await` can also appear at the top level of a program to collect results from previously launched async calls. ```jade let t1 = summarize("The quick brown fox...") let t2 = summarize("Four score and seven years...") let s1 = await t1 let s2 = await t2 print(s1) print(s2) ``` ## Concurrency Model Jade's async model follows a simple rule: calling an `async fn` starts the work; `await` collects the result. The two operations are separate, which is what enables overlap. - **Call phase** — the `async fn` body is dispatched to the Tokio runtime. The call expression returns a future handle immediately. - **Concurrent phase** — the caller continues executing (starting more async calls, computing other values) while the async bodies run in the background. - **Await phase** — `await future` suspends the caller until that specific task finishes and returns its value. Token accounting is performed per-task: each async task tracks the tokens it consumes and adds them to `__tokens__` when the task completes. :::warning **REPL limitation:** The tree-walk evaluator used by the interactive REPL does not run a Tokio runtime. Async functions execute synchronously in that context, and a warning is printed to stderr. Use `jade run` for true concurrent execution. ::: ## Common Patterns ### Fan-out: send N prompts, collect all results ```jade async fn ask(q) { prompt p = q return await ?p } let r1 = ask("Name a red fruit.") let r2 = ask("Name a blue fruit.") let r3 = ask("Name a green fruit.") print(await r1) print(await r2) print(await r3) ``` All three prompts are dispatched before any `await` is reached. The program waits for each in order of collection, but the LLM calls run in parallel. ### Async with typed dereference ```jade async fn count_words(sentence) { prompt p = "How many words in: " + sentence + "? Reply with only the number." return await ?p |> int } let n = count_words("The quick brown fox") print(await n) // integer word count ``` Typed dereference (`|> int`, `|> bool`, etc.) works inside async functions. Retry logic applies per-task. ### Passing futures to functions A future is a first-class value and can be passed to another function, stored in a variable, or returned from a function. ```jade async fn get(q) { prompt p = q return await ?p } fn print_result(future) { print(await future) } let f = get("What year did WWII end?") print_result(f) ``` ## Token Accounting Each async task accumulates the tokens it uses during inference and adds them to the global `__tokens__` counter when the task finishes. This means `__tokens__` reflects the running total across all completed tasks, both synchronous and async. ```jade async fn ask(q) { prompt p = q return await ?p } let a = ask("hi") let b = ask("hello") let _ = await a let _ = await b print(__tokens__) // total tokens from both tasks ``` :::note If you read `__tokens__` before awaiting a task, that task's tokens are not yet included. Always await all tasks before reading the final token count. ::: ## Error Reference | Error | Cause | |-------|-------| | `NotAFuture` | `await` applied to a value that is not a pending async result (e.g., an `int` or `str`) | | `DoubleAwait` | The same future was awaited more than once — a future is consumed on first await | | `AsyncPanic` | A spawned async task panicked internally; the panic message and source span are captured and reported | | `PromptOverflow` | Inside an async task, a typed dereference exhausted all retries — same as in synchronous code | | `InferenceError` | HTTP or API error from the provider inside an async task — propagated to the awaiting call site | ## Related Pages - [Functions](functions) — regular `fn` syntax, closures, first-class functions - [LLM Integration](llm) — `prompt` declarations, `?` dereference, typed coercion, configuration - [Exceptions](exceptions) — how runtime errors surface and how to handle them --- # Structs Structs are named record types that group related values under named fields. Methods can be attached to any struct type using `extend` blocks. ## Overview A `struct` definition introduces a new named type with an ordered list of field names. Once defined, a `struct` type can be instantiated by providing values for every field in a struct literal. The resulting value is a struct instance that holds those field values and can be stored in a variable. Individual fields are read with dot syntax (`obj.field`) and updated with field assignment syntax (`obj.field = expr`). Field assignment mutates the existing instance in place — all variables that hold a reference to the same instance see the updated value immediately. Methods are defined separately from the struct using an `extend` block. Each method receives the instance it was called on as its first parameter, conventionally named `self`. Calling a method through dot syntax (`obj.method(args)`) automatically supplies the instance as `self`; the caller does not pass it explicitly. ## Syntax ### Struct Definition ```jade struct { , , ... } ``` - `` — an identifier naming the type; registered in the global struct registry. - `` — one or more field names separated by commas. Fields have no type annotation — they hold any value at runtime. ### Struct Instantiation ```jade { : , : , ... } ``` - Every field declared in the struct definition must be present in the literal. - Extra fields not declared in the definition raise an `UndefinedField` error. - Missing fields raise a `MissingField` error. ### Field Access ```jade . ``` Evaluates `` to a struct instance, then returns the value of the named field. If the named field does not exist, raises `UndefinedField`. If `` does not evaluate to a struct, raises `NotAStruct`. ### Field Assignment ```jade . = ``` Updates the named field on the struct instance held by ``. The field must already exist on the instance. ### Extend Block ```jade extend { fn (self, , ...) { return } ... } ``` Each method is a `fn` definition where the first parameter receives the receiver instance. Conventionally named `self`. ### Method Call ```jade .(, ...) ``` Field access on a struct first checks instance fields, then checks the method table for the struct type. When a method is found, a bound method value is returned. Calling it automatically passes the receiver as the first argument (`self`). The caller supplies only the arguments after `self`. ## Basic Examples ### Defining a struct and accessing its fields ```jade struct Point { x, y } let p = Point { x: 10, y: 20 } let px = p.x let py = p.y ``` `p.x` evaluates to `10` and `p.y` evaluates to `20`. ### Mutating a field with field assignment ```jade struct Point { x, y } let p = Point { x: 10, y: 20 } p.x = 99 let updated_x = p.x ``` `p.x = 99` overwrites the `x` field on the existing instance. After the assignment, `p.x` evaluates to `99`. The instance is mutated in place. ### Attaching a method with extend and calling it ```jade struct Counter { count } extend Counter { fn increment(self) { self.count = self.count + 1 } fn value(self) { return self.count } } let c = Counter { count: 0 } c.increment() c.increment() let v = c.value() ``` After two calls to `c.increment()`, `c.value()` returns `2`. Mutations through `self` inside a method are visible on the original instance because `self` and the caller's variable share the same underlying struct object. ## Advanced Examples ### Method that uses a parameter alongside self ```jade struct Accumulator { total } extend Accumulator { fn add(self, n) { self.total = self.total + n } fn result(self) { return self.total } } let acc = Accumulator { total: 0 } acc.add(10) acc.add(5) acc.add(3) let sum = acc.result() ``` `add` takes `self` and an extra parameter `n`. When called as `acc.add(10)`, the evaluator binds `self` to the receiver and `n` to `10`. After three calls, `acc.result()` returns `18`. ## Error Conditions | Error | Trigger | Example | |-------|---------|---------| | `UndefinedType` | Struct literal uses a type name that has not been defined | `let p = Foo { x: 1 }` when no `struct Foo` exists | | `MissingField` | Struct literal omits a required field | `struct Point { x, y }` then `let p = Point { x: 1 }` | | `UndefinedField` | Struct literal includes an undeclared field, or dot access targets an undeclared field | `struct Point { x, y }` then `Point { x: 1, y: 2, z: 3 }` | | `NotAStruct` | Dot access or field assignment on a non-struct value | `let x = 5` then `let y = x.foo` | | `ArityMismatch` | Method called with the wrong number of arguments (not counting `self`) | `extend Counter { fn add(self, n) { … } }` then `c.add(1, 2)` | :::note The arity check for method calls excludes `self` from the expected count — `self` is supplied automatically by the evaluator. A method defined as `fn add(self, n)` expects exactly one argument from the caller. ::: ## Implementation Notes :::note Struct instances are shared by reference. Assigning a struct instance to a new variable does not copy it — both variables reference the same object. A field mutation through one variable is immediately visible through the other. ::: :::note Struct literals are disallowed in `if` and `while` conditions. The parser sets `struct_literal_allowed = false` while parsing a condition so that `while running { … }` does not try to interpret `running {…}` as a struct literal. ::: --- # Imports Jade's `use` statement loads another `.jde` file or a standard-library package and makes its definitions available in the importing file. There are two import forms with different syntax: - **File imports** use a quoted path and **require an alias**: `use "lib.jde" as lib`. - **Package imports** (standard library) use **`::` notation**: `use std::math`. ## File Imports ```jade use "" as ``` - `` — a relative path to another `.jde` file, resolved relative to the directory of the importing file. - `` — the alias the imported module is bound to. The alias is **required**; a bare `use "lib.jde"` without `as ` is a compile-time error (`MissingImportAlias`). - The `use` statement must appear at the top level (not inside a function body). - The imported file is executed once; its definitions are reachable through the alias. ## Basic Example **math_lib.jde** — the library: ```jade fn add(a, b) { return a + b } fn mul(a, b) { return a * b } ``` **main.jde** — the importer: ```jade use "math_lib.jde" as math_lib let x = math_lib.add(2, 3) // 5 let y = math_lib.mul(4, 5) // 20 ``` After the `use` statement executes, the imported module's functions are reachable through the alias (`math_lib.add`, `math_lib.mul`). They can be called, passed as values, or stored in variables. ## Path Resolution Paths in `use` are resolved relative to the directory containing the *importing* file, not the directory from which `jade` was invoked. ```jade // If your project layout is: // project/ // main.jde // lib/ // utils.jde // Inside main.jde: use "lib/utils.jde" as utils ``` :::note Absolute paths are not supported. Always use paths relative to the importing file's location. ::: ## Library Imports (`[lib]`) Relative paths get awkward across a deep project tree (`use "../../shared/util.jde"`). To import a module from anywhere in a project, register a **library** in `jade.toml`: a named directory, optionally with an allowlist of its modules. ```toml # jade.toml [project] name = "myapp" [lib.utils] path = "src/utils" # directory, relative to the project root files = ["math.jde", "io.jde"] # optional: allowlist of importable filenames ``` `files` is optional: - **Omit it** to make every recognized file in the directory importable. - **List filenames** (with extension) to restrict imports to that allowlist. A module's **file extension decides how it loads**: - `.jde` → a Jade source module. - `.dylib` / `.so` / `.dll` → a **native** C-ABI shared library (e.g. a Rust crate built as `cdylib`), loaded over the `jade_pkg_init` FFI and bound as a dict of functions. This replaces the old `[native]` section — Jade and native modules now live under one `[lib]` system. ```toml [lib.ext] path = "lib" files = ["math.jde", "fastmath.dylib"] # one Jade module, one native library ``` ```jade use ext::math // -> lib/math.jde (Jade) use ext::fastmath // -> lib/fastmath.dylib (native), then fastmath.some_fn(...) ``` Native libraries run under the interpreter only: `jade build` (the AOT/native compiler) can't link the FFI loader into a static binary, so it rejects native imports with a message pointing you at `jade run`. Then import the module with **`::` notation** from **any** file in the project — the path is resolved against the library's directory anchored at the project root, not the importing file. The import binds to its last segment automatically (no `as` needed): ```jade use utils::math // -> /src/utils/math.jde, from anywhere print(math.square(5)) // binds as `math` (the last segment) ``` Rules: - **`::` notation names a module** — a stdlib package (`use std::math`) or a registered library (`use utils::math`). It binds the last segment and needs no alias. **String notation names a file path** (`use "lib/helper.jde" as h`) and still requires an alias. - With a `files` allowlist, importing an unlisted module is a hard error in both `jade run` and `jade build`. Without one, a missing file is a normal not-found error. - `::` notation is treated as a library reference only when its first segment names a registered library; plain relative file imports (string form) keep working unchanged. - Library resolution is identical in the VM and the native (AOT) build. ## What Gets Imported The imported module's top-level functions, variables, and struct definitions are reachable through the alias (`.`). The imported file runs to completion before execution of the importing file continues past the `use` statement. | Exported from `lib.jde` (imported `as lib`) | Available after `use` | |----------------------------------------------|-----------------------| | `fn add(a, b) { … }` | `lib.add(2, 3)` works | | `let PI = 3.14159` | `lib.PI` is in scope | | `struct Point { x, y }` | `lib.Point { x: 1, y: 2 }` works | ## Multiple Imports A file may contain multiple `use` statements. Each is processed in order; each file import must have its own alias. ```jade use "math_lib.jde" as m use "string_lib.jde" as s let n = m.add(1, 2) let greeting = s.concat("hello", " world") ``` ## No Re-export Imports are not re-exported. If `a.jde` uses `b.jde`, a third file that uses `a.jde` does *not* automatically get access to what `b.jde` defined. Each file must import the libraries it needs directly. --- ## Standard Library Packages Jade's standard library is imported with **`::` notation** — `use std::json`, not a quoted path. These packages are always available; no installation required. ```jade use std::math use std::json use std::path use std::random let n = math.sqrt(144.0) // 12.0 let data = json.parse('{"x": 1}') let p = path.join("src", "main.jde") let roll = random.int(1, 6) ``` Importing a package binds it as a global variable named after the package (`math`, `json`, `path`, etc.). The table below lists all available packages. :::warning The string-literal form for stdlib packages — `use "std/math"` — is **rejected at compile time** (`StdlibStringImport`). Always use `::` notation: `use std::math`. Quoted paths are reserved for file imports, which require an alias. ::: | Import | Global | Summary | |--------|--------|---------| | `use std::math` | `math` | `floor`, `ceil`, `abs`, `sqrt`, `min`, `max`, `pow` | | `use std::string` | `string` | `split`, `upper`, `lower`, `trim`, `contains`, `replace`, `starts_with`, `ends_with` | | `use std::array` | `array` | `map`, `filter`, `sort`, `reverse` (higher-order; non-mutating) | | `use std::dict` | `dict` | `keys`, `values`, `has`, `get`, `merge` | | `use std::fs` | `fs` | `read`, `write`, `append`, `exists`, `delete`, `list_dir`, `mkdir` | | `use std::time` | `time` | `now`, `now_ms`, `sleep`, `local` | | `use std::http` | `http` | `get`, `post`, `put`, `delete`, `head` | | `use std::sh` | `sh` | `exec`, `run`, `output` | | `use std::json` | `json` | `parse`, `stringify`, `stringify_pretty` | | `use std::env` | `env` | `get`, `set`, `args`, `cwd` | | `use std::path` | `path` | `join`, `basename`, `dirname`, `ext`, `stem`, `abs`, `is_abs` | | `use std::random` | `random` | `int`, `float`, `choice`, `shuffle`, `seed` | | `use llm` | `llm` | `set_max_tokens`, `count_tokens`, `total_tokens` | See the [Standard Library](stdlib) reference for full API documentation. ## Selective Imports (`from … use`) The `from use ` form imports specific names from a package directly into scope, without the package prefix. It uses the same `::` notation as `use`. ```jade from std::math use floor, ceil, sqrt let a = floor(3.7) // 3 let b = sqrt(16.0) // 4.0 ``` As with `use`, the string-literal form (`from "std/math" use floor`) is a compile-time error — use `::` notation. ## Native Packages Native packages declared in `jade.toml` under a `[native]` section are imported by their `native/` path and bound to the `alias` declared in the manifest. Unlike other file imports, native packages do not need an inline `as ` — the alias comes from the manifest: ```toml # jade.toml [native] mylib = { path = "/path/to/libmylib.dylib", alias = "mylib" } ``` ```jade use "native/mylib" // bound as `mylib` per the manifest alias let r = mylib.compute(21) ``` --- # Exceptions Jade provides structured exception handling through three keywords: `raise` raises any value as an exception, `try` wraps a block that might raise, and `catch` handles the raised value by type or unconditionally. ## Overview An exception is any Jade value raised with the `raise` statement. The raised value can be a string, a struct instance, an integer, or any other runtime value. The most common pattern is to define a dedicated `struct` type to carry the exception payload, then raise an instance of that type. This lets `catch` arms match by type name and access the fields of the caught value. A `try`/`catch` block wraps the statements that might raise. If the `try` body completes without raising, execution continues normally after the last `catch` arm and no catch arm runs. If a `raise` occurs, execution of the `try` body stops immediately and the runtime searches the catch arms in order, executing the first one that matches the raised value. Built-in runtime errors — division by zero, type errors, undefined variable references, index out of bounds, and all other errors — are automatically wrapped in a synthetic `RuntimeError` struct with a single `message` field. A catch-all arm (`catch e { … }`) will catch these just as it catches user-raised exceptions. If no `catch` arm matches the raised value, the exception propagates outward to the nearest enclosing `try`/`catch`. If it reaches the top level without being caught, the program exits with an error message. ## Syntax ### raise ```jade raise ``` `` — any expression. The evaluated value becomes the raised exception. Execution of the enclosing block stops immediately after `raise`. ### try / catch ```jade try { } catch { } catch { } ``` - `` — any sequence of statements that might raise. - `` — an optional struct type name. When present, this arm only matches exceptions that are instances of that struct type. When absent, the arm is a catch-all and matches any raised value. - `` — a name bound to the caught value inside the arm body. Any number of typed `catch` arms may appear before the optional catch-all. Arms are tested in the order they are written; the first match wins. A catch-all arm should appear last because it matches everything. ## Basic Examples ### Raising and catching a string ```jade fn risky() { raise "something went wrong" } try { risky() } catch e { print(e) } ``` `risky` raises a string. The catch-all arm binds the string to `e` and prints it. Output: `something went wrong`. ### Typed catch with a struct exception ```jade struct ValueError { message } fn parse_age(n) { if n < 0 { raise ValueError { message: "age cannot be negative" } } return n } try { parse_age(-1) } catch ValueError e { print(e.message) } ``` Defining a dedicated exception struct lets catch arms match by type. `catch ValueError e` only runs when the raised value is a `ValueError` instance. Output: `age cannot be negative`. ### Catching a built-in runtime error ```jade try { let x = 1 / 0 } catch e { print("caught runtime error") } ``` Division by zero normally terminates the program. Inside a `try` block, built-in runtime errors are automatically wrapped in a `RuntimeError` struct with a `message` field. The catch-all arm binds this struct to `e` and the program continues. Output: `caught runtime error`. ## Advanced Examples ### Multiple typed catch arms — first match wins ```jade struct NetworkError { code, message } struct ValueError { message } try { raise NetworkError { code: 503, message: "service unavailable" } } catch ValueError e { print("wrong: value error") } catch NetworkError e { print(e.code) print(e.message) } catch e { print("wrong: catch-all") } ``` Three arms are checked in order. The raised value is a `NetworkError`, so the first arm does not match. The second arm matches and runs, printing `503` then `service unavailable`. The catch-all is never reached. ### Exception propagation through the call stack ```jade struct ValueError { message } fn inner() { raise ValueError { message: "deep error" } } fn outer() { inner() } try { outer() } catch ValueError e { print(e.message) } ``` When `inner` raises, execution unwinds through `outer` without any catch arm in either function. The exception propagates to the `try`/`catch` block at the call site, which catches it and prints `deep error`. Exceptions cross function boundaries automatically. ### Nested try/catch — inner handles if it matches, outer handles if it does not ```jade struct ValueError { message } struct NetworkError { code, message } try { try { raise ValueError { message: "inner error" } } catch NetworkError e { print("wrong: inner caught network error") } } catch ValueError e { print(e.message) } ``` The inner `try`/`catch` only handles `NetworkError`. Because a `ValueError` was raised and the inner arm does not match, the exception propagates to the outer `try`/`catch`, which catches it and prints `inner error`. ### try body completes normally — catch never runs ```jade try { let x = 1 + 1 print(x) } catch e { print("wrong: should not run") } ``` When no exception is raised, the `try` body executes fully and no catch arm runs. Output: `2`. ## Type Rules | Operation | Condition | Result | |-----------|-----------|--------| | `raise ` | Any value | Exception propagation; execution of current block stops | | `catch TypeName e` — typed arm | Raised value must be a `Struct` with `type_name == TypeName` | Arm body executes; `e` bound to the struct instance | | `catch e` — catch-all arm | Any raised value, including built-in runtime errors | Arm body executes; `e` bound to the raised value | | Built-in runtime error inside `try` | Any internal error | Wrapped as `RuntimeError { message }` struct; catchable by catch-all or `catch RuntimeError e` | | No catch arm matches | Raised value does not match any typed arm | Exception re-raised; propagates to nearest enclosing `try` | :::note Typed `catch` arms only match struct values — they check the `type_name` field of the struct instance at runtime. Raising a plain string or integer and then catching with a typed arm will not match; use a catch-all arm to handle non-struct raises. ::: ## Interaction with Other Features - **Structs**: The most common pattern is to raise struct instances as typed exceptions. Typed `catch` arms match by struct type name. See [Structs](structs). - **Functions**: Exceptions propagate through function call boundaries automatically. A `raise` inside a function unwinds the call stack until it reaches the nearest enclosing `try`/`catch`. See [Functions](functions). - **Control flow**: `try`/`catch` blocks can appear anywhere a statement is valid. A `return` inside a `try` body exits the enclosing function normally without triggering any `catch` arm. See [Control Flow](control-flow). - **Variables**: The binding introduced by a `catch` arm is scoped to that arm's body. Use bare assignment to write back to a variable declared in an enclosing scope from inside a catch arm. --- # LLM Integration Jade is built around first-class LLM access. A `prompt` declaration names a prompt string; the `?` operator sends it to the configured model and returns the response. The `|>` pipe suffix coerces the response to a typed Jade value, with automatic retry on failure. ## Declaring a Prompt Use the `prompt` keyword to bind a prompt string to a name. The right-hand side is any expression that evaluates to a `str`. ```jade prompt p = "What is the capital of France?" ``` A `prompt` binding holds the prompt text — it does not call the model. The model is only called when the variable is dereferenced with `?`. ```jade let question = "What is 2 + 2?" prompt p = question ``` ## Untyped Dereference — `?p` Prefixing a prompt variable with `?` sends the prompt to the configured model and returns the raw response as a `str`. ```jade prompt p = "Say exactly: Hello from Jade!" let response = ?p print(response) ``` Each `?` dereference appends a user turn and the model's reply to the shared **conversation history** for this program run. Subsequent dereferences see the full prior context, so prompts can naturally build on each other. ```jade prompt p1 = "My name is Alice." prompt p2 = "What is my name?" let _ = ?p1 // establishes context let name = ?p2 // model sees p1 exchange, should reply "Alice" print(name) ``` ## Typed Dereference — `?p |> type` Append `|> type` after a prompt dereference to coerce the model's response to a Jade value type. The supported target types are: | Type | Accepted LLM output | Result | |------|---------------------|--------| | `int` | `"42"`, `"-7"` | `int` value | | `float` | `"3.14"`, `"1e10"` | `float` value | | `bool` | `"true"`, `"True"`, `"false"` | `bool` value | | `str` | anything | `str` value (always succeeds) | ```jade prompt p = "What is 3 + 4? Respond with only the number." let n = ?p |> int print(n + 1) // 8 ``` ```jade prompt p = "Is 5 greater than 3? Respond with only: true or false" let result = ?p |> bool if result { print("correct!") } ``` :::note `?p |> type` must be assigned to a variable — it cannot appear directly inside `print()`. Use `let n = ?p |> int` then `print(n)`. ::: ## Retry on Coercion Failure When the model's response cannot be parsed as the requested type, Jade automatically sends a correction follow-up and tries again. This continues up to `max_retries` times (default: `3`, giving 4 total attempts including the initial one). If all attempts fail, Jade raises a `PromptOverflow` runtime error naming the prompt variable and the number of attempts made. On success, the retry conversation turns are stripped from the shared history — only the final successful exchange is retained. ```jade // With max_retries = 3, Jade will try up to 4 times to get a valid int. prompt p = "Pick a lucky number." let n = ?p |> int print(n) ``` ## Configuration Jade reads LLM settings from `jade.toml` in the working directory. Environment variables override file values. ### `jade.toml` format ```toml [model] provider = "anthropic" # or "openai" model = "claude-haiku-4-5-20251001" max_retries = 3 api_key = "sk-..." # optional — prefer the env var ``` ### Environment variables | Variable | Purpose | |----------|---------| | `JADE_API_KEY` | API key (overrides `api_key` in jade.toml) | | `JADE_PROVIDER` | `anthropic` or `openai` | | `JADE_MODEL` | Model name string | | `JADE_MAX_RETRIES` | Integer, overrides `max_retries` | ### Interactive setup Run `jade configure` to launch the interactive wizard. It prompts for provider, model, API key, and max retries, then writes `jade.toml` in the current directory. ```bash jade configure ``` :::warning **Security:** storing `api_key` in `jade.toml` saves it in plaintext. Prefer setting `JADE_API_KEY` in your shell environment and omitting the key from the file. ::: ### Supported providers | Provider string | API used | Default model | |----------------|----------|---------------| | `anthropic` (default) | Anthropic Messages API | `claude-haiku-4-5-20251001` | | `openai` | OpenAI Chat Completions | `gpt-4o-mini` | | `jade-os` | Jade OS on-device kernel backend | set by device configuration | ## Session Variables Jade pre-populates several read-only-by-convention variables in the global scope before your program runs. These are updated after each `?` dereference. | Variable | Type | Description | |----------|------|-------------| | `__tokens__` | `int` | Running total of tokens consumed by all inference calls this run | | `__model__` | `str` | Name of the model being used | | `__max_retries__` | `int` | Configured maximum retry count for typed dereferences | | `__retry_log__` | `array` | Log of retry events (reserved for future structured output) | ```jade prompt p = "Say: hi" let _ = ?p print(__model__) // e.g. "claude-haiku-4-5-20251001" print(__max_retries__) // 3 print(__tokens__) // tokens used so far ``` ## Runtime Configuration — `use llm` Import the built-in `llm` package (dot notation, like all packages) to adjust LLM settings at runtime: ```jade use llm llm.set_max_tokens(256) // cap responses at 256 tokens ``` ### `llm.set_max_tokens(n)` Sets the maximum number of tokens the model may generate per inference call. `n` must be a positive integer. The setting takes effect for all subsequent `?` dereferences in the same run. ```jade use llm llm.set_max_tokens(64) prompt p = "Write a haiku about rain." let haiku = ?p print(haiku) ``` :::note `set_max_tokens` overrides any `max_tokens` value set in `jade.toml` or environment variables for the remainder of the program run. There is no way to reset it to the config file value once changed. ::: ### `llm.count_tokens(text)` Returns the number of tokens in `text` under the active model's tokenizer, as an `int`. Useful for budgeting prompt size before a `?` dereference. ```jade use llm let n = llm.count_tokens("How many tokens is this?") print(n) ``` ### `llm.total_tokens()` Returns the total number of tokens consumed by LLM inference so far in the current run, as an `int`. ```jade use llm prompt p = "Summarize Jade in one line." let _ = ?p print(llm.total_tokens()) // tokens used so far this run ``` ## Model Profiles When inference runs through the Jade OS daemon backend, the active model has a **profile** describing its model-specific token vocabulary — the delimiters it wraps tool calls in, the JSON field that names the tool, and any special-token spans (e.g. reasoning). The profile lets Jade construct and parse a model's tool calls without any program hardcoding the model's delimiters. ### `llm.model()` Returns the active model's name as a `str` (the name reported by the backend, or the configured default). ```jade use llm print(llm.model()) // e.g. "Qwen3-Coder-30B" ``` ### `llm.profile()` Returns the active model's profile as a dict, or `nil` when the model has no registered profile. Shape: ```jade use llm let p = llm.profile() print(p.tool_call.open) // "" print(p.tool_call.close) // "" print(p.tool_call.name_field) // "name" // p.model → the model name // p.spans → array of { tag, open, close } special-token spans ``` ## Tool Calls A model emits a tool call as a JSON object wrapped in its profile delimiters, e.g. `{ "name": "get_weather", "arguments": { … } }`. The `llm` package finds and extracts these using the **active model's profile**, so the same code works across models. ### `llm.find_tool_call(text)` Returns the **first** tool call found in `text` as a dict `{ name, args }` — where `name` is the tool name lifted from the profile's name field and `args` is the verbatim JSON body — or `nil` if none is present. ```jade use llm let reply = ?prompt "What's the weather in SF? Use a tool." let call = llm.find_tool_call(reply) if call != nil { print(call.name) // "get_weather" print(call.args) // {"name": "get_weather", "arguments": {"city": "SF"}} } ``` ### `llm.find_tool_calls(text)` Returns **every** tool call in `text`, in order, as an array of `{ name, args }` dicts (empty when none are present). ```jade use llm for call in llm.find_tool_calls(reply) { print(call.name) } ``` ### `llm.tool_grammar()` Returns Jade's canonical tool-call GBNF grammar (the JSON body shape) as a `str`. Pair it with the profile delimiters to constrain generation to a valid tool call: ```jade use llm let p = llm.profile() let g = Grammar.new(llm.tool_grammar(), p.tool_call.open, p.tool_call.close) let reply = stream(?prompt "Call a tool.", mute_on=[g]) ``` ### `llm.keep_anchors(enabled)` Sticky session control (`bool`). When enabled, prompts ask the daemon to make the tool-span boundary observable **in-band** — the closing delimiter is not stripped from the streamed output and is synthesized at span close if the model omits it — so a client can delimit a tool span by string matching. Applies to all subsequent `?` dereferences in the run. ```jade use llm llm.keep_anchors(true) ``` ## Daemon Health ### `llm.health()` Returns a health snapshot of the inference daemon as a dict — a cheap liveness/readiness probe that never runs the model. For non-daemon backends a minimal `ok` snapshot is synthesized. ```jade use llm let h = llm.health() print(h.status) // "ok" | "degraded" | "loading" | "error" print(h.model) // active model name print(h.model_loaded) // true print(h.uptime_secs) // seconds since the daemon started serving // h.protocol_version → the daemon's wire-protocol revision ``` ## Async Inference Jade supports concurrent LLM inference through `async fn` definitions and `await` expressions. Defining a function with `async fn` allows it to run prompt dereferences concurrently with other async functions. Within an `async fn`, prefix any expression with `await` to wait for its result. When running under `jade run`, async functions execute concurrently via the Tokio runtime — multiple LLM calls can be in-flight at the same time. ```jade async fn ask_question(q) { prompt p = q return await ?p } let a = ask_question("What is the capital of France?") let b = ask_question("What is the capital of Germany?") print(await a) print(await b) ``` The two calls above run concurrently — both prompts are sent to the model at the same time. :::note The REPL executes `async fn` definitions synchronously (one at a time). Use `jade run` for true concurrent execution. A warning is printed to stderr when an `async fn` is evaluated in the tree-walk path. ::: See [Async / Await](async) for the full reference. ## Error Reference | Error | Cause | |-------|-------| | `MissingApiKey` | `?p` was evaluated but no API key was configured | | `NotAPrompt` | `?x` where `x` is not a `prompt` binding | | `PromptOverflow` | Typed dereference exhausted all retries without producing a valid value | | `InferenceError` | HTTP or API error from the provider (non-2xx response, network failure, etc.) | | `StreamingWithType` | `?p |> Type` used directly inside `print()` — assign to a variable first | | `NotAFuture` | `await` applied to a non-Future value | | `DoubleAwait` | The same Future was awaited more than once | | `AsyncPanic` | A spawned async task panicked; the message and span are captured from the task | --- # Standard Library Jade ships a standard library of built-in packages. Import any package with **`::` notation** — `use std::` — and it becomes a global variable in scope for the rest of the file. ```jade use std::json use std::path use std::random let data = json.parse('{"x": 1}') let p = path.join("/home/user", "projects", "app") let n = random.int(1, 100) ``` :::warning Standard-library packages must be imported with `::` notation. The string-literal form — `use "std/json"` — is **rejected at compile time** (`StdlibStringImport`). Quoted paths are reserved for file imports, which require an `as ` alias. See [Imports](imports). ::: | Import | Global name | Description | |--------|-------------|-------------| | `use std::math` | `math` | Numeric functions | | `use std::string` | `string` | String utilities | | `use std::array` | `array` | Higher-order array functions | | `use std::dict` | `dict` | Dict utilities | | `use std::fs` | `fs` | File system I/O | | `use std::time` | `time` | Clock and sleep | | `use std::http` | `http` | HTTP client | | `use std::sh` | `sh` | Shell command execution | | `use std::json` | `json` | JSON encode / decode | | `use std::env` | `env` | Environment variables and process info | | `use std::path` | `path` | Path manipulation | | `use std::random` | `random` | Random number generation | | `use llm` | `llm` | LLM runtime configuration | --- ## `std/math` ```jade use std::math ``` | Function | Returns | Description | |----------|---------|-------------| | `math.floor(x)` | `int` | Largest integer ≤ x | | `math.ceil(x)` | `int` | Smallest integer ≥ x | | `math.abs(x)` | same as input | Absolute value (int or float) | | `math.sqrt(x)` | `float` | Square root | | `math.min(a, b)` | number | Smaller of two numbers | | `math.max(a, b)` | number | Larger of two numbers | | `math.pow(base, exp)` | `float` | base raised to exp | ```jade use std::math print(math.floor(3.7)) // 3 print(math.ceil(3.2)) // 4 print(math.abs(-5)) // 5 print(math.sqrt(16.0)) // 4.0 print(math.min(3, 7)) // 3 print(math.max(3, 7)) // 7 print(math.pow(2.0, 10.0)) // 1024.0 ``` --- ## `std/string` ```jade use std::string ``` The `std/string` package exposes functions that take the target string as the first argument. The same operations are also available as **primitive methods** directly on any `str` value — see the method form in the table below. | Function | Method form | Returns | Description | |----------|-------------|---------|-------------| | `string.split(s, sep)` | `s.split(sep)` | `array` | Split `s` on `sep`; returns array of str | | `string.upper(s)` | `s.upper()` | `str` | Uppercase | | `string.lower(s)` | `s.lower()` | `str` | Lowercase | | `string.trim(s)` | `s.trim()` | `str` | Strip leading and trailing whitespace | | `string.contains(s, sub)` | `s.contains(sub)` | `bool` | True if `sub` appears in `s` | | `string.replace(s, from, to)` | `s.replace(from, to)` | `str` | Replace first occurrence of `from` with `to` | | `string.starts_with(s, prefix)` | `s.starts_with(prefix)` | `bool` | True if `s` starts with `prefix` | | `string.ends_with(s, suffix)` | `s.ends_with(suffix)` | `bool` | True if `s` ends with `suffix` | `str` also has a `len()` method (no package import needed): ```jade let s = "Hello, Jade!" print(s.len()) // 12 print(s.upper()) // HELLO, JADE! print(s.lower()) // hello, jade! print(s.trim()) // Hello, Jade! (no change here) print(s.split(", ")) // ["Hello" "Jade!"] print(s.contains("Jade")) // true print(s.replace("Jade", "World")) // Hello, World! print(s.starts_with("Hello")) // true print(s.ends_with("!")) // true ``` --- ## `std/array` ```jade use std::array ``` The `std/array` package adds higher-order functions (`map`, `filter`) that are not available as primitive methods, plus standalone versions of `sort` and `reverse` that return new arrays. | Function | Description | |----------|-------------| | `array.map(arr, fn)` | Apply `fn` to each element; return new array of results | | `array.filter(arr, fn)` | Keep elements for which `fn` returns true; return new array | | `array.sort(arr)` | Return a new sorted copy of `arr` (does not mutate) | | `array.reverse(arr)` | Return a new reversed copy of `arr` (does not mutate) | **Primitive methods** (available without import): | Method | Returns | Description | |--------|---------|-------------| | `arr.len()` | `int` | Number of elements | | `arr.push(x)` | `nil` | Append `x` in place | | `arr.pop()` | value | Remove and return the last element | | `arr.contains(x)` | `bool` | True if `x` is in the array | | `arr.sort()` | `nil` | Sort in place | | `arr.reverse()` | `nil` | Reverse in place | ```jade use std::array let nums = [3, 1, 4, 1, 5, 9] // Higher-order functions let doubled = array.map(nums, |x| x * 2) let evens = array.filter(nums, |x| x % 2 == 0) let sorted = array.sort(nums) // new copy, nums unchanged let rev = array.reverse(nums) // new copy print(doubled) // [6, 2, 8, 2, 10, 18] print(evens) // [4] print(sorted) // [1, 1, 3, 4, 5, 9] print(rev) // [9, 5, 1, 4, 1, 3] // Primitive methods (mutate in place) nums.push(2) let last = nums.pop() // 2 print(nums.contains(4)) // true nums.sort() print(nums) // [1, 1, 3, 4, 5, 9] ``` :::note `arr.sort()` and `arr.reverse()` mutate the array in place and return `nil`. `array.sort(arr)` and `array.reverse(arr)` return a **new** array and leave the original unchanged. ::: --- ## `std/dict` ```jade use std::dict ``` The `std/dict` package adds a `merge` function plus standalone versions of the primitive dict methods. | Function | Description | |----------|-------------| | `dict.keys(d)` | Return all keys as an array | | `dict.values(d)` | Return all values as an array | | `dict.has(d, key)` | True if `key` is present | | `dict.get(d, key)` | Value at `key`, or `nil` if absent | | `dict.merge(d1, d2)` | Return new dict combining both; `d2` wins on duplicate keys | **Primitive methods** (available without import): | Method | Returns | Description | |--------|---------|-------------| | `d.len()` | `int` | Number of key-value pairs | | `d.keys()` | `array` | All keys | | `d.values()` | `array` | All values | | `d.has(key)` | `bool` | True if key exists | | `d.get(key)` | value \| nil | Value at key, or nil if missing | ```jade use std::dict let a = {"x": 1, "y": 2} let b = {"y": 99, "z": 3} let merged = dict.merge(a, b) // {"x": 1, "y": 99, "z": 3} print(merged["y"]) // 99 // Primitive methods let keys = a.keys() // ["x", "y"] print(a.has("x")) // true print(a.get("missing")) // nil ``` --- ## `std/fs` ```jade use std::fs ``` | Function | Returns | Description | |----------|---------|-------------| | `fs.read(path)` | `str` | Read entire file as a string | | `fs.write(path, content)` | `nil` | Write string to file, creating or overwriting | | `fs.append(path, content)` | `nil` | Append string to file (creates if absent) | | `fs.exists(path)` | `bool` | True if path exists (file or directory) | | `fs.delete(path)` | `nil` | Delete a file | | `fs.list_dir(path)` | `array` | List entries in a directory (names only, not full paths) | | `fs.mkdir(path)` | `nil` | Create directory (and all parents) | ```jade use std::fs fs.write("hello.txt", "Hello, world!\n") let content = fs.read("hello.txt") print(content) // Hello, world! print(fs.exists("hello.txt")) // true print(fs.exists("no_such_file.txt")) // false let entries = fs.list_dir(".") for entry in entries { print(entry) } fs.mkdir("output/logs") fs.append("output/logs/run.log", "started\n") fs.delete("hello.txt") ``` :::note `fs.read` raises an `IoError` if the file does not exist. Use `fs.exists` to check first when the file may be absent. ::: --- ## `std/time` ```jade use std::time ``` | Function | Returns | Description | |----------|---------|-------------| | `time.now()` | `int` | Current Unix timestamp in seconds | | `time.now_ms()` | `int` | Current Unix timestamp in milliseconds | | `time.sleep(secs)` | `nil` | Block execution for `secs` seconds (int or float) | | `time.local(tz)` | `str` | Formatted local time string. Pass a timezone name (e.g. `"America/Denver"`) or `nil` for the system timezone. | ```jade use std::time let start = time.now_ms() time.sleep(0.1) let elapsed = time.now_ms() - start print(f"elapsed: {elapsed}ms") // elapsed: ~100ms ``` --- ## `std/http` ```jade use std::http ``` All HTTP functions return a `dict` with two keys: | Key | Type | Description | |-----|------|-------------| | `status` | `int` | HTTP status code (e.g. `200`) | | `body` | `str` | Response body as a string | An optional `headers` dict may be passed as the last argument to any function. Keys and values must be strings. | Function | Description | |----------|-------------| | `http.get(url, headers?)` | HTTP GET | | `http.post(url, body, headers?)` | HTTP POST with string body | | `http.put(url, body, headers?)` | HTTP PUT with string body | | `http.delete(url, headers?)` | HTTP DELETE | | `http.head(url, headers?)` | HTTP HEAD (body will be empty) | ```jade use std::http use std::json // Simple GET let resp = http.get("https://api.example.com/status") print(resp["status"]) // 200 print(resp["body"]) // POST with JSON body and headers let payload = json.stringify({"name": "jade"}) let resp2 = http.post( "https://api.example.com/items", payload, {"Content-Type": "application/json", "Authorization": "Bearer sk-..."} ) let result = json.parse(resp2["body"]) ``` :::note Requests time out after 30 seconds. HTTP errors (non-2xx status) do **not** raise — check `resp["status"]` yourself. Network errors (DNS failure, connection refused, etc.) raise an `IoError`. ::: --- ## `std/sh` ```jade use std::sh ``` All three functions run commands through `sh -c`, so shell features like pipes, redirection, and globbing work. | Function | Returns | Description | |----------|---------|-------------| | `sh.exec(cmd)` | `str` | Run command, return stdout (trailing newline stripped). Raises if exit code is non-zero. | | `sh.run(cmd)` | `int` | Run command with inherited stdio. Returns exit code. Never raises. | | `sh.output(cmd)` | `dict` | Capture all output. Returns `{stdout: str, stderr: str, code: int}`. Never raises. | ```jade use std::sh // exec — great for capturing output of simple commands let branch = sh.exec("git rev-parse --abbrev-ref HEAD") print(f"current branch: {branch}") // run — when you want the command's output to go directly to the terminal let code = sh.run("npm test") if code != 0 { raise "tests failed" } // output — when you need full control let result = sh.output("ls -la nonexistent 2>&1") print(result["code"]) // 1 or 2 print(result["stderr"]) // ls: cannot access... ``` :::warning `sh.exec` raises an `IoError` if the command exits with a non-zero code. Use `sh.run` or `sh.output` when you expect failure or need to inspect the exit code. ::: --- ## `std/json` ```jade use std::json ``` | Function | Returns | Description | |----------|---------|-------------| | `json.parse(s)` | value | Parse a JSON string into a Jade value | | `json.stringify(val)` | `str` | Serialize a Jade value to compact JSON | | `json.stringify_pretty(val)` | `str` | Serialize to indented (pretty-printed) JSON | **Type mapping:** | JSON type | Jade type | |-----------|-----------| | `null` | `nil` | | `true` / `false` | `bool` | | integer number | `int` | | floating-point number | `float` | | string | `str` | | array | `array` | | object | `dict` | ```jade use std::json // Parse let data = json.parse('{"name": "jade", "version": 1, "stable": true}') print(data["name"]) // jade print(data["version"]) // 1 // Serialize let compact = json.stringify(data) print(compact) // {"name":"jade","stable":true,"version":1} let pretty = json.stringify_pretty(data) print(pretty) // { // "name": "jade", // "stable": true, // "version": 1 // } // Round-trip let arr = [1, 2, {"x": 3}] let back = json.parse(json.stringify(arr)) print(back[2]["x"]) // 3 ``` :::note `json.parse` raises an `IoError` if the input is not valid JSON. Numbers with a decimal point become `float`; numbers without one become `int`. ::: --- ## `std/env` ```jade use std::env ``` | Function | Returns | Description | |----------|---------|-------------| | `env.get(name)` | `str` \| `nil` | Value of environment variable `name`, or `nil` if unset | | `env.set(name, value)` | `nil` | Set environment variable `name` to `value` | | `env.args()` | `array` | Command-line arguments (including the program name as `args[0]`) | | `env.cwd()` | `str` | Current working directory as an absolute path | ```jade use std::env // Read an env var with a fallback let key = env.get("API_KEY") if key == nil { raise "API_KEY is not set" } // Set a variable (visible to child processes spawned via std/sh) env.set("DEBUG", "1") // Inspect command-line arguments let args = env.args() print(f"running: {args[0]}") if args.len() > 1 { print(f"first arg: {args[1]}") } // Working directory print(env.cwd()) // /home/user/myproject ``` --- ## `std/path` ```jade use std::path ``` | Function | Returns | Description | |----------|---------|-------------| | `path.join(base, part, ...)` | `str` | Join two or more path segments with the OS separator | | `path.basename(p)` | `str` | Filename with extension (last path component) | | `path.dirname(p)` | `str` | Parent directory; `"."` for a bare filename | | `path.ext(p)` | `str` \| `nil` | File extension including the dot (e.g. `".rs"`), or `nil` if none | | `path.stem(p)` | `str` | Filename without extension | | `path.abs(p)` | `str` | Absolute path (resolves relative to cwd; path need not exist) | | `path.is_abs(p)` | `bool` | True if the path is absolute | ```jade use std::path let p = path.join("/home/user", "projects", "app", "main.jde") print(p) // /home/user/projects/app/main.jde print(path.basename(p)) // main.jde print(path.dirname(p)) // /home/user/projects/app print(path.ext(p)) // .jde print(path.stem(p)) // main print(path.is_abs(p)) // true let rel = "src/main.jde" print(path.dirname(rel)) // src print(path.is_abs(rel)) // false print(path.abs(rel)) // /current/working/dir/src/main.jde ``` :::note `path.join` accepts two or more arguments. If any component is an absolute path it resets the result (same behaviour as Python's `os.path.join`). `path.abs` does not resolve symlinks and does not require the path to exist. ::: --- ## `std/random` ```jade use std::random ``` Jade uses a single global RNG (seeded from OS entropy at first use). Calling `random.seed` replaces it with a deterministic seed. | Function | Returns | Description | |----------|---------|-------------| | `random.int(min, max)` | `int` | Uniformly random integer in `[min, max]` inclusive | | `random.float()` | `float` | Uniformly random float in `[0.0, 1.0)` | | `random.choice(arr)` | value | Random element from an array. Raises if empty. | | `random.shuffle(arr)` | `nil` | Shuffle array in place (Fisher-Yates) | | `random.seed(n)` | `nil` | Reseed the global RNG with integer `n` for reproducible output | ```jade use std::random // Reproducible output random.seed(42) let n = random.int(1, 6) // simulated die roll, 1–6 print(n) let f = random.float() // 0.0 ≤ f < 1.0 print(f) let items = ["rock", "paper", "scissors"] let pick = random.choice(items) // random element print(pick) let deck = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] random.shuffle(deck) // in-place shuffle print(deck) ``` --- ## `llm` (LLM configuration) ```jade use llm ``` The `llm` package exposes runtime controls for LLM inference. See [LLM Integration](llm) for the full reference on prompts, typed dereferences, and configuration. | Function | Returns | Description | |----------|---------|-------------| | `llm.set_max_tokens(n)` | `nil` | Cap model responses at `n` tokens for all subsequent `?` calls | | `llm.count_tokens(text)` | `int` | Token count of `text` under the active model's tokenizer | | `llm.total_tokens()` | `int` | Total tokens consumed by LLM inference so far this run | ```jade use llm llm.set_max_tokens(128) prompt p = "Write a one-sentence summary of Jade." let summary = ?p print(summary) ``` --- # CLI Reference The `jade` command uses a subcommand structure. Every operation is a named subcommand — there is no bare `jade ` primary form (though the old form is accepted as a backward-compatible shorthand via `jade run`). ## `jade run` Run a Jade source file, a named script from `jade.toml`, or the project entry point (`main.jde` by default when no argument is given). ```bash jade run program.jde # run a specific file jade run # run the project entry point (main.jde) jade run build # run the script named "build" in jade.toml ``` ### Flags | Flag | Description | |------|-------------| | `-v`, `--verbose` | Print all global variables and their final values after execution. Variables are printed in alphabetical order. | ```bash jade run program.jde --verbose jade run program.jde -v ``` ## `jade check` Type-check a source file without executing it. Exits with code 0 if there are no errors. ```bash jade check program.jde ``` ## `jade build` Compile a Jade file to a native binary. The `jade` CLI runs the language frontend (lex → parse → type-infer → typed IR) and hands the typed program to the **build daemon** over a Unix socket at `$HOME/.jade/build.sock`. The daemon performs import resolution, native code generation, and linking. The build daemon must be running, and this subcommand is available on Unix only. ```bash jade build program.jde jade build program.jde --output mybin jade build program.jde --emit ir ``` ### Flags | Flag | Description | |------|-------------| | `-o`, `--output ` | Output binary path. Defaults to the input filename without extension. | | `--emit ir` | Print the generated IR text (returned by the daemon) to stdout instead of producing a binary. | :::note Native code generation, the C runtime, and linking live in the out-of-process build daemon, not in the `jade` binary itself. If the daemon is not reachable at `$HOME/.jade/build.sock`, `jade build` reports a build error. Run `jade env` to check daemon reachability (the `build` line reports `reachable` or `not running`). ::: ## `jade new` / `jade init` `jade new ` creates a new Jade project in a new directory named ``. `jade init` initializes a project in the current directory. Both accept a `--template` flag. ```bash jade new myapp jade new myapp --template llm jade init jade init --template basic ``` ### Flags | Flag | Description | |------|-------------| | `--template ` | Project template. Defaults to `basic`. | ## `jade repl` Start an interactive REPL session. Each line is evaluated against the running environment; definitions from previous lines persist. ```bash jade repl jade repl --verbose ``` ### Flags | Flag | Description | |------|-------------| | `-v`, `--verbose` | Print extra debug info for each evaluated expression. | ## `jade test` Discover and run test files matching `test_*.jde` or `*_test.jde` patterns. ```bash jade test jade test my_feature jade test --verbose ``` ### Flags | Flag | Description | |------|-------------| | `[pattern]` | Only run tests whose name contains this string. | | `-v`, `--verbose` | Show output from each test file. | ## `jade fmt` Format Jade source files. Works on a single file or all `.jde` files in a directory recursively. ```bash jade fmt program.jde jade fmt src/ jade fmt src/ --check ``` ### Flags | Flag | Description | |------|-------------| | `--check` | Exit with code 1 if any file would be changed (useful for CI). | ## `jade env` Show the Jade environment: version, config file location, cache directory, and project info. ```bash jade env jade env --json ``` ### Flags | Flag | Description | |------|-------------| | `--json` | Output environment information as JSON. | ## `jade upgrade` Download and install the latest Jade release, replacing the current binary in place. ```bash jade upgrade ``` Checks the GitHub releases page, compares the latest version against the running version, downloads the correct prebuilt binary for your platform, and atomically replaces the current executable. If the binary is in a system directory, re-run with `sudo jade upgrade`. ## `jade cache` Manage the build cache. The cache stores compiled AST and bytecode to skip redundant compilation passes. ```bash jade cache info jade cache clean jade cache clean --older-than 30 jade cache clean --dry-run ``` ### Subcommands | Subcommand | Description | |------------|-------------| | `info` | Show cache statistics (entry count, total size). | | `clean` | Remove stale or old cache entries. | ### clean Flags | Flag | Description | |------|-------------| | `--older-than ` | Also remove entries older than this many days. | | `--dry-run` | Show what would be removed without deleting. | ## `jade model` Manage LLM model configuration. ```bash jade model list jade model use anthropic/claude-3-5-sonnet ``` ### Subcommands | Subcommand | Description | |------------|-------------| | `list` | List known LLM models by provider. | | `use ` | Set the default model, writing to `~/.jade/config.toml`. | ## `jade configure` Run the interactive configuration wizard to set up an LLM backend. Stores provider, model, and API key in `~/.jade/config.toml`. The `JADE_API_KEY` environment variable can also be used to supply a key at runtime. ```bash jade configure ``` ## Backward-Compatible File Execution The old `jade ` form (without the `run` subcommand) is still accepted as a hidden shorthand. It dispatches to `jade run ` internally. Prefer `jade run` in new scripts and documentation. ```bash jade program.jde # equivalent to: jade run program.jde jade program.jde -v # equivalent to: jade run program.jde --verbose ``` ## Error Output Errors are written to stderr with a source location prefix: ``` [line:col] error description ``` For example: `[3:5] undefined variable 'x'`. The phase that produced the error (lexer, parser, or evaluator) is implicit in the error message text. --- # Changelog ## v1.1.12 - Expanded the built-in `llm` package to expose the inference daemon's model profiles, tool-call helpers, protocol controls, and health to Jade programs. The package stays decoupled from the daemon — the Unix socket (`~/.jade/llm.sock`) is the only contract; jadelang implements the wire format itself, drift-guarded by a golden-bytes test - Added **model profile** introspection — `llm.model()` returns the active model name; `llm.profile()` returns the model's token/tool vocabulary (tool-call delimiters, name field, special-token spans) as a dict. Profiles are selected by the model name the daemon reports - Added **tool-call helpers** — `llm.find_tool_call(text)` returns the first tool call in a response as `{name, args}` (or `nil`); `llm.find_tool_calls(text)` returns all of them; `llm.tool_grammar()` returns the canonical tool-call GBNF. All resolve tool-call delimiters from the active model's profile, so they work across models. The canonical grammar is checked in at `grammars/tool_call.gbnf` - Added **protocol controls** — the wire request now carries `keep_anchors` (toggle via `llm.keep_anchors(b)`, making tool-span boundaries observable in-band) and `trust` (prompt provenance), matching the daemon's request schema - Added **daemon lifecycle** — `llm.health()` returns a daemon health snapshot (`status`, `model`, `model_loaded`, `uptime_secs`, `protocol_version`) via a new `health` op and structured-JSON response frame ## v1.1.11 - Improved type inference for values read out of a dict. A `let`-bound homogeneous dict literal now records its value type, so indexing it (`d["k"]`) infers that concrete type instead of `Unknown`. This lets the native (AOT) backend pick the right print/format codegen for, e.g., `bool` values stored in a dict; the VM is unaffected (it dispatches on runtime tags) - Fixed a regression in unary `!` type inference. The v1.1.10 logical-operator fix typed *every* `!expr` as `bool`, which incorrectly accepted `!` on a known non-`bool` operand such as `!1` (this should be a `TypeError`). `!x` now short-circuits to `bool` only when the operand type is `Unknown` (e.g. `!method_call(x)` on an untyped value, where native codegen emits an `i1`); a known non-`bool` operand once again reports a `TypeError`. `&&` and `||` are unaffected — they continue to yield `bool` whenever an operand is `Unknown` ## v1.1.10 - Fixed a native build failure (LLVM verification error) when a function returns a logical expression with an untyped operand — `!x`, `a && b`, and `a || b` are now always typed as `bool` (matching the `i1` codegen emits), even when an operand is `Unknown` such as a method call on an untyped parameter. Previously these inferred `int`, mismatching the generated function signature. Mirrors the earlier comparison-operator fix ## v1.1.9 - **Breaking:** module-path imports now use `::` as the separator instead of `.` — `use std::math`, `from std::math use floor`, `use utils::math` for `[lib]` libraries. The `.` form is no longer accepted in module-path position (`.` is reserved for field and method access on values); `use std.math` is now a parse error - Namespaced decorators also use `::` — `@tools::register` instead of `@tools.register` - Quoted file-path imports (`use "lib.jde" as lib`) are unchanged - Added `null` as a third spelling of `nil` — `nil`, `None`, and `null` are interchangeable aliases for the same value; they compare equal and may be used as literals, default parameter values, and type annotations ## v1.1.8 - Native code generation moved out of the `jade` binary — `jade build` now runs the language frontend (lex → parse → type-infer → typed IR) and hands the typed program to the **build daemon** over `$HOME/.jade/build.sock`, which performs import resolution, code generation, and linking. The in-process LLVM backend and the `llvm` Cargo feature were removed; `jade env` now reports build-daemon reachability instead of LLVM status - Stdlib package imports must now use dot notation — `use std.math`, `use std.fs`, etc.; string-literal forms (`use "std/math"`) are now a compile-time error. Applies to both `use` and `from … use` forms - File-path imports now require an alias — `use "lib.jde" as lib`; bare string imports without `as name` are now a compile-time error - Native packages declared in `jade.toml [native]` now require an `alias` field specifying the global binding name - Fixed: functions exported from imported modules can now access stdlib packages the module imported (e.g. `use std.fs` in a module is visible when module functions are called in the parent scope) - Improved error messages — type errors now include the actual type of the offending value; heterogeneous array literals, nested function definitions, and non-string prompt struct fields each emit a dedicated error - Added empty struct test coverage (`struct Unit {}`) ## v1.1.7 - Added `std/sh` package — execute shell commands from Jade via `sh.exec`, `sh.run`, and `sh.output` - Added `std/json` package — parse JSON strings into Jade values and serialize Jade values back to JSON with `json.parse`, `json.stringify`, and `json.stringify_pretty` - Added `std/env` package — read and write environment variables (`env.get`, `env.set`), inspect command-line arguments (`env.args`), and get the working directory (`env.cwd`) - Added `std/path` package — cross-platform path manipulation: `path.join`, `path.basename`, `path.dirname`, `path.ext`, `path.stem`, `path.abs`, `path.is_abs` - Added `std/random` package — random number generation with `random.int`, `random.float`, `random.choice`, `random.shuffle`, and a seedable global RNG via `random.seed` ## v1.1.6 - Added `input(prompt?)` built-in — reads a line from stdin; the optional `prompt` argument prints to stdout without a trailing newline before reading. Returns an empty string on EOF. - Added `write(str)` built-in — prints to stdout without a trailing newline and flushes immediately (complements `print`, which adds `\n`) - Fixed array mutation semantics — mutations to an array are now visible through all aliases (reference semantics); previously mutations did not propagate to other variables pointing at the same array - Added `llm.set_max_tokens(n)` via `use "llm"` — configure the maximum token limit for LLM inference at runtime - Extended LLVM native codegen: typed `try`/`catch` arms and struct method calls (`obj.method(args)`) now compile and run correctly in native binaries ## v1.1.5 - Added single-quote string literals — `'hello'` and `'''triple'''` are now equivalent to their double-quote forms; `f'…{expr}…'` f-strings work too - Fixed `jade.toml` config loading — a config-only file with only a `[model]` section (no `[project]`) is now correctly picked up - Added `jade upgrade` command — downloads and atomically replaces the binary from the latest GitHub release ## v1.1.4 - Added `async fn` definitions and `await` expressions — concurrent LLM inference via `await` on prompt dereferences - Added Jade OS as a supported LLM backend provider - Added comprehensive error handling for async tasks — panics from spawned tasks produce `AsyncPanic` errors with source location - Switched TLS backend to `rustls` (no OpenSSL dependency) ## v1.1.3 - Added official install script at `https://jadelang.org/install.sh` — detects OS and architecture, downloads the correct prebuilt binary, and installs to `/usr/local/bin/jade` - Added Windows prebuilt binary: `jade-windows-x86_64.exe` available from the GitHub Releases page - Updated documentation installation page to document the install script and Windows download path ## v1.0.9 - Added `try`/`catch`/`raise` exception handling — raise any value as an exception, catch by struct type name or with a catch-all arm, nested `try`/`catch` blocks, built-in runtime errors (division by zero, type errors, etc.) are automatically catchable - Upgraded CLI to full subcommand structure: `jade run`, `jade check`, `jade build`, `jade repl`, `jade test`, `jade fmt`, `jade env`, `jade cache`, `jade model`, `jade new`, `jade init` - Fixed implicit function return: the last bare expression in a function body is now returned automatically without needing an explicit `return` keyword ## v1.0.8 - Added anonymous closures: `|x| x * 2` (inline expression body) and `|x| { … }` (block body) with environment capture at creation time - Added `for` loops: `for x in array { … }` iteration over arrays (via bytecode VM) - Added `dict` type: dictionary literals (`{"key": value}`), key access (`d["key"]`), key assignment, and `len` support - Added `use "path.jde"` for multi-file imports - Added bytecode compiler and VM — programs now run through type inference, bytecode emission, and a register-based VM - Added multi-level AST and TIR caching to skip redundant compilation passes ## v1.0.7 - Added `str` type: string literals, triple-quoted strings, concatenation with `+`, character indexing, equality and lexicographic ordering - Added f-string interpolation: `f"…{expr}…"` and `f"""…{expr}…"""` - Added array literals (`[1, 2, 3]`), index access (`arr[i]`), and index assignment (`arr[i] = expr`) - Added `print` and `len` built-in functions - Added pipe operator `|>` for chaining function calls - Added `interface` definitions and `extend Type: Interface` conformance checking - Added `elif` clause for chained conditionals - Added `jade configure` command for LLM backend configuration - Added `prompt` declarations and `?` dereference for LLM inference ## v1.0.6 - Added `struct` definitions with named fields, field access, and field mutation - Added `extend` blocks for attaching methods, with `self` binding - Added bare variable assignment (`x = expr`) - Added `while` loops with boolean condition ## v1.0.5 - Added `struct` definitions with named fields - Added struct instantiation with `TypeName { field: value, … }` literals - Added field access (`obj.field`) and field mutation (`obj.field = expr`) - Added `extend` blocks for attaching methods to struct types - Added method calls (`obj.method(args)`) with automatic `self` binding - Added bare variable assignment (`x = expr`) as an alternative to `let` rebinding ## v1.0.4 - Added `while` loops with boolean condition ## v1.0.3 - Added `fn` definitions with parameter lists and `return` - Added function calls as first-class expressions - Added first-class function values — functions can be assigned to variables and passed as arguments - Added recursion — functions can call themselves - Added `if`/`else` control flow ## v1.0.2 - Modulus operator: `%` - Bitwise operators: `&`, `|`, `^`, `<<`, `>>` - Unary bitwise NOT: `~` - Float literals (`f64`) and unary negation for floats - Boolean literals: `true`, `false` - Logical operators: `&&`, `||`, `!` with short-circuit evaluation - Comparison operators: `==`, `!=`, `<`, `>`, `<=`, `>=` - Runtime errors: remainder by zero, invalid shift amount (negative or ≥ 64) ## v1.0.1 - Initial interpreter release written in Rust - `let` variable declarations with arithmetic expressions - Operators: `+`, `-`, `*`, `/` - Automatic semicolon insertion — no semicolons required - Runtime errors: undefined variable, division by zero - CLI: `jade `, `--verbose`, `--help`