fusion-lang 0.0.1.alpha1 → 0.0.1.alpha2

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.
@@ -93,79 +93,89 @@ by the same rule.
93
93
  ### 2.5 Grammar (EBNF)
94
94
 
95
95
  ```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 *)
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
+ identifier = letter { letter | digit | "_" } ;
105
+
106
+ atom = "null" | "true" | "false" | number | string ;
107
+ number = int_lit | float_lit ;
108
+ int_lit = [ "-" ] digit { digit } ;
109
+ float_lit = int_lit "." digit { digit } [ exp ] | int_lit exp ;
110
+ exp = ("e" | "E") [ "+" | "-" ] digit { digit } ;
111
+ string = '"' { char | escape } '"' ; (* char excludes raw newline; use \n *)
112
+
113
+ array = "[" [ item { "," item } [ "," ] ] "]" ;
114
+ item = expr | spread ;
115
+ object = "{" [ pair { "," pair } [ "," ] ] "}" ;
116
+ pair = string ":" expr | spread ;
117
+ spread = "..." expr ;
118
+
119
+ function = "(" [ clause { "," clause } [ "," ] ] ")" ; (* "()" is the empty function *)
120
+ clause = pattern "=>" expr ;
121
+
122
+ fileref = "@" [ refpath ] ; (* bare "@" = current file *)
123
+ refpath = { "../" } segment { "/" segment } ; (* ".fsn" implied *)
124
+ segment = identifier ;
125
+
126
+ pattern = p_error | p_guarded ;
127
+ p_error = "!" | "!" p_guarded ; (* bare "!" matches any error, binds nothing *)
128
+ p_guarded = p_core [ "?" predicate ] ;
129
+ predicate = pipe ; (* a `|` chain of functions; the matched value flows in *)
130
+ p_core = p_literal | p_bind | p_wildcard | p_array | p_object ;
131
+ p_literal = atom ;
132
+ p_wildcard = "_" ;
133
+ p_bind = identifier ;
134
+
135
+ p_array = "[" ( p_fixed_items | p_rest_items ) "]" ;
136
+ p_fixed_items = [ p_items [ "," ] ] ;
137
+ p_rest_items = [ p_items "," ] p_rest [ "," p_items ] [ "," ] ;
138
+ p_items = p_item { "," p_item } ;
139
+ p_item = p_guarded ;
140
+
141
+ p_object = "{" ( p_fixed_pairs | p_rest_pairs ) "}" ;
142
+ p_fixed_pairs = [ p_pairs [ "," ] ] ;
143
+ p_rest_pairs = [ p_pairs "," ] p_rest [ "," ] ;
144
+ p_pairs = p_pair { "," p_pair } ;
145
+ p_pair = string ":" p_guarded ;
146
+
147
+ p_rest = "..." [ identifier ] ;
148
+
138
149
  ```
139
150
 
151
+ ### 2.6 Context-sensitive rules the grammar cannot express
152
+
153
+ Object literals and object patterns may not repeat a fixed key. `{"a": …, "a": …}`
154
+ is a `syntax_error`. Keys arriving through `...spread` / `...rest` are dynamic and
155
+ not checked.
156
+
140
157
  ---
141
158
 
142
159
  ## 3. Functions and application
143
160
 
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.
161
+ A function is an ordered list of clauses `pattern => expression`. The literal `()`
162
+ is the empty function, with no clauses.
163
163
 
164
164
  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))`.
165
+ object.
166
+
167
+ Applying a function to a value (`value | function`):
168
+ 1. Clauses are tried in order. The first clause whose pattern matches `value`
169
+ produces the result, evaluated with that clause's bindings in scope.
170
+ 2. If a `?` predicate evaluates to an error during matching, that error becomes
171
+ the function's result; no further clauses are tried (see §6.4).
172
+ 3. If no clause matches:
173
+ - Unmatched errors propagate.
174
+ - For unmatched regular values the function returns `null`.
166
175
 
167
176
  Functions are values: they may be elements of arrays, values of object keys, results
168
- of clauses, and arguments to other functions.
177
+ of clauses, and arguments to other functions. However, they may not cross the CLI
178
+ boundary (see §9.3).
169
179
 
170
180
  ---
171
181
 
@@ -173,17 +183,17 @@ of clauses, and arguments to other functions.
173
183
 
174
184
  A pattern both tests structure and extracts parts. Pattern forms:
175
185
 
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 |
186
+ | Form | Matches | Binds |
187
+ | ------------------------------------------ | ------------------------------------------ | ---------------------------------- |
188
+ | literal (`42`, `"x"`, `true`, `null`) | exactly that value | nothing |
189
+ | `_` (wildcard) | anything **except** an error | nothing |
190
+ | identifier (`a`) | anything **except** an error | the value, under that name |
191
+ | Fixed size arrays, e.g. `[x_1, x_2]` | array of matching length, elementwise | each `x_i` binds |
192
+ | Variable arrays, e.g. `[p, ...rest]` | array with ≥ fixed elements | `rest` = remaining elements |
193
+ | Fixed member objects, e.g. `{"a": p}` | object whose keys are **exactly** `a` | as `p` binds |
194
+ | Variable objects, e.g. `{"a": p, ...rest}` | object having key `a` (extra keys allowed) | `rest` = unmatched key/value pairs |
195
+ | `p_core ? pred` | `p_core` matches **and** pred is `true` | as `p_core` binds |
196
+ | `!`, `!_`, `!pat` | an error; `!pat` destructures the payload | as `pat` binds |
187
197
 
188
198
  Rules:
189
199
 
@@ -208,19 +218,27 @@ Rules:
208
218
  - May appear at most once.
209
219
  - In an array pattern it may appear in any position and captures the start / middle / end
210
220
  of the array.
211
- - In an object pattern it may appear at the end and captures all keys not explicitly matched.
221
+ - In an object pattern it must be the last member and captures all keys not explicitly matched.
212
222
  - 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).
223
+ - An object pattern is **open** if it carries a `...rest` (named or bare `...`). Otherwise
224
+ it is **closed**. A **closed** object pattern matches only objects whose keys are *exactly*
225
+ the named keys. An **open** object pattern also matches objects with additional keys.
215
226
 
216
227
  ---
217
228
 
218
229
  ## 5. The `?` predicate (refinement / types)
219
230
 
220
- `corepat ? predicate` matches when `corepat` matches structurally **and** piping the
221
- matched value into `predicate` yields exactly `true`. The predicate is any
231
+ `p_core ? predicate` matches when `p_core` matches structurally **and** piping the
232
+ matched value through `predicate` yields a **truthy** result. Truthiness is
233
+ Ruby-style: every value is truthy except `false` and `null` (so `0` and `""` are
234
+ truthy). The built-ins `@and` / `@or` / `@not` apply the same test.
235
+
236
+ The predicate is a `|` chain of functions, and the matched value flows in from the
237
+ left: `a ? b | c` matches when `a` matches and `a | b | c` is truthy. A single-stage
238
+ predicate (`n ? @Integer`) is just the one-function case. Each stage is any
222
239
  expression evaluating to a function (a name, a member access, or an inline function
223
- literal).
240
+ literal). If applying the predicate produces an error, that error bubbles up as the
241
+ function's result (see §6.4).
224
242
 
225
243
  "Types" are not a separate construct: the built-in predicates `@Integer`, `@String`,
226
244
  etc. are ordinary functions returning booleans, reached with `@` and used with `?`
@@ -230,21 +248,29 @@ etc. are ordinary functions returning booleans, reached with `@` and used with `
230
248
 
231
249
  ## 6. Errors
232
250
 
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.
251
+ An **error value** is `!` followed by a payload:
252
+ - `!null`
253
+ - `!42`
254
+ - `!"oops"`
255
+ - `!{"kind":"missing_key","key":"id"}`
256
+
257
+ The payload may be any regular Fusion value. Errors can't nest (§6.3). Instead the inner
258
+ error will propagate.
236
259
 
237
260
  Error values are distinct from ordinary data:
238
261
  - `null` means legitimate absence.
239
262
  - An error means something went wrong.
240
263
 
264
+ Errors produced by the interpreter itself all share one standardized payload shape,
265
+ documented in §6.5.
266
+
241
267
  ### 6.1 Constructing errors
242
268
 
243
269
  In expression position, `!` is a prefix operator that constructs an error from its
244
270
  operand:
245
271
 
246
272
  - `!42` is an error with payload `42`.
247
- - `!"oops"` is an error with payload the string `"oops"`.
273
+ - `!"oops"` is an error with the string `"oops"` as payload.
248
274
  - `!` alone (with nothing following it) is shorthand for `!null`.
249
275
  - `!expr` where `expr` itself evaluates to an error **propagates** that inner error
250
276
  rather than wrapping it (so you cannot accidentally bury an error inside another
@@ -263,7 +289,7 @@ In pattern position, `!` introduces an **error pattern**:
263
289
  | `!_` | any error; payload is not bound. Can carry a predicate (`!_ ? @pred`). |
264
290
  | `!pattern` | any error with a **payload** that matches `pattern` |
265
291
 
266
- The payload pattern (the `pattern` in `!pattern`) is a full `guardedpat`:
292
+ The payload pattern (the `pattern` in `!pattern`) is a full `p_guarded`:
267
293
  - It uses the same destructuring semantics you know from regular values.
268
294
  - It fully supports `?` predicates. Predicates only refer to the payload, not the `!`.
269
295
  Caution: `! ? @Integer` is a syntax error. The bare `!` has no payload pattern
@@ -297,8 +323,8 @@ particular built-ins:
297
323
  evaluating an element/member. `[1, !"bad", 2]` evaluates to `!"bad"`, not to
298
324
  an array of three things.
299
325
  - **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
326
+ error rather than wrapping it. `!([5,0] | @divide)` evaluates to the division
327
+ error itself (a `math_error`, §6.5), never to an error wrapping an error. (This
302
328
  preserves the rule that there is never more than one error simultaneously.)
303
329
  - **When the function value itself is an error** (e.g. `value | @undefined_name`
304
330
  where `@undefined_name` resolves to an error), that error is the result.
@@ -312,22 +338,64 @@ treated as program failures, not as "no match." This is the key reason to make
312
338
  your predicates *total* (end with `_ => false`): a predicate that can crash will
313
339
  short-circuit your whole function.
314
340
 
315
- ### 6.5 Sources of errors
341
+ ### 6.5 The standardized error payload
316
342
 
317
- Built-in errors are produced by:
343
+ There are two origins of error values, and they differ in payload:
318
344
 
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":"..."}`.
345
+ - **Interpreter errors** produced by the language itself (a bad built-in call,
346
+ an unresolved `@`-reference, a parse failure, …). Every interpreter error
347
+ carries one **standardized payload**: a JSON object with a fixed set of fields,
348
+ documented here. The uniform shape lets one catch clause dispatch on a single
349
+ field, e.g. `(!{"kind": "math_error"} => …)`. Standard library functions
350
+ adhere to this same structure.
351
+ - **User errors** built with `!payload` (§6.1). The payload is whatever you
352
+ give it: any Fusion value, with no required shape.
329
353
 
330
- User code constructs errors with `!payload` as described above.
354
+ #### Payload shape
355
+
356
+ ```json
357
+ {"kind": "type_error", "location": "builtin add", "operation": "add", "input": [1, "x"], "message": "expected numbers"}
358
+ ```
359
+
360
+ | Field | Required | Meaning |
361
+ | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
362
+ | `kind` | yes | The error category, from the closed set below. |
363
+ | `location` | yes | Where the failing operation lives, from the closed set below. |
364
+ | `operation` | yes | A short description of the operation that failed, e.g. `"\|"`, `".name"`, `"[2]"`, `"add"`, `"reading file"`, `"parsing"`. |
365
+ | `input` | yes | The operand(s) the operation received — often the offending value; for member/index access it is `[object, key]`. |
366
+ | `message` | no | Extra human-readable detail, e.g. `"expected an object"`. |
367
+
368
+ #### `kind` — the closed set
369
+
370
+ | `kind` | Raised when |
371
+ | --------------------- | -------------------------------------------------------------------------------------------------------------- |
372
+ | `syntax_error` | source code, or the JSON input, fails to parse. |
373
+ | `reference_error` | an `@`-reference cannot be resolved: unknown name, file not found, a file-system failure, a non-productive data cycle, or a `@`-self-reference with no current file. |
374
+ | `type_error` | a value has the wrong type for an operation (expected X / a type mismatch); also applying a non-function, spreading a non-array/object, member access on a non-object, or a wrong-typed index. |
375
+ | `argument_error` | a built-in receives the wrong number/shape of arguments (e.g. not a pair), or an `array`/`object`-mode input envelope has the wrong shape (§9.4). Its `message` states the expected shape as a Fusion pattern where possible (the pair-built-ins report `expected [_, _]`). |
376
+ | `binding_error` | reading an unbound identifier, or binding the same name twice in one clause. |
377
+ | `access_error` | a missing object key or an out-of-range array index — and nothing else (a non-object member access or a wrong-typed index is a `type_error`). |
378
+ | `math_error` | division or modulo by zero, or a non-finite number. |
379
+ | `conversion_error` | a value cannot be converted (`@toString` of an unconvertible type, `@parseNumber` of a non-numeric string). |
380
+ | `stack_error` | recursion too deep (a stack overflow). |
381
+ | `serialization_error` | a result, or a user error's payload, has no JSON form — see §9.3. |
382
+
383
+ #### `location` — the closed set
384
+
385
+ | `location` | Meaning |
386
+ | --------------- | ----------------------------------------------------------------- |
387
+ | `builtin X` | the built-in named X, e.g. `builtin divide`. |
388
+ | `stdlib X` | the standard-library file X. |
389
+ | `code X` | the user source file X (basename). |
390
+ | `code <inline>` | an inline `-e` program or a REPL statement. |
391
+ | `input` | the input channel (stdin or the CLI-argument). |
392
+ | `output` | the output channel (the serialized result). |
393
+ | `interpreter` | the interpreter itself, e.g. a stack overflow. |
394
+
395
+ `input` and `output` name the data channels; they **never** refer to the program
396
+ source, which always reports as `code X`.
397
+
398
+ User errors don't have to adhere to this standard.
331
399
 
332
400
  ---
333
401
 
@@ -341,21 +409,21 @@ input that is not of the queried type (they never return `!`).
341
409
 
342
410
  ### 7.1 Arithmetic (operations)
343
411
 
344
- | Name | Input | Result |
345
- | ---------- | ----------------- | -------------------------------------------------- |
346
- | `add` | `[number, number]`| sum |
347
- | `subtract` | `[number, number]`| difference |
348
- | `multiply` | `[number, number]`| product |
412
+ | Name | Input | Result |
413
+ | ---------- | ----------------- | ---------------------------------------------------------------------- |
414
+ | `add` | `[number, number]`| sum |
415
+ | `subtract` | `[number, number]`| difference |
416
+ | `multiply` | `[number, number]`| product |
349
417
  | `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) |
418
+ | `mod` | `[number, number]`| remainder; `!` if divisor is 0 |
419
+ | `negate` | `number` | negation |
420
+ | `floor` | `number` | floor (integer) |
353
421
 
354
422
  ### 7.2 Comparison (operations)
355
423
 
356
- | Name | Input | Result |
357
- | ----------- | --------------------------------------- | --------------- |
358
- | `equals` | `[any, any]` | deep structural equality (boolean) |
424
+ | Name | Input | Result |
425
+ | ----------- | --------------------------------------- | ---------------------------------------- |
426
+ | `equals` | `[any, any]` | deep structural equality (boolean) |
359
427
  | `lessThan` | `[number, number]` or `[string, string]`| boolean; `!` on mismatched/invalid types |
360
428
 
361
429
  Other comparisons (`lessEq`, `greaterThan`, `greaterEq`, `notEquals`) are specified
@@ -363,11 +431,14 @@ for the standard library, derivable from `equals` and `lessThan`.
363
431
 
364
432
  ### 7.3 Boolean (operations)
365
433
 
366
- | Name | Input | Result |
367
- | ----- | -------------------- | ------------- |
368
- | `and` | `[boolean, boolean]` | logical and |
369
- | `or` | `[boolean, boolean]` | logical or |
370
- | `not` | `boolean` | logical not |
434
+ These judge **truthiness** (the same Ruby-style test as `?` predicates: every value
435
+ is truthy except `false` and `null`), not strict booleans, and always return a boolean.
436
+
437
+ | Name | Input | Result |
438
+ | ----- | -------- | --------------------------------------------------- |
439
+ | `and` | `[_, _]` | `true` if both operands are truthy |
440
+ | `or` | `[_, _]` | `true` if either operand is truthy |
441
+ | `not` | `_` | `true` if the operand is falsey (`false` or `null`) |
371
442
 
372
443
  ### 7.4 Strings and structure bridges (operations)
373
444
 
@@ -381,11 +452,31 @@ for the standard library, derivable from `equals` and `lessThan`.
381
452
  | `parseNumber` | string | integer or float; `!` if not numeric |
382
453
  | `keys` | object | array of key strings |
383
454
  | `values` | object | array of values |
455
+ | `get` | `[array, int]` or `[object, string-key]` | element at that index/key (like `[]`, §8); `!` if out of range / missing |
456
+ | `set` | `[array, int, value]` or `[object, string-key, value]` | a **new** array/object with that entry set; an array index must already exist, an object key may be new |
457
+ | `toObject` | `[[string-key, value], …]` | object built from entries; later duplicate keys win |
384
458
 
385
459
  ### 7.5 Type predicates (predicates)
386
460
 
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.
461
+ Each of these functions takes any input value and returns a boolean. This set of
462
+ functions provides a runtime type system.
463
+
464
+ | Name | `true` for | Equivalent pattern |
465
+ | ----------- | ------------------------------------ | ------------------ |
466
+ | `Null` | `null` | `null` |
467
+ | `Boolean` | `true`, `false` | |
468
+ | `Integer` | integers | |
469
+ | `Float` | floats | |
470
+ | `Number` | integers and floats | |
471
+ | `String` | strings | |
472
+ | `Array` | arrays | `[_]` |
473
+ | `Object` | objects | `{_}` |
474
+ | `Function` | any function (builtin, stdlib, user) | |
475
+ | `NonFinite` | "Infinity", "-Infinity", "NaN" | |
476
+
477
+ Notes:
478
+ - Booleans are separate from numbers. There's no automatic type conversion (`false` <-> `0`, `true` <-> `1`).
479
+ - The set of values without JSON representation (§9.3) is exactly `Function` + `NonFinite`
389
480
 
390
481
  ### 7.6 Special built-ins: `ENV` and `load`
391
482
 
@@ -481,11 +572,12 @@ above. Recursion through functions is not a data cycle.
481
572
 
482
573
  ### 9.3 Runtime contract
483
574
 
484
- The interpreter reads standard input as JSON, converts it to a Fusion value `v`,
485
- computes `v | programFunction`, and prints the result on standard output as JSON.
575
+ The default use case (**pipe**) reads standard input as JSON, converts it to a
576
+ Fusion value `v`, computes `v | programFunction`, and prints the result on
577
+ standard output as JSON.
486
578
 
487
- - Empty input is treated as `null`.
488
- - Non-JSON input yields an error (payload `{"kind":"stdin_not_json"}`).
579
+ - Empty input is treated as `null` (in every input mode).
580
+ - Non-JSON input yields a `syntax_error` at `location: "input"` (§6.5).
489
581
  - **If the final result is an error**, the interpreter prints **nothing** to
490
582
  standard output, prints the error's **payload** (as JSON) to standard error, and
491
583
  exits with status `1`. Otherwise the result is printed to standard output and the
@@ -493,13 +585,135 @@ computes `v | programFunction`, and prints the result on standard output as JSON
493
585
  stdout stream carries the value-or-nothing, the stderr stream carries the failure
494
586
  detail, and the exit code is `0`/`1` accordingly.
495
587
 
496
- ### 9.4 Command-line interface
588
+ This stdout/stderr/exit-code split is the **unix** output mode — the default for
589
+ the pipe use case. §9.4 describes the other ways an error can cross the boundary,
590
+ §9.5 the streaming use case, and §9.6 the REPL.
591
+
592
+ **Serialization.** A function and a non-finite float number have no JSON form.
593
+ How one is rendered depends on where it sits:
594
+
595
+ - In a **result** or inside a **user error's** payload, they can't be serialized.
596
+ The whole output becomes a `serialization_error`.
597
+ - Inside an **interpreter error's** payload, it is serialized leniently as a
598
+ string:
599
+ - a function renders as `"<function>"`
600
+ - a non-finite number as `"<Infinity>"` / `"<-Infinity>"` / `"<NaN>"`
601
+
602
+ Note:
603
+ - If a regular value or user error fails to serialize strictly, the resulting
604
+ `serializaton_error` will be an interpreter error and will subsequently
605
+ serialize leniently to preserve as much information about the root error as
606
+ possible.
607
+
608
+ ### 9.4 Input and output modes
609
+
610
+ An error value cannot cross the CLI boundary as plain JSON — something must mark
611
+ it as an error. The **input mode** and the **output mode** define that marking.
612
+ They are independent of each other and selected with the `--input` and `--output`
613
+ flags:
614
+
615
+ - **`unix`** — the input is plain JSON and always a value; the `-!` flag marks
616
+ the whole input as an error value instead (its JSON becomes the payload).
617
+ Output: a value goes to stdout with exit code `0`; an error's payload goes to
618
+ stderr with exit code `1` (§9.3).
619
+ - **`bang`** — a leading `!` marks an error value; the payload is the JSON after
620
+ the `!`. A lone `!` is `!null`, like the language's bare `!`. Output is always
621
+ on stdout and the exit code is always `0`. A `!`-marked line is not valid JSON;
622
+ that is the price of the most lightweight marking.
623
+ - **`array`** — everything is wrapped in an envelope: `[0, value]` for a value,
624
+ `[1, payload]` for an error. Output is always on stdout, exit code always `0`.
625
+ - **`object`** — the envelope is `{"value": value}` for a value, `{"error": payload}`
626
+ for an error. Output is always on stdout, exit code always `0`.
627
+
628
+ A malformed `array`/`object` input envelope (any other shape; the array tag must
629
+ be exactly the integer `0` or `1`) is an `argument_error` at `location: "input"`.
630
+ Like any input failure it flows into the program as an error and is catchable.
631
+
632
+ Mode support per use case (defaults in bold):
633
+
634
+ | Use case | `unix` | `bang` | `array` | `object` |
635
+ | ---------- | -------- | -------- | ------- | -------- |
636
+ | pipe | **yes** | yes | yes | yes |
637
+ | `--stream` | no | **yes** | yes | yes |
638
+ | `--repl` | — | — | — | — |
639
+
640
+ The unix mode spends the process's only exit code and both standard streams on a
641
+ single result, so it cannot mark errors per record in a stream; the stream use
642
+ case therefore excludes it. The REPL is interactive and has no modes at all.
643
+
644
+ ### 9.5 Streaming (`--stream`)
645
+
646
+ `fusion --stream` loads the program once, then treats standard input and output
647
+ as NDJSON streams: each input line is decoded per the input mode, piped through
648
+ the program, and printed as one output line encoded per the output mode.
649
+
650
+ - Blank lines are skipped; every other input line produces exactly one output line.
651
+ - Errors stay in-band, so a failing record — including a stack overflow — becomes
652
+ that record's output line and the stream continues. The exit code is always `0`.
653
+ - A program that fails to load answers every record with that same load error.
654
+
655
+ ### 9.6 The REPL (`--repl`)
656
+
657
+ `fusion --repl` starts an interactive session. It loads no program, takes no
658
+ pipeline input, has no input/output mode, and always exits `0`. Each entry is
659
+ read, evaluated, and its result printed. An entry is one of:
660
+
661
+ - an **expression** — evaluated and printed; or
662
+ - a **statement** — an assignment that also binds a name:
497
663
 
664
+ ```ebnf
665
+ statement = identifier "=" expr ;
498
666
  ```
499
- fusion <file.fsn> [json-input]
500
- fusion -e '<source>' [json-input]
667
+
668
+ A statement evaluates `expr`, prints the result, and binds it to `identifier`
669
+ for later entries. Bare identifiers read earlier bindings; `@`-references resolve
670
+ relative to the working directory.
671
+
672
+ - Results print leniently (§9.3): a function prints as `"<function>"` instead of
673
+ becoming a `serialization_error`.
674
+ - An error prints as `!payload`. A statement binds an error result like any other
675
+ result; reading that identifier later propagates the error, exactly as reading an
676
+ `@`-reference that resolved to one. (Pattern binders never capture an error, but a
677
+ statement is an assignment, not a pattern match.)
678
+ - Rebinding a name is allowed; later entries see the new value.
679
+ - A bound function can call itself through its own name
680
+ (`fact = (0 => 1, n => [n, [n,1] | @subtract | fact] | @multiply)`), because
681
+ the name is looked up at application time.
682
+ - Entries report errors at `location: "code <inline>"`, like `-e` programs.
683
+
684
+ **Input editing.** An entry is submitted only once it parses as a complete
685
+ statement or expression; until then — whether still unfinished or not yet valid —
686
+ the session opens a new line so the entry can be finished or corrected. An entry
687
+ may therefore span multiple lines (continuation lines show `...> `); on an empty
688
+ continuation line, backspace returns to the previous line. The prompt and the
689
+ echoed input render on **stderr** (like a shell prompt), so stdout carries only
690
+ the stream of results. End the session with Ctrl-D; Ctrl-C discards the entry
691
+ being typed.
692
+
693
+ ### 9.7 Command-line interface
694
+
501
695
  ```
696
+ usage: fusion [options] <file.fsn> [json-input]
697
+ fusion [options] -e '<source>' [json-input]
698
+ fusion --repl
699
+
700
+ use cases:
701
+ (default) pipe: apply the program to one input
702
+ --stream apply the program to each line of an NDJSON stream
703
+ --repl interactive expressions and `identifier = expression`
704
+
705
+ options:
706
+ -e '<source>' inline program instead of a file
707
+ --input MODE how the input marks an error value (§9.4)
708
+ --output MODE how the output marks an error value (§9.4)
709
+ -! treat the input as an error value (unix input mode only)
710
+ ```
711
+
712
+ In the pipe use case, input comes from the `[json-input]` argument if present,
713
+ otherwise from standard input. The stream use case always reads standard input
714
+ and accepts no input argument.
502
715
 
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.
716
+ A command-line misuse (an unknown flag, an unsupported mode combination, a
717
+ missing program) is reported as plain usage text on stderr with exit code `1`.
718
+ It happens before the input/output contract begins, so it is not a payloaded
719
+ error.
@@ -5,8 +5,8 @@ every example. By the end you will have written a recursive program and understo
5
5
  Fusion's pieces fit together.*
6
6
 
7
7
  > **What you need:** Ruby installed and the `fusion` interpreter available on your
8
- > `PATH` (along with its bundled `stdlib/` folder). All commands below can be run
9
- > from any directory containing your `.fsn` files.
8
+ > `PATH`. All commands below can be run from any directory containing your `.fsn`
9
+ > files.
10
10
 
11
11
  ---
12
12
 
@@ -271,15 +271,16 @@ wrong? Try dividing by zero. Save as `boom.fsn`:
271
271
  echo '5' | fusion boom.fsn
272
272
  ```
273
273
 
274
- You will see no output on stdout, a message like `"divide: division by zero"` on
275
- stderr, and the process will exit with status `1`. The result *was* an error, and
276
- the interpreter knows the difference: it routes the error's **payload** to
277
- stderr, leaves stdout empty, and signals failure. That makes Fusion programs
278
- well-behaved Unix filters.
274
+ You will see no output on stdout, an error payload like
275
+ `{"kind":"math_error",…,"message":"division by zero"}` on stderr, and the process
276
+ will exit with status `1`. The result *was* an error, and the interpreter knows
277
+ the difference: it routes the error's **payload** to stderr, leaves stdout empty,
278
+ and signals failure. That makes Fusion programs well-behaved Unix filters.
279
279
 
280
- An error is always written as `!` followed by a **payload** any regular JSON value.
281
- The built-ins above produced `!"divide: division by zero"` (an error whose payload
282
- is a string). You can construct your own:
280
+ An error is always written as `!` followed by a **payload**. Errors the
281
+ interpreter produces (like the one above) use a standardized payload shape
282
+ see [reference §6.5](./reference.md#65-the-standardized-error-payload). You can
283
+ also construct your own, with any payload you like:
283
284
 
284
285
  | Example | Meaning |
285
286
  | ----------------------------------- | ------------------------ |