fusion-lang 0.0.1.alpha1 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -6
  3. data/Rakefile +9 -0
  4. data/docs/lang/design.md +418 -28
  5. data/docs/lang/implementation.md +238 -0
  6. data/docs/lang/roadmap.md +20 -57
  7. data/docs/user/explanation.md +5 -10
  8. data/docs/user/how-to-guides.md +62 -23
  9. data/docs/user/reference.md +596 -168
  10. data/docs/user/tutorial.md +32 -29
  11. data/examples/double.fsn +1 -1
  12. data/examples/ends.fsn +4 -0
  13. data/examples/factorial.fsn +2 -2
  14. data/examples/fizzbuzz.fsn +1 -4
  15. data/examples/json_test.fsn +4 -0
  16. data/examples/palindrome.fsn +2 -1
  17. data/exe/fusion +17 -44
  18. data/lib/fusion/ast.rb +97 -0
  19. data/lib/fusion/atom.rb +17 -0
  20. data/lib/fusion/cli/decoder.rb +84 -0
  21. data/lib/fusion/cli/encoder.rb +28 -0
  22. data/lib/fusion/cli/options.rb +212 -0
  23. data/lib/fusion/cli/parser.rb +38 -0
  24. data/lib/fusion/cli/repl.rb +78 -0
  25. data/lib/fusion/cli/serializer.rb +70 -0
  26. data/lib/fusion/cli.rb +207 -0
  27. data/lib/fusion/interpreter/builtins.rb +465 -0
  28. data/lib/fusion/interpreter/env.rb +89 -0
  29. data/lib/fusion/interpreter/error_val.rb +71 -0
  30. data/lib/fusion/interpreter/func.rb +22 -0
  31. data/lib/fusion/interpreter/native_func.rb +22 -0
  32. data/lib/fusion/interpreter/thunk.rb +53 -0
  33. data/lib/fusion/interpreter.rb +752 -0
  34. data/lib/fusion/lexer.rb +249 -0
  35. data/lib/fusion/null.rb +9 -0
  36. data/lib/fusion/parser.rb +542 -0
  37. data/lib/fusion/token.rb +22 -0
  38. data/lib/fusion/typed_data.rb +23 -0
  39. data/lib/fusion/version.rb +1 -1
  40. data/lib/fusion/wire_pair.rb +11 -0
  41. data/lib/fusion.rb +11 -1122
  42. data/stdlib/all.fsn +13 -0
  43. data/stdlib/any.fsn +12 -0
  44. data/stdlib/chars.fsn +5 -0
  45. data/stdlib/compact.fsn +6 -0
  46. data/stdlib/concat.fsn +5 -0
  47. data/stdlib/falsey.fsn +6 -0
  48. data/stdlib/filter.fsn +12 -0
  49. data/stdlib/flatten.fsn +7 -0
  50. data/stdlib/gt.fsn +9 -0
  51. data/stdlib/gte.fsn +9 -0
  52. data/stdlib/lt.fsn +9 -0
  53. data/stdlib/lte.fsn +9 -0
  54. data/stdlib/map.fsn +6 -2
  55. data/stdlib/range.fsn +2 -1
  56. data/stdlib/reduce.fsn +8 -0
  57. data/stdlib/sanitize.fsn +12 -0
  58. data/stdlib/truthy.fsn +7 -0
  59. metadata +41 -2
  60. data/stdlib/math/square.fsn +0 -1
@@ -41,13 +41,19 @@ Precedence, tightest to loosest:
41
41
 
42
42
  1. Primary: literals, `[...]`, `{...}`, `(...)` grouping, function literals,
43
43
  identifiers, `@`-references.
44
- 2. Postfix member/index access: `x.key`, `x[expr]`.
45
- 3. Error prefix: `!expr` (construct an error). Bare `!` (with no operand) is the
46
- same as `!null`.
47
- 4. Pipe (application): `value | function`. Left-associative
48
- (`a | f | g` ≡ `(a | f) | g`).
49
- 5. Clause arrow: `=>` (loosest, so the entire right-hand side of a clause is one
50
- expression).
44
+ 2. Postfix: member access `x.key`, index read `x[expr]`, index write `x[key = value]`.
45
+ 3. Unary prefix: `!x` (construct an error; bare `!` is `!null`), `-x` (negate),
46
+ `/x` (invert), `~x` (logical not).
47
+ 4. Pipe (application) and map-pipes: `value | function`, `|:` (map), `|?` (filter),
48
+ `|+` (reduce). Left-associative (`a | f | g` ≡ `(a | f) | g`).
49
+ 5. Multiplicative: `*`, `/`, `%`, `//`. Left-associative.
50
+ 6. Additive: `+`, `-`. Left-associative.
51
+ 7. Ordering: `??`. Left-associative.
52
+ 8. Equality: `==`.
53
+ 9. Logical and: `&&`.
54
+ 10. Logical or: `||`.
55
+ 11. Clause arrow: `=>` (loosest, so the entire right-hand side of a clause is one
56
+ expression).
51
57
 
52
58
  ### 2.2 Function literals
53
59
 
@@ -93,79 +99,148 @@ by the same rule.
93
99
  ### 2.5 Grammar (EBNF)
94
100
 
95
101
  ```ebnf
96
- file = expr ;
97
-
98
- expr = pipe ;
99
- pipe = prefix { "|" prefix } ;
100
- prefix = "!" [ prefix ] | postfix ; (* bare "!" -> !null *)
101
- postfix = primary { "." identifier | "[" expr "]" } ;
102
- primary = atom | array | object | function | identifier | fileref | "(" expr ")" ;
103
-
104
- fileref = "@" [ refpath ] ; (* bare "@" = current file *)
105
- refpath = { "../" } segment { "/" segment } ; (* ".fsn" implied *)
106
- segment = identifier ;
107
-
108
- atom = "null" | "true" | "false" | number | string ;
109
- (* errors are `!`+payload (see prefix) *)
110
- array = "[" [ elem { "," elem } [ "," ] ] "]" ;
111
- elem = expr | spread ;
112
- spread = "..." expr ;
113
- object = "{" [ member { "," member } [ "," ] ] "}" ;
114
- member = string ":" expr | spread ;
115
-
116
- function = "(" clause { "," clause } [ "," ] ")" ;
117
- clause = pattern "=>" expr ;
118
-
119
- pattern = errpat | guardedpat ;
120
- errpat = "!" | "!" guardedpat ; (* bare "!" matches any error, binds nothing *)
121
- guardedpat = corepat [ "?" predicate ] ;
122
- predicate = prefix ; (* any expression yielding a function *)
123
- corepat = literalpat | bindpat | wildcard | arraypat | objectpat ;
124
- literalpat = atom ;
125
- wildcard = "_" ;
126
- bindpat = identifier ;
127
- arraypat = "[" [ pelem { "," pelem } [ "," ] ] "]" ;
128
- pelem = guardedpat | "..." [ identifier ] ;
129
- objectpat = "{" [ pmember { "," pmember } [ "," ] ] "}" ;
130
- pmember = string ":" guardedpat | "..." [ identifier ] ;
131
-
132
- identifier = letter { letter | digit | "_" } ;
133
- number = int_lit | float_lit ;
134
- int_lit = [ "-" ] digit { digit } ;
135
- float_lit = int_lit "." digit { digit } [ exp ] | int_lit exp ;
136
- exp = ("e" | "E") [ "+" | "-" ] digit { digit } ;
137
- string = '"' { char | escape } '"' ; (* char excludes raw newline; use \n *)
102
+ file = expr ;
103
+
104
+ expr = logical_or ;
105
+ logical_or = logical_and { "||" logical_and } ;
106
+ logical_and = equality { "&&" equality } ;
107
+ equality = ordering { "==" ordering } ;
108
+ ordering = additive { "??" additive } ;
109
+ additive = multiplicative { ( "+" | "-" ) multiplicative } ;
110
+ multiplicative = pipe { ( "*" | "/" | "%" | "//" ) pipe } ;
111
+ pipe = unary { ( "|" | "|:" | "|?" | "|+" ) unary } ;
112
+ unary = "!" [ unary ] (* bare "!" -> !null; "!x" builds an error *)
113
+ | ( "-" | "/" | "~" ) unary (* negate / invert / not; operand required *)
114
+ | postfix ;
115
+ postfix = primary { "." identifier | "[" expr [ "=" expr ] "]" } ; (* "[e]" reads, "[e = e]" writes *)
116
+ primary = atom | array | object | function | identifier | fileref | "(" expr ")" ;
117
+
118
+ identifier = letter { letter | digit | "_" } ;
119
+
120
+ atom = "null" | "true" | "false" | number | string ;
121
+ number = int_lit | float_lit ; (* unsigned; a negative is unary "-" *)
122
+ int_lit = digit { digit } ;
123
+ float_lit = int_lit "." digit { digit } [ exp ] | int_lit exp ;
124
+ exp = ("e" | "E") [ "+" | "-" ] digit { digit } ;
125
+ string = '"' { char | escape } '"' ; (* char excludes raw newline; use \n *)
126
+
127
+ array = "[" [ item { "," item } [ "," ] ] "]" ;
128
+ item = expr | spread ;
129
+ object = "{" [ pair { "," pair } [ "," ] ] "}" ;
130
+ pair = string ":" expr | spread ;
131
+ spread = "..." expr ;
132
+
133
+ function = "(" [ clause { "," clause } [ "," ] ] ")" ; (* "()" is the empty function *)
134
+ clause = pattern "=>" expr ;
135
+
136
+ fileref = ( "@" | "@@" ) [ path ] ; (* bare "@"/"@@" = current unit / its super *)
137
+ path = { ".." "/" } segment { "/" segment } ; (* one tight lexer token; ".fsn" implied *)
138
+ segment = identifier ;
139
+
140
+ pattern = p_error | p_guarded ;
141
+ p_error = "!" | "!" p_guarded ; (* bare "!" matches any error, binds nothing *)
142
+ p_guarded = p_core [ "?" predicate ] ;
143
+ predicate = pipe ; (* a `|` chain of functions; the matched value flows in *)
144
+ p_core = p_literal | p_bind | p_wildcard | p_array | p_object ;
145
+ p_literal = atom | "-" number ; (* "-" number is a negative literal *)
146
+ p_wildcard = "_" ;
147
+ p_bind = identifier ;
148
+
149
+ p_array = "[" ( p_fixed_items | p_rest_items ) "]" ;
150
+ p_fixed_items = [ p_items [ "," ] ] ;
151
+ p_rest_items = [ p_items "," ] p_rest [ "," p_items ] [ "," ] ;
152
+ p_items = p_item { "," p_item } ;
153
+ p_item = p_guarded ;
154
+
155
+ p_object = "{" ( p_fixed_pairs | p_rest_pairs ) "}" ;
156
+ p_fixed_pairs = [ p_pairs [ "," ] ] ;
157
+ p_rest_pairs = [ p_pairs "," ] p_rest [ "," ] ;
158
+ p_pairs = p_pair { "," p_pair } ;
159
+ p_pair = string ":" p_guarded ;
160
+
161
+ p_rest = "..." [ identifier ] ;
162
+
138
163
  ```
139
164
 
165
+ ### 2.6 Context-sensitive rules the grammar cannot express
166
+
167
+ Object literals and object patterns may not repeat a fixed key. `{"a": …, "a": …}`
168
+ is a `syntax_error`. Keys arriving through `...spread` / `...rest` are dynamic and
169
+ not checked.
170
+
171
+ A file-reference **path** is a single token, lexed only immediately after `@` or `@@`
172
+ with no intervening whitespace: tight `/`-separated `segment`s (identifiers) with an
173
+ optional leading `../` chain. So a `/` that is not part of such a path is division/invert:
174
+ `@a/b` is the path `a/b`, but `@a / b` is `@a` divided by `b`. `//` is always the
175
+ integer-quotient operator, never a path separator.
176
+
177
+ There are no negative-number tokens: `-` is always the negation/subtraction operator.
178
+ A negative literal is written `-` directly before a number — the parser folds it into a
179
+ literal in expressions, and `p_literal` admits it directly in patterns.
180
+
181
+ ### 2.7 Operators (syntactic sugar)
182
+
183
+ Every operator here is **pure syntactic sugar**: it desugars to a pipe into an `@OP.*`
184
+ member (§7.6), or, for the map-pipes, into a stdlib call.
185
+
186
+ ```
187
+ -a → negative literal if a is a number, else a | @OP.negate
188
+ /a → a | @OP.invert
189
+ ~a → a | @OP.not
190
+ a + b + c → [a, b, c] | @OP.sum
191
+ a - b → [a, b | @OP.negate] | @OP.sum (numeric b folds: a - 42 → [a, -42] | @OP.sum)
192
+ a * b * c → [a, b, c] | @OP.product
193
+ a / b → [a, b | @OP.invert] | @OP.product
194
+ a % b → [a, b] | @OP.modulo
195
+ a // b → [a, b] | @OP.quotient
196
+ a ?? b → [a, b] | @OP.compare
197
+ a == b == c → [a, b, c] | @OP.equal
198
+ a && b && c → [a, b, c] | @OP.and
199
+ a || b || c → [a, b, c] | @OP.or
200
+ xs |: f → {"c": xs, "f": f} | @map
201
+ xs |? f → {"c": xs, "f": f} | @filter
202
+ xs |+ f → {"c": xs, "f": f} | @reduce
203
+ ```
204
+
205
+ Folding and associativity:
206
+
207
+ - A maximal run of `+`/`-` folds into one `@OP.sum` over all terms; each `-` term is
208
+ negated (a numeric literal folds to a negative literal, otherwise via `@OP.negate`).
209
+ - A maximal run of `*`/`/` folds into one `@OP.product`; each `/` term is inverted via
210
+ `@OP.invert` (never a literal, so `1/x` stays a float and `/0` stays a runtime error).
211
+ - Runs of `==`, `&&`, `||` fold n-ary into `@OP.equal` / `@OP.and` / `@OP.or`.
212
+ - `%`, `//`, `??` are binary and left-associative; they sit at their level and break a
213
+ fold run: `a * b % c` is `(a * b) % c`; `a ?? b == 0` is `(a ?? b) == 0`.
214
+
215
+ Because pipe binds tighter than the value operators, `x|@f + 1` is `(x|@f) + 1`; to pipe a
216
+ computed value onward, parenthesize it: `(a + b)|@f`, `(a ?? b)|@gt`.
217
+
218
+ Comparisons: `a == b` is equality. `a < b` needs to be expressed as `(a ?? b) | @lt`
219
+ (likewise `@gt` / `@lte` / `@gte`), since `??` yields the `-1`/`0`/`1` ordering those
220
+ stdlib helpers interpret.
221
+
140
222
  ---
141
223
 
142
224
  ## 3. Functions and application
143
225
 
144
- A function is an ordered list of clauses. Applying a function to a value `v`:
145
-
146
- 1. If the function value itself is an error (e.g. piping into an unresolved name),
147
- that error is the result.
148
- 2. Otherwise clauses are tried in order. The first clause whose pattern matches
149
- `v` produces the result, evaluated with that clause's bindings in scope.
150
- 3. If a `?` predicate evaluates to an error during matching, that error becomes
151
- the function's result; no further clauses are tried (see §6.4).
152
- 4. **If no clause matches:** the result is `v` itself when `v` is an error
153
- (propagation — an unmatched error is never silently swallowed), and `null`
154
- otherwise (the lenient default).
155
-
156
- A consequence of rule 4: a function with error clauses that only match *some*
157
- shapes of error (e.g. `(!42 => "got 42", _ => "ok")`) still propagates any
158
- other-shaped error it receives — `!"oops"` is not caught by `!42`, the `_`
159
- clause rejects errors, no clause matches, so `!"oops"` propagates. To handle
160
- "any unrecognized error" you must add a catch-all error clause: `!` (or
161
- equivalently `!_`) for "catch any error, ignore the payload"; `!msg` to bind
162
- the payload.
226
+ A function is an ordered list of clauses `pattern => expression`. The literal `()`
227
+ is the empty function, with no clauses.
163
228
 
164
229
  Functions take exactly one argument. Multiple arguments are passed as an array or
165
- object. Currying is done by returning a function: `(x => (y => [x, y] | @add))`.
230
+ object.
231
+
232
+ Applying a function to a value (`value | function`):
233
+ 1. Clauses are tried in order. The first clause whose pattern matches `value`
234
+ produces the result, evaluated with that clause's bindings in scope.
235
+ 2. If a `?` predicate evaluates to an error during matching, that error becomes
236
+ the function's result; no further clauses are tried (see §6.4).
237
+ 3. If no clause matches:
238
+ - Unmatched errors propagate.
239
+ - For unmatched regular values the function returns `null`.
166
240
 
167
241
  Functions are values: they may be elements of arrays, values of object keys, results
168
- of clauses, and arguments to other functions.
242
+ of clauses, and arguments to other functions. However, they may not cross the CLI
243
+ boundary (see §9.3).
169
244
 
170
245
  ---
171
246
 
@@ -173,17 +248,17 @@ of clauses, and arguments to other functions.
173
248
 
174
249
  A pattern both tests structure and extracts parts. Pattern forms:
175
250
 
176
- | Form | Matches | Binds |
177
- | ------------------------------------------ | ----------------------------------------- | ---------------------------------- |
178
- | literal (`42`, `"x"`, `true`, `null`) | exactly that value | nothing |
179
- | `_` (wildcard) | anything **except** an error | nothing |
180
- | identifier (`a`) | anything **except** an error | the value, under that name |
181
- | Fixed size arrays, e.g. `[x_1, x_2]` | array of matching length, elementwise | each `x_i` binds |
182
- | Variably arrays, e.g. `[p, ...rest]` | array with ≥ fixed elements | `rest` = remaining elements |
183
- | Fixed member objects, e.g. `{"a": p}` | object having key `a` (and others) | as `p` binds |
184
- | Variable objects, e.g. `{"a": p, ...rest}` | object | `rest` = unmatched key/value pairs |
185
- | `corepat ? pred` | `corepat` matches **and** pred is `true` | as `corepat` binds |
186
- | `!`, `!_`, `!pat` | an error; `!pat` destructures the payload | as `pat` binds |
251
+ | Form | Matches | Binds |
252
+ | ------------------------------------------ | ------------------------------------------ | ---------------------------------- |
253
+ | literal (`42`, `"x"`, `true`, `null`) | exactly that value | nothing |
254
+ | `_` (wildcard) | anything **except** an error | nothing |
255
+ | identifier (`a`) | anything **except** an error | the value, under that name |
256
+ | Fixed size arrays, e.g. `[x_1, x_2]` | array of matching length, elementwise | each `x_i` binds |
257
+ | Variable arrays, e.g. `[p, ...rest]` | array with ≥ fixed elements | `rest` = remaining elements |
258
+ | Fixed member objects, e.g. `{"a": p}` | object whose keys are **exactly** `a` | as `p` binds |
259
+ | Variable objects, e.g. `{"a": p, ...rest}` | object having key `a` (extra keys allowed) | `rest` = unmatched key/value pairs |
260
+ | `p_core ? pred` | `p_core` matches **and** pred is `true` | as `p_core` binds |
261
+ | `!`, `!_`, `!pat` | an error; `!pat` destructures the payload | as `pat` binds |
187
262
 
188
263
  Rules:
189
264
 
@@ -208,19 +283,27 @@ Rules:
208
283
  - May appear at most once.
209
284
  - In an array pattern it may appear in any position and captures the start / middle / end
210
285
  of the array.
211
- - In an object pattern it may appear at the end and captures all keys not explicitly matched.
286
+ - In an object pattern it must be the last member and captures all keys not explicitly matched.
212
287
  - A bare `...` with no name matches without binding.
213
- - An object pattern matches if all its named keys are present; extra keys are allowed
214
- (and captured by `...rest` if present).
288
+ - An object pattern is **open** if it carries a `...rest` (named or bare `...`). Otherwise
289
+ it is **closed**. A **closed** object pattern matches only objects whose keys are *exactly*
290
+ the named keys. An **open** object pattern also matches objects with additional keys.
215
291
 
216
292
  ---
217
293
 
218
294
  ## 5. The `?` predicate (refinement / types)
219
295
 
220
- `corepat ? predicate` matches when `corepat` matches structurally **and** piping the
221
- matched value into `predicate` yields exactly `true`. The predicate is any
296
+ `p_core ? predicate` matches when `p_core` matches structurally **and** piping the
297
+ matched value through `predicate` yields a **truthy** result. Truthiness is
298
+ Ruby-style: every value is truthy except `false` and `null` (so `0` and `""` are
299
+ truthy). The operators `@OP.and` / `@OP.or` / `@OP.not` apply the same test.
300
+
301
+ The predicate is a `|` chain of functions, and the matched value flows in from the
302
+ left: `a ? b | c` matches when `a` matches and `a | b | c` is truthy. A single-stage
303
+ predicate (`n ? @Integer`) is just the one-function case. Each stage is any
222
304
  expression evaluating to a function (a name, a member access, or an inline function
223
- literal).
305
+ literal). If applying the predicate produces an error, that error bubbles up as the
306
+ function's result (see §6.4).
224
307
 
225
308
  "Types" are not a separate construct: the built-in predicates `@Integer`, `@String`,
226
309
  etc. are ordinary functions returning booleans, reached with `@` and used with `?`
@@ -230,21 +313,29 @@ etc. are ordinary functions returning booleans, reached with `@` and used with `
230
313
 
231
314
  ## 6. Errors
232
315
 
233
- An **error value** is `!` followed by a payload: `!42`, `!"divide by zero"`,
234
- `!{"kind":"missing_key","key":"id"}`, `!null`. The payload may be any regular Fusion
235
- value (including `null`). Not allowed are functions and nested errors.
316
+ An **error value** is `!` followed by a payload:
317
+ - `!null`
318
+ - `!42`
319
+ - `!"oops"`
320
+ - `!{"kind":"missing_key","key":"id"}`
321
+
322
+ The payload may be any regular Fusion value. Errors can't nest (§6.3). Instead the inner
323
+ error will propagate.
236
324
 
237
325
  Error values are distinct from ordinary data:
238
326
  - `null` means legitimate absence.
239
327
  - An error means something went wrong.
240
328
 
329
+ Errors produced by the interpreter itself all share one standardized payload shape,
330
+ documented in §6.5.
331
+
241
332
  ### 6.1 Constructing errors
242
333
 
243
334
  In expression position, `!` is a prefix operator that constructs an error from its
244
335
  operand:
245
336
 
246
337
  - `!42` is an error with payload `42`.
247
- - `!"oops"` is an error with payload the string `"oops"`.
338
+ - `!"oops"` is an error with the string `"oops"` as payload.
248
339
  - `!` alone (with nothing following it) is shorthand for `!null`.
249
340
  - `!expr` where `expr` itself evaluates to an error **propagates** that inner error
250
341
  rather than wrapping it (so you cannot accidentally bury an error inside another
@@ -263,7 +354,7 @@ In pattern position, `!` introduces an **error pattern**:
263
354
  | `!_` | any error; payload is not bound. Can carry a predicate (`!_ ? @pred`). |
264
355
  | `!pattern` | any error with a **payload** that matches `pattern` |
265
356
 
266
- The payload pattern (the `pattern` in `!pattern`) is a full `guardedpat`:
357
+ The payload pattern (the `pattern` in `!pattern`) is a full `p_guarded`:
267
358
  - It uses the same destructuring semantics you know from regular values.
268
359
  - It fully supports `?` predicates. Predicates only refer to the payload, not the `!`.
269
360
  Caution: `! ? @Integer` is a syntax error. The bare `!` has no payload pattern
@@ -287,7 +378,7 @@ particular built-ins:
287
378
  is per-call: it is not enough for the function to have *some* error clause;
288
379
  that clause must match the specific error received. An error of a shape no
289
380
  clause catches propagates unchanged.
290
- - **Built-in operations (`@add`, `@divide`, `@equals`, `@Integer`, …) all
381
+ - **Built-in and stdlib operations (`@math.divide`, `@OP.sum`, `@Integer`, …) all
291
382
  propagate** their input error without examining it. To inspect or compare an
292
383
  error's payload, you must catch it first and operate on the extracted payload:
293
384
  `!42 | (!a => a) | @Integer` returns `true` (the payload `42` *is* an integer);
@@ -297,8 +388,8 @@ particular built-ins:
297
388
  evaluating an element/member. `[1, !"bad", 2]` evaluates to `!"bad"`, not to
298
389
  an array of three things.
299
390
  - **Constructing an error from an erroring expression** propagates the inner
300
- error rather than wrapping it. `!([5,0] | @divide)` evaluates to
301
- `!"divide: division by zero"`, never to an error wrapping an error. (This
391
+ error rather than wrapping it. `!([5,0] | @math.divide)` evaluates to the division
392
+ error itself (a `math_error`, §6.5), never to an error wrapping an error. (This
302
393
  preserves the rule that there is never more than one error simultaneously.)
303
394
  - **When the function value itself is an error** (e.g. `value | @undefined_name`
304
395
  where `@undefined_name` resolves to an error), that error is the result.
@@ -308,86 +399,221 @@ particular built-ins:
308
399
  If a `?` predicate evaluates to an error (the predicate function itself errored,
309
400
  or it was a non-function error value), that error becomes the function's return
310
401
  value immediately. Subsequent clauses are **not** tried — predicate-errors are
311
- treated as program failures, not as "no match." This is the key reason to make
312
- your predicates *total* (end with `_ => false`): a predicate that can crash will
313
- short-circuit your whole function.
402
+ treated as program failures, not as "no match." This is the key reason to keep a
403
+ `?` predicate from raising: a predicate that can crash will short-circuit your
404
+ whole function. A non-matching input is not a crash — it falls through to `null`
405
+ (falsey) — so a predicate needs no `_ => false` catch-all to be safe here.
314
406
 
315
- ### 6.5 Sources of errors
407
+ ### 6.5 The standardized error payload
316
408
 
317
- Built-in errors are produced by:
409
+ There are two origins of error values, and they differ in payload:
318
410
 
319
- - A strict function (`_ => !`) on no match payload `null`.
320
- - Built-in operations on bad input payload is a descriptive string,
321
- e.g. `"divide: division by zero"`, `"add: expected a pair of numbers"`.
322
- - Member access on a missing key or non-object, index out of range, or bad index
323
- type payload is a structured object like
324
- `{"kind":"missing_key","key":"foo"}` or `{"kind":"index_out_of_range",...}`.
325
- - Spread of a non-array or non-object `{"kind":"spread_non_array",...}`.
326
- - Unresolved `@`-reference `{"kind":"unresolved_ref","name":"..."}`.
327
- - Non-productive data cycle — `{"kind":"data_cycle","path":"..."}`.
328
- - Applying a non-function — `{"kind":"apply_non_function","got":"..."}`.
411
+ - **Interpreter errors** produced by the language itself (a bad built-in call,
412
+ an unresolved `@`-reference, a parse failure, …). Every interpreter error
413
+ carries one **standardized payload**: a JSON object with a fixed set of fields,
414
+ documented here. The uniform shape lets one catch clause dispatch on a single
415
+ field, e.g. `(!{"kind": "math_error"} => …)`. Standard library functions
416
+ adhere to this same structure.
417
+ - **User errors** built with `!payload` (§6.1). The payload is whatever you
418
+ give it: any Fusion value, with no required shape.
329
419
 
330
- User code constructs errors with `!payload` as described above.
420
+ #### Payload shape
421
+
422
+ ```json
423
+ {"kind": "argument_error", "origin": "builtin", "operation": "@math.divide", "status": 0, "input": [1, "x"], "expected": ["[_ ? @Number, _ ? @Number]"]}
424
+ ```
425
+
426
+ | Field | Required | Meaning |
427
+ | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
428
+ | `kind` | yes | The error category. Possible values are defined below. |
429
+ | `origin` | yes | Where the failing operation is *defined*. Possible values are defined below. |
430
+ | `file` | no | The **innermost user-code file** on the call chain. `builtin`/`stdlib` frames are skipped. The path is **relative to** `Dir.pwd`. Contains `"<inline>"` for errors in the CLI `-e` option or the REPL. Contains `"<fusion>"` for an error above all user code (e.g. `stdin` present, but `code` is not a function). Present for `builtin`/`stdlib`/`code` origins; absent for a channel/runtime origin (`input`/`output`/`interpreter`). |
431
+ | `operation` | yes | The operation that failed. All `@`-references are named by their **source text**. A syntactic operation uses its form (`"\|"`, `".name"`, `"[]"`, `"parsing code"`, `"parsing JSON"`). Loading the top-level program file uses `"loading code"`. |
432
+ | `status` | yes | `0` or `1`. Whether the operation received an ordinary value (`0`) or an error value (`1`) |
433
+ | `input` | yes | The operation's input. A 0-argument operation (every `@`-reference except `@load`) carries `null`. |
434
+ | `expected` | no | The acceptable inputs as a list of Fusion **patterns**. The input matched none of them. |
435
+ | `message` | no | Extra human-readable detail, e.g. `"division by zero"`. Absent whenever `expected` is present. |
436
+
437
+ #### Possible values for `kind`
438
+
439
+ | `kind` | Raised when |
440
+ | --------------------- | -------------------------------------------------------------------------------------------------------------- |
441
+ | `syntax_error` | source code or the JSON input fails to parse. |
442
+ | `reference_error` | an `@`-reference cannot be resolved: unknown name, file not found, a file-system failure, a non-productive data cycle, a target outside the jail (§9.2), no enclosing file for `@@` (§9.2). |
443
+ | `argument_error` | a value has the wrong shape or type for an operation: a built-in given the wrong number/shape of arguments (e.g. not a pair) or a wrong-typed value, applying a non-function, spreading a non-array/object, member access on a non-object, a wrong-typed index, or an `array`/`object`-mode input envelope of the wrong shape (§9.4). Its `expected` lists the acceptable inputs as patterns. |
444
+ | `binding_error` | reading an unbound identifier, or binding the same name twice in one clause. |
445
+ | `access_error` | a missing object key or an out-of-range array index. |
446
+ | `math_error` | division or modulo by zero, or a non-finite number. |
447
+ | `conversion_error` | a value cannot be converted (`@toString` of an unconvertible type, `@parseNumber` of a non-numeric string). |
448
+ | `limit_error` | a runtime resource limit was exceeded. `"stack level too deep"`. |
449
+ | `internal_error` | an interpreter BUG. Please open an issue. |
450
+ | `serialization_error` | a result/error value has no JSON form. It contains functions or non-finite numbers. See §9. |
451
+
452
+ #### Possible values for `origin`
453
+
454
+ | `origin` | Meaning |
455
+ | ------------- | ------------------------------------------------------------------------ |
456
+ | `builtin` | a built-in operation (named by its `@`-reference in `operation`). |
457
+ | `stdlib` | a standard-library function (named by its `@`-reference in `operation`). |
458
+ | `code` | user source core (a file or an inline expression (`-e`/REPL). |
459
+ | `input` | the input channel (stdin). Usually syntax errors. |
460
+ | `output` | the output channel. Usually serialization errors. |
461
+ | `interpreter` | the interpreter itself, e.g. a stack overflow. |
462
+
463
+ `input` and `output` name the data channels; they **never** refer to the program
464
+ source, which always reports as `code`.
465
+
466
+ User errors don't have to adhere to this standard.
331
467
 
332
468
  ---
333
469
 
334
470
  ## 7. Built-in functions
335
471
 
336
472
  All built-ins are ordinary one-argument functions, **reached with an `@` prefix**
337
- (`@add`, `@Integer`, …); see §9.2 for how `@name` resolves. The names in the tables
473
+ (`@size`, `@Integer`, …); see §9.2 for how `@name` resolves. The names in the tables
338
474
  below are the built-in names; write `@` before them to use them. **Operations** return
339
475
  `!` on type-invalid or domain-invalid input. **Predicates** return `false` on any
340
476
  input that is not of the queried type (they never return `!`).
341
477
 
342
- ### 7.1 Arithmetic (operations)
343
-
344
- | Name | Input | Result |
345
- | ---------- | ----------------- | -------------------------------------------------- |
346
- | `add` | `[number, number]`| sum |
347
- | `subtract` | `[number, number]`| difference |
348
- | `multiply` | `[number, number]`| product |
349
- | `divide` | `[number, number]`| quotient; integer if evenly divisible, else float; `!` if divisor is 0 |
350
- | `mod` | `[number, number]`| remainder; `!` if divisor is 0 |
351
- | `negate` | `number` | negation |
352
- | `floor` | `number` | floor (integer) |
353
-
354
- ### 7.2 Comparison (operations)
355
-
356
- | Name | Input | Result |
357
- | ----------- | --------------------------------------- | --------------- |
358
- | `equals` | `[any, any]` | deep structural equality (boolean) |
359
- | `lessThan` | `[number, number]` or `[string, string]`| boolean; `!` on mismatched/invalid types |
360
-
361
- Other comparisons (`lessEq`, `greaterThan`, `greaterEq`, `notEquals`) are specified
362
- for the standard library, derivable from `equals` and `lessThan`.
363
-
364
- ### 7.3 Boolean (operations)
365
-
366
- | Name | Input | Result |
367
- | ----- | -------------------- | ------------- |
368
- | `and` | `[boolean, boolean]` | logical and |
369
- | `or` | `[boolean, boolean]` | logical or |
370
- | `not` | `boolean` | logical not |
478
+ The operators (`+ - * / == < …` and the boolean ops) live in the shadowable `@OP`
479
+ object (§7.6); a directory reskins them by placing an `OP.fsn`. Their **named** forms
480
+ below are stdlib functions built on `@OP.*`, so they follow a per-directory override.
481
+
482
+ ### 7.1 Arithmetic
483
+
484
+ In source you normally write the infix sugar of §2.7 — `a + b`, `-a`, `a * b`, `a % b`,
485
+ `a // b` which desugars to the members below. Addition, multiplication and negation are
486
+ `@OP.sum` / `@OP.product` / `@OP.negate` (§7.6); subtraction is `[a, b | @OP.negate] | @OP.sum`.
487
+ Integer division/remainder are `@OP.quotient` / `@OP.modulo`. Numerically correct
488
+ division and the other numeric functions live in `@math` (§7.6a): `@math.divide`,
489
+ `@math.floor`, `@math.round`, `@math.abs`, `@math.log`, `@math.sqrt`, etc.
490
+
491
+ ### 7.2 Comparison
492
+
493
+ Equality is `@OP.equal` (deep structural equality of a pair; §7.6) — used directly,
494
+ there is no `@eq` helper. Ordering is `@OP.compare`, which returns `-1`/`0`/`1`. The
495
+ named boolean forms **interpret that result** and are applied *after* it:
496
+
497
+ | Name | `-1` | `0` | `1` | `null` |
498
+ | ----- | ------ | ------ | ------ | ------ |
499
+ | `lt` | `true` | `false`| `false`| `null` |
500
+ | `gt` | `false`| `false`| `true` | `null` |
501
+ | `lte` | `true` | `true` | `false`| `null` |
502
+ | `gte` | `false`| `true` | `true` | `null` |
503
+
504
+ So `a < b` is written `[a, b] | @OP.compare | @lt` (with sugar: `(a ?? b) | @lt`; equality
505
+ is `a == b` see §2.7). Because the caller invokes
506
+ `@OP.compare`, the ordering follows a per-directory `@OP` override while the helper
507
+ itself is fixed and shadow-independent. A partial order whose `compare` returns `null`
508
+ for incomparable operands passes that `null` straight through; any other input is an
509
+ `argument_error`. A type mismatch surfaces as `@OP.compare`'s own error, before the
510
+ helper runs.
511
+
512
+ ### 7.3 Boolean
513
+
514
+ The truthiness operators live in `@OP` (§7.6): `@OP.and`, `@OP.or`, `@OP.not`, written
515
+ with the sugar `a && b`, `a || b`, `~a` (§2.7). They
516
+ judge truthiness (every value is truthy except `false` and `null`), not strict
517
+ booleans, and always return a boolean. There are no top-level `@and`/`@or`/`@not`.
518
+ The stdlib helpers `@truthy` and `@falsey` reduce any single value to its truthiness
519
+ by **pattern matching** (independent of any `@OP` override): `@truthy` is `true` for
520
+ everything except `false`/`null`, and `@falsey` is its complement.
371
521
 
372
522
  ### 7.4 Strings and structure bridges (operations)
373
523
 
374
524
  | Name | Input | Result |
375
525
  | ------------- | --------------------------- | --------------------------------------- |
376
- | `length` | string / array / object | element/character/key count (integer) |
377
- | `concat` | `[string, string]` | concatenation |
378
- | `chars` | string | array of single-character strings |
379
- | `join` | `[array-of-strings, string]`| joined string |
526
+ | `size` | string / array / object | element/character/key count (integer) |
527
+ | `join` | `[array-of-strings, separator]` | the elements joined by the separator string |
528
+ | `split` | `[string, separator]` | array split on the **literal** separator (an empty separator splits into characters), keeping empty fields |
380
529
  | `toString` | any | string form of the value |
381
530
  | `parseNumber` | string | integer or float; `!` if not numeric |
382
531
  | `keys` | object | array of key strings |
383
532
  | `values` | object | array of values |
533
+ | `toObject` | `[[string-key, value], …]` | object built from entries; later duplicate keys win |
384
534
 
385
- ### 7.5 Type predicates (predicates)
535
+ `concat` (`[string, string]` concatenation) and `chars` (string → array of its
536
+ characters) are **standard-library** functions built on `join` / `split`, not
537
+ built-ins.
386
538
 
387
- `Integer`, `Float`, `Number`, `String`, `Boolean`, `Array`, `Object`, `Null`. Each
388
- takes any value and returns a boolean. `Number` is true for integers and floats.
539
+ Indexed read (`x[k]`) and write (`x[k = v]`) are **core syntax**, not built-ins — there
540
+ is no `@get`/`@set`; see §8.
389
541
 
390
- ### 7.6 Special built-ins: `ENV` and `load`
542
+ ### 7.5 Type predicates (predicates)
543
+
544
+ Each of these functions takes any input value and returns a boolean. This set of
545
+ functions provides a runtime type system.
546
+
547
+ | Name | `true` for | Equivalent pattern |
548
+ | ----------- | ------------------------------------ | ------------------ |
549
+ | `Null` | `null` | `null` |
550
+ | `Boolean` | `true`, `false` | |
551
+ | `Integer` | integers | |
552
+ | `Float` | floats | |
553
+ | `Number` | integers and floats | |
554
+ | `String` | strings | |
555
+ | `Array` | arrays | `[_]` |
556
+ | `Object` | objects | `{_}` |
557
+ | `Function` | any function (builtin, stdlib, user) | |
558
+ | `NonFinite` | "Infinity", "-Infinity", "NaN" | |
559
+
560
+ Notes:
561
+ - Booleans are separate from numbers. There's no automatic type conversion (`false` <-> `0`, `true` <-> `1`).
562
+ - The set of values without JSON representation (§9.3) is exactly `Function` + `NonFinite`
563
+
564
+ ### 7.6 The `@OP` object (the operators)
565
+
566
+ `@OP` is a built-in **object**, reached by member access (`@OP.sum`, `@OP.and`, …),
567
+ holding the arithmetic/comparison/boolean operators. Its members generalise to an
568
+ **array of any length** (`sum`/`product`/`and`/`or` fold; `equal` is deep over all
569
+ elements); `compare` reports an ordering. The infix operators (§2.7) desugar to these members.
570
+
571
+ `@OP` is **shadowable per directory**: place an `OP.fsn` sibling that overrides members
572
+ (spread the originals with `@@`) to reskin the operators — complex numbers, matrices,
573
+ ternary logic — for that directory only. The comparison helpers in §7.2 (`@lt`, `@gt`, …)
574
+ interpret an `@OP.compare` result, so ordering follows a local override the moment the
575
+ caller pipes through `@OP.compare` (see the how-to guide).
576
+
577
+ | Member | Input | Result |
578
+ | ------------- | ---------------------------------------- | -------------------------------------------------------- |
579
+ | `OP.sum` | array of numbers | sum (`0` for `[]`) |
580
+ | `OP.product` | array of numbers | product (`1` for `[]`) |
581
+ | `OP.negate` | number | negation |
582
+ | `OP.invert` | number | reciprocal `1/x`, always a float; `!` if `0` |
583
+ | `OP.quotient` | `[integer, integer]` | integer division; `!` on a non-integer or a `0` divisor |
584
+ | `OP.modulo` | `[integer, integer]` | integer remainder; `!` on a non-integer or a `0` divisor |
585
+ | `OP.equal` | array (any element types) | deep equality: `true` iff every element equals the first |
586
+ | `OP.compare` | `[number, number]` or `[string, string]` | `-1` / `0` / `1` (first smaller / equal / larger) |
587
+ | `OP.and` | array | `true` if every element is truthy (`true` for `[]`) |
588
+ | `OP.or` | array | `true` if any element is truthy (`false` for `[]`) |
589
+ | `OP.not` | `_` | `true` if the operand is falsey |
590
+
591
+ ### 7.6a The `@math` object (numeric functions and constants)
592
+
593
+ `@math` is a built-in object (shadowable like `@OP`) of numeric functions and two
594
+ constants. `pi`/`e` are plain values; the rest are one-argument functions. A
595
+ non-finite input to `round`/`floor`/`ceil`, a `log` of a non-positive number, and a
596
+ `pow` with a complex result are `math_error`s.
597
+
598
+ | Member | Input | Result |
599
+ | ------------- | -------------------- | ---------------------------------------------------------- |
600
+ | `math.pi` | — | `3.141592653589793` (a value, not a function) |
601
+ | `math.e` | — | `2.718281828459045` (a value) |
602
+ | `math.round` | number | nearest integer (half away from zero) |
603
+ | `math.floor` | number | floor (integer) |
604
+ | `math.ceil` | number | ceiling (integer) |
605
+ | `math.divide` | `[number, number]` | quotient, always a **float**; `!` if divisor is 0 |
606
+ | `math.sign` | number | `-1` / `0` / `1` |
607
+ | `math.abs` | number | absolute value (keeps int/float) |
608
+ | `math.rand` | `null` or a positive integer `n` | float in `[0, 1)`, or integer in `[0, n)` |
609
+ | `math.sin` | number | sine (radians), float |
610
+ | `math.cos` | number | cosine (radians), float |
611
+ | `math.exp` | number | `e^x`, float |
612
+ | `math.log` | positive number | natural log, float; `!` on a non-positive number |
613
+ | `math.pow` | `[base, exponent]` | `base^exp` (integer when base and non-negative integer exponent; else float); `!` on a complex result |
614
+ | `math.sqrt` | non-negative number | square root (float); `!` on a negative number |
615
+
616
+ ### 7.7 Special built-ins: `ENV` and `load`
391
617
 
392
618
  These resolve in the `@name` chain like other built-ins (so a sibling file of the
393
619
  same name shadows them), but they are not plain unary value functions:
@@ -404,12 +630,18 @@ same name shadows them), but they are not plain unary value functions:
404
630
 
405
631
  ## 8. Member and index access
406
632
 
633
+ Member access (`.`), index read (`[]`), and index write (`[=]`) are **core syntax**,
634
+ evaluated directly by the runtime. There are no `@get`/`@set` built-ins.
635
+
407
636
  - `x.key` — if `x` is an object containing `key`, its value; otherwise `!`.
408
- - `x[expr]` — if `x` is an array and `expr` is an integer in range, the element
409
- (negative indices count from the end); if `x` is an object and `expr` is a string
410
- key that exists, its value; otherwise `!`.
637
+ - `x[expr]` — **read**: if `x` is an array and `expr` is an integer in range, the element
638
+ (negative indices count from the end); if `x` is an object and `expr` is a string key
639
+ that exists, its value; otherwise `!`.
640
+ - `x[key = value]` — **write**: a **new** array/object with that one entry set; `x` itself
641
+ is unchanged. An array index must already exist (arrays are not extended; negative
642
+ indices count from the end); an object key may be new. `!` on a bad index/type.
411
643
 
412
- Both `.` and `[]` bind tighter than `|`.
644
+ `.`, `[]`, and `[=]` are postfix and bind tighter than every operator (including `|`).
413
645
 
414
646
  ---
415
647
 
@@ -424,7 +656,19 @@ A `.fsn` file contains **exactly one expression**, which is its value. A file is
424
656
 
425
657
  A `@` reference takes one of these forms:
426
658
 
427
- - **`@`** (nothing after it) — the **current file**'s value. Used for self-recursion.
659
+ - **`@`** (nothing after it) — the value of the **current top-level unit**: the
660
+ current file, or the inline (`-e`) / REPL entry being evaluated. Used for
661
+ self-recursion.
662
+ - **`@@`** (super) — the built-in or standard-library value the current file
663
+ **shadows**: the file's own name resolved by steps 2–3 below, skipping the
664
+ sibling step (which would be the file itself). Lets an override refer to the
665
+ original method, e.g. `add.fsn` containing `@@` refers to the stdlib `add`.
666
+ Outside a file (inline `-e` / REPL) there is no name to take super of, so it is a
667
+ `reference_error` (`no enclosing file`).
668
+ - **`@@name`, `@@dir/name`** — super with an explicit name: resolve `name` by
669
+ steps 2–3 below, skipping its sibling. The **stable** form of a reference — a
670
+ local shadow cannot intercept it (used inside an `OP.fsn` as `@@OP`, and by
671
+ error patterns that must stay canonical). `@@../…` is a syntax error.
428
672
  - **`@ENV`** — an object of all environment variables (string keys, string values;
429
673
  no parsing). Resolved in the `@name` chain below, so it is shadowable.
430
674
  - **`@name`** — a single bare identifier (no `/`, no `../`).
@@ -438,9 +682,9 @@ in order, first match winning:
438
682
  2. a **built-in** of that exact name (including `ENV` and `load`);
439
683
  3. a **standard-library file** at `<stdlib root>/<name>.fsn`.
440
684
 
441
- If none match, the result is `!`. Downward paths participate fully: `@math/sqrt`
442
- checks a sibling `math/sqrt.fsn`, then a built-in named `math/sqrt`, then a stdlib
443
- `math/sqrt.fsn`.
685
+ If none match, the result is `!`. Downward paths participate fully: `@util/helper`
686
+ checks a sibling `util/helper.fsn`, then a built-in named `util/helper`, then a stdlib
687
+ `util/helper.fsn`.
444
688
 
445
689
  **Resolution of upward paths** (any reference containing `../`) is **file-only**: it
446
690
  resolves solely to a file relative to the referencing directory and never falls back
@@ -451,7 +695,20 @@ is relative to the **referencing file's** directory; built-ins and the standard
451
695
  library are global to the runtime but, per the order above, are shadowed by a sibling
452
696
  file of the same name. That shadowing is per-directory, not global.
453
697
 
454
- **Built-ins are reached through this same mechanism**: `@add`, `@Integer`, etc. are
698
+ **Confinement (the jail).** File-backed resolution is confined to a *jail*: a directory
699
+ and its subtree, set by `-j`/`--jail` and defaulting to the program's directory (the
700
+ working directory for `-e` and the REPL). All `@`-references and the builtin `@load`
701
+ respect the jail. Referencing a file outside the jail is a `reference_error`
702
+ (`outside the jail`). An existing sibling outside the jail fails this way too — it does
703
+ *not* fall back to a built-in or the stdlib, so a forbidden file fails loudly rather than
704
+ silently resolving elsewhere. References still resolve relative to the referencing file;
705
+ the jail only filters the result. The standard library is always reachable regardless of
706
+ the jail, and stdin is never affected — it is plain JSON, never an `@`-reference.
707
+ Confinement is lexical (it normalises `..`) and follows existing symlinks. It confines
708
+ references to a directory tree; it is not a security sandbox and needs none, since Fusion
709
+ cannot write files. Pass `--jail '*'` to disable confinement entirely.
710
+
711
+ **Built-ins are reached through this same mechanism**: `@size`, `@Integer`, etc. are
455
712
  `@name` references that resolve at step 2. A *bare* identifier (without `@`) is only
456
713
  a pattern hole; it never denotes a built-in.
457
714
 
@@ -481,11 +738,19 @@ above. Recursion through functions is not a data cycle.
481
738
 
482
739
  ### 9.3 Runtime contract
483
740
 
484
- The interpreter reads standard input as JSON, converts it to a Fusion value `v`,
741
+ The **pipe** use case (`--pipe`, and the default whenever any argument is given
742
+ see §9.7) reads standard input as JSON, converts it to a Fusion value `v`,
485
743
  computes `v | programFunction`, and prints the result on standard output as JSON.
486
-
487
- - Empty input is treated as `null`.
488
- - Non-JSON input yields an error (payload `{"kind":"stdin_not_json"}`).
744
+ When standard input is empty, the program get evaluated and immediately becomes
745
+ the result instead.
746
+
747
+ - Input always arrives on standard input; there is no input argument.
748
+ - **Empty input means "no input": the program's own value is the result.** A
749
+ `.fsn` file therefore doubles as enriched JSON data — it can compute, read
750
+ `@ENV`, and pull in `@`-references, then print the value with no pipeline
751
+ input. (Under `-!` the input is an error value instead; empty input then has no
752
+ payload to mark, which is a usage error — see §9.4.)
753
+ - Non-JSON input yields a `syntax_error` at `origin: "input"` (§6.5).
489
754
  - **If the final result is an error**, the interpreter prints **nothing** to
490
755
  standard output, prints the error's **payload** (as JSON) to standard error, and
491
756
  exits with status `1`. Otherwise the result is printed to standard output and the
@@ -493,13 +758,176 @@ computes `v | programFunction`, and prints the result on standard output as JSON
493
758
  stdout stream carries the value-or-nothing, the stderr stream carries the failure
494
759
  detail, and the exit code is `0`/`1` accordingly.
495
760
 
496
- ### 9.4 Command-line interface
761
+ This stdout/stderr/exit-code split is the **unix** output mode — the default for
762
+ the pipe use case. §9.4 describes the other ways an error can cross the boundary,
763
+ §9.5 the streaming use case, and §9.6 the REPL.
764
+
765
+ **Serialization.** A function and a non-finite float number have no JSON form.
766
+ How one is rendered depends on where it sits:
767
+
768
+ - In a **result** or inside a **user error's** payload, they can't be serialized.
769
+ The whole output becomes a `serialization_error`.
770
+ - Inside an **interpreter error's** payload, it is serialized leniently as a
771
+ string:
772
+ - a function renders as `"<function>"`
773
+ - a non-finite number as `"<Infinity>"` / `"<-Infinity>"` / `"<NaN>"`
774
+
775
+ Note:
776
+ - If a regular value or user error fails to serialize strictly, the resulting
777
+ `serializaton_error` will be an interpreter error and will subsequently
778
+ serialize leniently to preserve as much information about the root error as
779
+ possible.
780
+
781
+ ### 9.4 Input and output modes
782
+
783
+ An error value cannot cross the CLI boundary as plain JSON — something must mark
784
+ it as an error. The **input mode** and the **output mode** define that marking.
785
+ They are independent of each other and selected with the `--input` and `--output`
786
+ flags:
787
+
788
+ - **`unix`** — the input is plain JSON and always a value; the `-!` flag marks
789
+ the whole input as an error value instead (its JSON becomes the payload). `-!`
790
+ therefore requires input: with empty input there is no payload to mark, which
791
+ is a usage error (the program does not run). Output: a value goes to stdout
792
+ with exit code `0`; an error's payload goes to stderr with exit code `1`
793
+ (§9.3).
794
+ - **`bang`** — a leading `!` marks an error value; the payload is the JSON after
795
+ the `!`. A lone `!` is `!null`, like the language's bare `!`. Output is always
796
+ on stdout and the exit code is always `0`. A `!`-marked line is not valid JSON;
797
+ that is the price of the most lightweight marking, so `bang` is recommended only
798
+ between Fusion programs — for anything that must stay valid JSON, use `array` or
799
+ `object`.
800
+ - **`array`** — everything is wrapped in an envelope: `[0, value]` for a value,
801
+ `[1, payload]` for an error. Every line is valid JSON, which is why it is the
802
+ `--stream` default (§9.5). Output is always on stdout, exit code always `0`.
803
+ - **`object`** — the envelope is `{"value": value}` for a value, `{"error": payload}`
804
+ for an error. Output is always on stdout, exit code always `0`.
805
+
806
+ A malformed `array`/`object` input envelope (any other shape; the array tag must
807
+ be exactly the integer `0` or `1`) is an `argument_error` at `origin: "input"`.
808
+ Like any input failure it flows into the program as an error and is catchable.
809
+
810
+ Mode support per use case (defaults in bold):
811
+
812
+ | Use case | `unix` | `bang` | `array` | `object` |
813
+ | ---------- | -------- | -------- | ------- | -------- |
814
+ | pipe | **yes** | yes | yes | yes |
815
+ | `--stream` | no | yes | **yes** | yes |
816
+ | `--repl` | — | — | — | — |
817
+
818
+ The unix mode spends the process's only exit code and both standard streams on a
819
+ single result, so it cannot mark errors per record in a stream; the stream use
820
+ case therefore excludes it. Stream defaults to `array` rather than `bang` so each
821
+ record stays valid JSON (NDJSON, §9.5); `bang` remains available as the cheapest
822
+ encoding for Fusion-to-Fusion pipelines. The REPL is interactive and has no modes
823
+ at all.
824
+
825
+ ### 9.5 Streaming (`--stream`)
826
+
827
+ `fusion --stream` loads the program once, then treats standard input and output
828
+ as [NDJSON](https://github.com/ndjson/ndjson-spec) streams: each input line is
829
+ decoded per the input mode, piped through the program, and printed as one output
830
+ line encoded per the output mode. Input and output default to the **array** mode
831
+ (not `bang`) so every line is valid JSON. The media type is
832
+ `application/x-ndjson` and the file extension for storing such a stream should
833
+ be `.ndjson`.
834
+
835
+ NDJSON conformance:
836
+ - Every output record is a single JSON text in UTF-8, terminated by `\n`, and
837
+ never contains an embedded newline or carriage return.
838
+ - Both `\n` and `\r\n` are accepted as input line delimiters.
839
+ - A blank input line (empty or whitespace-only) carries no record, so the program
840
+ never runs on it. By default it is echoed as a blank output line, keeping input
841
+ and output aligned line-for-line. Pass `--skip-blank-lines` to drop blank lines
842
+ instead. Every non-blank line produces exactly one output line.
843
+
844
+ - Errors stay in-band, so a failing record — including a stack overflow — becomes
845
+ that record's output line and the stream continues. The exit code is always `0`.
846
+ - A program that fails to load will return the same load error for every record.
847
+
848
+ ### 9.6 The REPL (`--repl`)
849
+
850
+ `fusion --repl` starts an interactive session — as does a bare `fusion` with no
851
+ arguments at all (§9.7). It loads no program, takes no pipeline input, has no
852
+ input/output mode, and always exits `0`. Each entry is read, evaluated, and its
853
+ result printed. An entry is one of:
854
+
855
+ - an **expression** — evaluated and printed; or
856
+ - a **statement** — an assignment that also binds a name:
857
+
858
+ ```ebnf
859
+ statement = identifier "=" expr ;
860
+ ```
861
+
862
+ A statement evaluates `expr`, prints the result, and binds it to `identifier`
863
+ for later entries. Bare identifiers read earlier bindings; `@`-references resolve
864
+ relative to the working directory.
865
+
866
+ - Results print leniently (§9.3): a function prints as `"<function>"` instead of
867
+ becoming a `serialization_error`.
868
+ - An error prints as `!payload`. A statement binds an error result like any other
869
+ result; reading that identifier later propagates the error, exactly as reading an
870
+ `@`-reference that resolved to one. (Pattern binders never capture an error, but a
871
+ statement is an assignment, not a pattern match.)
872
+ - Rebinding a name is allowed; later entries see the new value.
873
+ - A bound function can call itself through its own name
874
+ (`fact = (0 => 1, n => [n, [n,-1] | @OP.sum | fact] | @OP.product)`), because
875
+ the name is looked up at application time.
876
+ - Entries report errors at `origin: "code"` with `file: "<inline>"`, like `-e` programs.
877
+
878
+ **Input editing.** An entry is submitted only once it parses as a complete
879
+ statement or expression; until then the session opens a new line so the entry
880
+ can be finished or corrected. An entry may therefore span multiple lines
881
+ (continuation lines show `...> `); on an empty continuation line, backspace
882
+ returns to the previous line. The prompt and the echoed input render on **stderr**
883
+ (like a shell prompt), so stdout carries only the stream of results.
884
+ The prompt is shown in light blue, and each result is preceded **on stderr**
885
+ by a green `✔` (a value) or a red `✗` (an error); these
886
+ are decorations only — the result itself stays unstyled on stdout. End the
887
+ session with Ctrl-D; Ctrl-C discards the entry being typed.
888
+
889
+ ### 9.7 Command-line interface
497
890
 
498
891
  ```
499
- fusion <file.fsn> [json-input]
500
- fusion -e '<source>' [json-input]
892
+ usage: fusion [options] <file.fsn>
893
+ fusion [options] -e '<source>'
894
+ fusion --repl
895
+
896
+ use cases (default: --repl with no arguments, otherwise --pipe):
897
+ -p, --pipe apply the program to stdin; with no input, the
898
+ program's own value is the result
899
+ -s, --stream apply the program to each line of an NDJSON stream
900
+ -r, --repl interactive expressions and `identifier = expression`
901
+
902
+ options:
903
+ -e, --execute '<source>'
904
+ inline program instead of a file
905
+ -i, --input MODE
906
+ how the input marks an error value (§9.4)
907
+ -o, --output MODE
908
+ how the output marks an error value (§9.4)
909
+ -j, --jail DIR confine @-references to DIR and its subtree
910
+ (default: the program's directory; '*' disables it; §9.2)
911
+ -! treat the input as an error value (unix input mode only)
912
+ -b, --skip-blank-lines
913
+ drop blank input lines instead of echoing them (--stream, §9.5)
501
914
  ```
502
915
 
503
- Input comes from the `[json-input]` argument if present, otherwise from standard
504
- input. Setting the environment variable `FUSION_DEBUG` causes file-not-found and
505
- parse errors during reference resolution to be reported on standard error.
916
+ **Selecting a use case.** At most one of `--pipe`, `--stream`, `--repl` may be
917
+ given; passing two is a command-line misuse. With none, a bare `fusion` (no
918
+ arguments at all) starts the REPL, while any other invocation is a pipe run. So
919
+ `--pipe` is needed only to be explicit, `fusion file.fsn` already implicitly
920
+ use `--pipe`.
921
+
922
+ In the pipe use case, input comes from standard input; when standard input is
923
+ empty, the program's own value is the result (§9.3). The stream use case also
924
+ reads standard input. Neither accepts an input argument.
925
+
926
+ Every flag has a short and a long form (`-p`/`--pipe`, `-i`/`--input`, …), except
927
+ `-!`, which has only the short form. Each of `--input`/`--output` may only be used
928
+ once. Multiple different modes for one direction is a misuse.
929
+
930
+ A command-line misuse (an unknown flag, more than one use case, two different
931
+ modes for one direction, an unsupported mode combination, a missing program) is
932
+ reported as plain usage text on stderr with exit code `1`. It happens before the
933
+ input/output contract begins, so it is not a payloaded error.