RILL LANGUAGE REFERENCE FOR LLM AGENTS
=======================================

Rill is a pipe-based scripting language. Data flows through pipes (->), not assignment (=).

NAMING CONVENTION: snake_case
-----------------------------
Use snake_case for all identifiers:
  $user_name, $item_list, $is_valid    # variables
  $double_value, $cleanup_text         # closures
  [first_name: "x", last_name: "y"]    # dict keys

WHY VARIABLES USE $ PREFIX
--------------------------
The $ prefix enables single-pass parsing without a symbol table:

  name()    -> host function call
  $name()   -> closure invocation
  $name     -> variable reference
  name      -> dict key literal

Without $, "process(data)" is ambiguous: is process a host function or stored
closure? Is data a variable or key? This would require tracking all declarations.

Additional disambiguation:
  - Capture: :> $x requires $ for lookahead (avoids conflict with slice /<1:>)
  - Destructure: *<$a, $b> marks variables vs skip patterns
  - Dynamic access: $data.$key distinguishes variable-key from literal field
  - Visual clarity: $total is always a variable, no context needed

This follows rill's "no magic" principle: syntax communicates intent explicitly.

SPACING RULES
-------------
Operators:    space both sides       5 + 3, $x -> .upper, "a" :> $b
Parentheses:  no inner space         ($x + 1), ($ > 3) ? "yes"
Braces:       space inside           { $x + 1 }, each { $ * 2 }
Brackets:     no inner space         $list[0], $dict.items[1]
Literals:     space after , and :    [1, 2, 3], [name: "x", age: 30]
Closures:     space after params     |x| ($x * 2), |a, b| { $a + $b }
Methods:      no space before . or ( $str.upper(), $list.join(", ")
Pipes:        space both sides       "x" -> .upper -> .len
Continuations: indent 2 spaces       $data
                                       -> .filter { $.active }
                                       -> map { $.name }

IMPLICIT $ SHORTHAND (always prefer)
------------------------------------
$.method()  ->  .method            "x" -> .upper  (not $.upper())
func($)     ->  func               "x" -> log     (not log($))
$fn($)      ->  $fn                5 -> $double   (not $double($))

NO THROWAWAY CAPTURES
---------------------
Don't capture just to continue - use line continuation instead:
  # avoid                          # good
  "x" :> $a                        "x"
  $a -> .upper :> $b                 -> .upper
  $b -> .len                         -> .len

Only capture when the variable is reused later in the code.

CRITICAL DIFFERENCES FROM MAINSTREAM LANGUAGES
----------------------------------------------

1. NO ASSIGNMENT OPERATOR
   Wrong: x = 5
   Right: 5 :> $x

   Pipe (->): passes value to next operation
   Capture (:>): stores value AND continues chain
   Example: "hello" :> $x -> .upper :> $y   # $x="hello", $y="HELLO"

2. NO NULL/UNDEFINED
   Empty values ("", [], [:]) exist. "No value" cannot be represented.
   Use ?? for defaults: $dict.field ?? "default"
   Use .empty to check: $str -> .empty ? "was empty"

3. NO TRUTHINESS
   Conditions MUST be boolean. No implicit coercion.
   Wrong: "" ? "yes" ! "no"
   Right: "" -> .empty ? "yes" ! "no"
   Wrong: 0 ? "yes" ! "no"
   Right: (0 == 0) ? "yes" ! "no"

4. VARIABLES LOCK TO FIRST TYPE
   "hello" :> $x
   42 :> $x        # ERROR: cannot assign number to string variable

5. NO VARIABLE SHADOWING (CRITICAL FOR LOOPS)
   Child scopes can READ parent variables but cannot WRITE or redeclare them.
   Variables created inside blocks/loops do NOT leak out.

   WRONG - this pattern NEVER works:
     0 :> $count
     [1, 2, 3] -> each { $count + 1 :> $count }  # creates LOCAL $count
     $count                                       # still 0!

   RIGHT - use $ or $@ as state carrier (see LOOP STATE PATTERNS below)

6. NO EXCEPTIONS
   Errors halt execution. No try/catch. Use conditionals for error handling.
   Built-in: assert (validate condition), error (halt with message).

7. VALUE SEMANTICS (no references)
   All copies are deep. All comparisons are by value. No object identity.
   [1, 2, 3] == [1, 2, 3]   # true (content equality)
   [1, 2] :> $a
   $a :> $b                  # $b is an independent deep copy
   Mainstream habit to avoid: expecting two variables to share the same object.

SYNTAX QUICK REFERENCE
----------------------

Variables:     $name (always prefixed with $)
Strings:       "hello {$var}"          # interpolation with {}
               """..."""               # multiline (also interpolates)
Numbers:       42, 3.14, -7
Booleans:      true, false
Lists:         [1, 2, 3]
Dicts:         [name: "alice", age: 30]    # identifier keys
               [1: "one", 2: "two"]        # number keys (incl. negative: [-1: "neg"])
               [true: "yes", false: "no"]  # boolean keys
Tuples:        *[1, 2, 3]              # for argument unpacking
Closures:      |x|($x + 1)             # like lambda/arrow functions
Type annot:    "hi" :> $x:string       # lock type on capture
Comments:      # single line only

PIPES AND $ BINDING
-------------------

$ is the current piped value. Its meaning depends on context:

| Context                    | $ contains              |
|----------------------------|-------------------------|
| -> { body }                | piped value             |
| -> each { }                | current item            |
| (cond) @ { }               | accumulated value       |
| @ { } ? cond               | accumulated value       |
| cond ? { } ! { }           | tested value            |
| -> ? { } ! { }             | piped value             |
| ||{ $.field } in dict      | the containing dict     |
| |x|{ } stored closure      | N/A - use parameters    |

Implied $: bare .method() means $ -> .method()
Example: "hello" -> .upper   # same as "hello" -> $.upper()

CONTROL FLOW
------------

Conditional (if-else):
  cond ? then_expr ! else_expr
  cond ? then_expr                    # else returns ""

Piped conditional ($ becomes condition):
  value -> ? then_expr ! else_expr

Condition loop (NO "while" keyword - use @ operator):
  init_value -> ($ < 10) @ { $ + 1 }  # $ is accumulator

Do-condition loop (body runs at least once):
  init_value -> @ { $ + 1 } ? ($ < 10)

Break (exits loop, returns collected results before break):
  [1,2,3,4,5] -> each { ($ == 3) ? break; $ }  # returns [1, 2]

Return (exits block or script with value):
  { 5 :> $x; ($x > 3) ? ("big" -> return); "small" }  # returns "big"
  "done" -> return                                     # exits script with "done"

Assert (validate condition, halt if false, pass through if true):
  5 -> assert ($ > 0)                   # returns 5
  -1 -> assert ($ > 0)                  # ERROR: Assertion failed
  "" -> assert !.empty "Input required" # ERROR: Input required
  $val -> assert $:?list "Expected list" # type validation

Error (halt execution immediately with message):
  error "Something went wrong"           # halt with message
  "Operation failed" -> error            # piped form (must be string)
  error "Status: {$code}"               # interpolation works

LOOP STATE PATTERNS (CRITICAL)
------------------------------
Rill loops CANNOT modify outer variables. All state must flow through $ or $@.

WRONG - outer variable modification (NEVER works):
  0 :> $sum
  [1, 2, 3] -> each { $sum + $ :> $sum }  # $sum unchanged!

WRONG - "while" keyword does not exist:
  while ($i < 10) { $i + 1 :> $i }        # SYNTAX ERROR

RIGHT - use fold for reduction:
  [1, 2, 3] -> fold(0) { $@ + $ }         # 6 ($@ is accumulator)

RIGHT - use each(init) when you need both results AND accumulator:
  [1, 2, 3] -> each(0) { $@ + $ }         # [1, 3, 6] (running totals)

RIGHT - use (cond) @ { } with $ as state dict for multiple values:
  [iter: 0, max: 3, text: $input, done: false]
    -> (!$.done && $.iter < $.max) @ {
      $.iter + 1 :> $i
      process($.text) :> $result
      $result.finished ? [iter: $i, max: $.max, text: $.text, done: true]
                       ! [iter: $i, max: $.max, text: $result.text, done: false]
    }
  # Access final state: $.text, $.iter

Pattern summary:
  - Single value accumulation     -> fold(init) { $@ + $ }
  - Per-item results + running    -> each(init) { ... $@ ... }
  - Multiple state values / while -> (cond) @ { } with $ as state dict
  - "while" and "for" keywords    -> DO NOT EXIST

COLLECTION OPERATORS
--------------------

| Operator           | Execution  | Returns              | Break? |
|--------------------|------------|----------------------|--------|
| -> each { }        | sequential | all body results     | yes    |
| -> each(i) { $@+$} | sequential | all with accumulator | yes    |
| -> map { }         | parallel   | all body results     | NO     |
| -> filter { }      | parallel   | matching elements    | NO     |
| -> fold(i) { $@+$} | sequential | final result only    | yes    |

$@ is the accumulator in each(init) and fold(init).

Method shorthand in collection operators:
  ["a", "b"] -> map .upper            # ["A", "B"]
  ["", "x"] -> filter (!.empty)       # ["x"]
  ["a", "b"] -> map .pad_start(3, "0") # ["00a", "00b"] (with args)
  ["  HI  "] -> map .trim.lower       # ["hi"] (chained methods)

Body forms (all operators accept these):
  -> each { $ * 2 }                   # block ($ is current element)
  -> each ($ + 10)                    # grouped expression
  -> each |x| ($x * 2)               # inline closure
  -> each $double                     # variable closure
  -> each .upper                      # method shorthand

Dict iteration ($ contains key and value fields):
  [a: 1, b: 2] -> each { "{$.key}={$.value}" }   # ["a=1", "b=2"]
  [a: 1, b: 5] -> filter { $.value > 2 }          # entries where value > 2

String iteration (iterates over characters):
  "abc" -> each { "{$}!" }            # ["a!", "b!", "c!"]
  "hello" -> filter { $ != "l" }      # ["h", "e", "o"]

CLOSURES
--------

BLOCK-CLOSURES vs EXPLICIT CLOSURES:

Two ways to create closures:

1. Block-closures: { body } in expression position
   { $ + 1 } :> $inc                  # implicit $ parameter
   $inc(5)                            # 6
   5 -> $inc                          # 6 (pipe invocation)
   [x: { $ * 2 }]                     # dict value is closure
   type({ "hi" })                     # "closure"

2. Explicit closures: |params| body
   |x|($x + 1) :> $inc                # named parameter
   |a, b|($a + $b) :> $add            # multiple params
   |x = 0|($x + 1) :> $inc_or_one     # default value
   |x: number|($x + 1) :> $typed      # type annotation

CRITICAL: { } vs ( ) distinction

| Syntax       | Semantics              | Example                    |
|--------------|------------------------|----------------------------|
| { body }     | Deferred (closure)     | { $ + 1 } :> $fn  # closure |
| ( expr )     | Eager (immediate eval) | ( 5 + 1 ) :> $x  # 6        |

When to use:
  { body } :> $fn     # store closure for later use
  ( expr ) :> $x      # store result value immediately

PIPE TARGET: { } creates closure then immediately invokes it:
  5 -> { $ + 1 }      # 6 (create closure, invoke with 5)
  5 -> ($ + 1)        # 6 (evaluate expression with $=5)
  Same observable result, different mechanism. Error messages differ.

Block-closure invocation:
  { $ + 1 } :> $inc
  $inc(5)                             # direct call: 6
  5 -> $inc                           # pipe call: 6
  [1,2,3] -> map $inc                 # in collection op

LATE BINDING: closures capture scope, not values. Variables resolve at call time.

$ vs named params:
  Use $ in inline pipes and loops:     "hello" -> { .upper }
  Use named params in stored closures: |x| ($x * 2) :> $double
  $ is undefined when a stored closure is called later — always use params.

Zero-param dict closures (methods):
  [count: 3, double: ||{ $.count * 2 }] :> $obj
  $obj.double                         # 6 ($ is bound to dict)

PROPERTY ACCESS
---------------

$data.field                           # dict field
$data[0], $data[-1]                   # list index (negative from end)
$data.$key                            # variable as key
$data.($i + 1)                        # computed key
$data.(a || b)                        # try keys left-to-right
$data.field ?? "default"              # default if missing
$data.?field                          # existence check (boolean)
$data.?field&string                   # existence AND type check

DISPATCH OPERATORS
------------------

DICT DISPATCH (single key):
Pipe a value to a dict to match keys and return associated values:
  $val -> [apple: "fruit", carrot: "veg"]        # returns "fruit" if $val is "apple"
  $val -> [apple: "fruit"] ?? "not found"         # default if no match
  $method -> [["GET", "HEAD"]: "safe", ["POST", "PUT"]: "unsafe"]  # multi-key

Type-aware matching (keys matched by value AND type):
  1 -> [1: "number", "1": "string"]              # "number" (number key matches)
  "1" -> [1: "number", "1": "string"]            # "string" (string key matches)
  true -> [true: "bool", "true": "str"]          # "bool" (boolean key matches)

LIST DISPATCH (index):
Pipe a number to a list to get element at index:
  0 -> ["first", "second"]                        # "first"
  -1 -> ["first", "second"]                       # "second" (last)
  5 -> ["a", "b"] ?? "not found"                  # default if out of bounds

HIERARCHICAL DISPATCH (path navigation):
Pipe a list of keys/indexes to navigate nested structures:
  ["name", "first"] -> [name: [first: "Alice"]]   # "Alice" (dict path)
  [0, 1] -> [[1, 2, 3], [4, 5, 6]]                 # 2 (list path)
  ["users", 0, "name"] -> [users: [[name: "Alice"]]] # "Alice" (mixed)
  [] -> [a: 1]                                    # [a: 1] (empty path = unchanged)
  ["a", "missing"] -> [a: [x: 1]] ?? "default"    # "default" (missing key)

Path elements: strings for dict keys, numbers for list indexes (negative supported).
Terminal closures receive $ bound to final path key:
  ["req", "draft"] -> [req: [draft: { "key={$}" }]]  # "key=draft"

TYPE OPERATIONS
---------------

:type   - assert type (error if wrong): 42:number passes; "x":number errors
:?type  - check type (boolean): 42:?number is true; "x":?number is false

Types: string, number, boolean, list, dict, tuple, closure

COMPARISON METHODS
------------------

Methods for readable comparisons in conditionals:
  .eq(val)   ==          $v -> .eq("A") ? "match"
  .ne(val)   !=          $v -> .ne("") ? "has value"
  .lt(val)   <           $v -> .lt(10) ? "small"
  .gt(val)   >           5 -> .gt(3) ? "big"
  .le(val)   <=          10 -> .le(10) ? "ok"
  .ge(val)   >=          $age -> .ge(18) ? "adult"

OPERATOR PRECEDENCE (highest to lowest)
---------------------------------------

1.  Member access:   .field, [index]
2.  Type operators:  :type, :?type
3.  Unary:           -, !
4.  Multiplicative:  *, /, %
5.  Additive:        +, -
6.  Comparison:      ==, !=, <, >, <=, >=
7.  Logical AND:     &&
8.  Logical OR:      ||
9.  Default:         ??
10. Pipe:            ->
11. Capture:         :>

Use parentheses to override: (2 + 3) * 4

EXTRACTION OPERATORS
--------------------

Destructure (*<>):
  [1, 2, 3] -> *<$a, $b, $c>          # $a=1, $b=2, $c=3
  [x: 1, y: 2] -> *<x: $a, y: $b>     # $a=1, $b=2
  [1, 2, 3] -> *<$first, _, $third>   # _ skips element

Slice (/<start:stop:step>):
  [0,1,2,3,4] -> /<1:3>               # [1, 2]
  [0,1,2,3,4] -> /<-2:>               # [3, 4]
  [0,1,2,3,4] -> /<::-1>              # [4,3,2,1,0] (reverse)
  "hello" -> /<1:4>                   # "ell"

TUPLES FOR ARGUMENT UNPACKING
-----------------------------

*[1, 2, 3] -> $fn()                   # positional: $fn(1, 2, 3)
*[b: 2, a: 1] -> $fn()                # named: $fn(a=1, b=2)

SPREAD OPERATOR (@)
-------------------

Chains closures sequentially:
  5 -> @[$inc, $double, $add10]       # (5+1)*2+10 = 22

STRING METHODS
--------------

.len            length
.empty          is empty string
.trim           remove whitespace
.upper          uppercase
.lower          lowercase
.split(sep)     split into list
.lines          split on newlines
.join(sep)      join list with separator (on list)
.contains(s)    substring check
.starts_with(s) prefix check
.ends_with(s)   suffix check
.replace(p,r)   replace first match
.replace_all(p,r) replace all matches
.match(regex)   first match info (dict with matched, index, groups)
.is_match(regex) boolean regex check
.head           first character
.tail           last character
.at(i)          character at index
.index_of(s)    position of first match (-1 if none)
.repeat(n)      repeat string n times: "ab" -> .repeat(3)  # "ababab"
.pad_start(n,f) pad start to length: "42" -> .pad_start(5, "0")  # "00042"
.pad_end(n,f)   pad end to length: "42" -> .pad_end(5, "0")  # "42000"
.str            convert any value to string: 42 -> .str  # "42"
.num            parse string to number: "42" -> .num  # 42

LIST/DICT METHODS
-----------------

.len            length
.empty          is empty
.head           first element
.tail           last element
.at(i)          element at index
.has(val)       check if list contains value (deep equality)
.has_any(list)  check if list contains any value from candidates
.has_all(list)  check if list contains all values from candidates
.keys           dict keys as list
.values         dict values as list
.entries        dict as list of [key: k, value: v]

PARSING FUNCTIONS (for LLM output)
----------------------------------

parse_auto(str)           auto-detect format
parse_json(str)           parse JSON (repairs common errors)
parse_xml(str, tag?)      extract XML tag content
parse_fence(str, lang?)   extract fenced code block
parse_fences(str)         all fenced blocks as list
parse_frontmatter(str)    parse --- delimited YAML + body
parse_checklist(str)      parse markdown task lists

GLOBAL FUNCTIONS
----------------

type(val)                 returns type name
log(val)                  print and pass through
json(val)                 convert to JSON string
identity(val)             returns input unchanged
range(start, end, step?)  number sequence
repeat(val, count)        repeat value n times
enumerate(coll)           add index to elements

ITERATORS
---------

Lazy sequence generation. Collection operators auto-expand iterators.

Built-in iterators:
  range(0, 5) -> each { $ * 2 }        # [0, 2, 4, 6, 8]
  repeat("x", 3) -> each { $ }         # ["x", "x", "x"]

.first() method (returns iterator for any collection):
  [1, 2, 3] -> .first()                # iterator at 1
  "abc" -> .first()                     # iterator at "a"
  [] -> .first()                        # [done: true, ...]

Iterator protocol (dict with value, done, next):
  $it.done                              # bool: is exhausted?
  $it.value                             # current element
  $it.next()                            # returns new iterator at next position

ITERATION LIMITS
----------------

Default: 10,000 iterations max for loops.
Override: ^(limit: N) statement

Example:
  ^(limit: 100) 0 -> ($ < 50) @ { $ + 1 }

Concurrency limit for map (controls parallel concurrency):
  ^(limit: 3) $items -> map { slow_process($) }

COMMON MISTAKES
---------------

1. Using = for assignment          -> use :> instead
2. Using || for defaults           -> use ?? instead
3. Assuming truthiness             -> explicit boolean checks required
4. Breaking from map/filter        -> only works in each/fold
5. Modifying outer vars in loops   -> use fold/$@ or $ as state dict (see LOOP STATE PATTERNS)
6. Expecting variables to leak     -> block scope is strict
7. Forgetting () on methods        -> .upper() not .upper (unless property)
8. Reassigning different type      -> variables lock to first type
9. Using while/for keywords        -> use (cond) @ { } or -> each { } instead

SCRIPT RETURN VALUES
--------------------

true / non-empty string -> exit code 0
false / empty string    -> exit code 1
[0, "message"]          -> exit code 0 with message
[1, "message"]          -> exit code 1 with message

MAINSTREAM TO RILL TRANSLATION
------------------------------

| Mainstream               | Rill                                      |
|--------------------------|-----------------------------------------  |
| x = value                | value :> $x                               |
| null / undefined         | ?? default, .? existence check             |
| if "" (truthiness)       | .empty, == 0, :?type                      |
| try { } catch { }        | assert, conditionals, error                |
| for (i = 0; ...)         | each, map, filter, fold                   |
| count += 1 in loop       | fold(0) { $@ + 1 } or $ accumulator      |
| a === b (reference)      | == always compares by value               |
| a = b (shared ref)       | :> always deep-copies                     |
