fusion-lang 0.0.1.alpha2 → 0.0.2
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.
- checksums.yaml +4 -4
- data/.mutant.yml +24 -0
- data/.simplecov +11 -0
- data/CHANGELOG.md +42 -0
- data/README.md +11 -9
- data/Rakefile +8 -0
- data/docs/lang/design.md +289 -51
- data/docs/lang/implementation.md +279 -0
- data/docs/lang/roadmap.md +20 -36
- data/docs/user/explanation.md +5 -10
- data/docs/user/how-to-guides.md +145 -15
- data/docs/user/reference.md +365 -140
- data/docs/user/tutorial.md +22 -19
- data/examples/double.fsn +4 -1
- data/examples/factorial.fsn +6 -3
- data/examples/fizzbuzz.fsn +1 -4
- data/examples/gcd.fsn +9 -0
- data/examples/json_test.fsn +4 -0
- data/examples/matrix/OP.fsn +2 -0
- data/examples/matrix/average.fsn +2 -0
- data/examples/matrix/solve.fsn +2 -0
- data/examples/palindrome.fsn +1 -1
- data/exe/fusion +12 -12
- data/lib/fusion/ast.rb +76 -27
- data/lib/fusion/atom.rb +1 -1
- data/lib/fusion/cli/decoder.rb +13 -8
- data/lib/fusion/cli/encoder.rb +2 -2
- data/lib/fusion/cli/options.rb +134 -61
- data/lib/fusion/cli/parser.rb +4 -4
- data/lib/fusion/cli/repl.rb +32 -27
- data/lib/fusion/cli/serializer.rb +11 -11
- data/lib/fusion/cli.rb +120 -49
- data/lib/fusion/interpreter/builtins.rb +298 -160
- data/lib/fusion/interpreter/env.rb +42 -12
- data/lib/fusion/interpreter/error_val.rb +42 -20
- data/lib/fusion/interpreter/thunk.rb +53 -0
- data/lib/fusion/interpreter.rb +263 -98
- data/lib/fusion/lexer.rb +125 -37
- data/lib/fusion/parser.rb +245 -70
- data/lib/fusion/version.rb +3 -1
- data/lib/fusion.rb +0 -1
- data/stdlib/all.fsn +13 -0
- data/stdlib/any.fsn +12 -0
- data/stdlib/chars.fsn +5 -0
- data/stdlib/compact.fsn +5 -0
- data/stdlib/concat.fsn +6 -0
- data/stdlib/entries.fsn +6 -0
- data/stdlib/falsey.fsn +6 -0
- data/stdlib/filter.fsn +12 -0
- data/stdlib/flatten.fsn +7 -0
- data/stdlib/map.fsn +7 -4
- data/stdlib/matrix/Matrix.fsn +8 -0
- data/stdlib/matrix/OP.fsn +18 -0
- data/stdlib/matrix/add.fsn +7 -0
- data/stdlib/matrix/column.fsn +6 -0
- data/stdlib/matrix/determinant.fsn +16 -0
- data/stdlib/matrix/dimensions.fsn +5 -0
- data/stdlib/matrix/identity.fsn +6 -0
- data/stdlib/matrix/invert.fsn +26 -0
- data/stdlib/matrix/minor.fsn +15 -0
- data/stdlib/matrix/multiply.fsn +10 -0
- data/stdlib/matrix/negate.fsn +5 -0
- data/stdlib/matrix/product.fsn +11 -0
- data/stdlib/matrix/rotate.fsn +10 -0
- data/stdlib/matrix/row.fsn +6 -0
- data/stdlib/matrix/scale.fsn +6 -0
- data/stdlib/matrix/subtract.fsn +7 -0
- data/stdlib/matrix/sum.fsn +14 -0
- data/stdlib/matrix/transpose.fsn +5 -0
- data/stdlib/range.fsn +2 -2
- data/stdlib/reduce.fsn +8 -0
- data/stdlib/safe.fsn +7 -0
- data/stdlib/sanitize.fsn +2 -3
- data/stdlib/toObject.fsn +7 -0
- data/stdlib/truthy.fsn +7 -0
- data/stdlib/vector/Vector.fsn +7 -0
- data/stdlib/vector/add.fsn +6 -0
- data/stdlib/vector/cross.fsn +6 -0
- data/stdlib/vector/dot.fsn +6 -0
- data/stdlib/vector/norm.fsn +6 -0
- data/stdlib/vector/scale.fsn +5 -0
- data/stdlib/vector/subtract.fsn +6 -0
- data/stdlib/zip.fsn +6 -0
- metadata +50 -4
- data/lib/fusion/interpreter/file_thunk.rb +0 -39
- data/stdlib/mapValues.fsn +0 -5
- data/stdlib/math/square.fsn +0 -4
data/docs/user/reference.md
CHANGED
|
@@ -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
|
|
45
|
-
3.
|
|
46
|
-
|
|
47
|
-
4. Pipe (application): `value | function
|
|
48
|
-
(`a | f | g` ≡ `(a | f) | g`).
|
|
49
|
-
5.
|
|
50
|
-
|
|
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: `??` and the comparisons `<`, `<=`, `>`, `>=`. Binary, 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
|
|
|
@@ -95,17 +101,25 @@ by the same rule.
|
|
|
95
101
|
```ebnf
|
|
96
102
|
file = expr ;
|
|
97
103
|
|
|
98
|
-
expr =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 *)
|
|
102
116
|
primary = atom | array | object | function | identifier | fileref | "(" expr ")" ;
|
|
103
117
|
|
|
104
118
|
identifier = letter { letter | digit | "_" } ;
|
|
105
119
|
|
|
106
120
|
atom = "null" | "true" | "false" | number | string ;
|
|
107
|
-
number = int_lit | float_lit ;
|
|
108
|
-
int_lit =
|
|
121
|
+
number = int_lit | float_lit ; (* unsigned; a negative is unary "-" *)
|
|
122
|
+
int_lit = digit { digit } ;
|
|
109
123
|
float_lit = int_lit "." digit { digit } [ exp ] | int_lit exp ;
|
|
110
124
|
exp = ("e" | "E") [ "+" | "-" ] digit { digit } ;
|
|
111
125
|
string = '"' { char | escape } '"' ; (* char excludes raw newline; use \n *)
|
|
@@ -119,8 +133,8 @@ spread = "..." expr ;
|
|
|
119
133
|
function = "(" [ clause { "," clause } [ "," ] ] ")" ; (* "()" is the empty function *)
|
|
120
134
|
clause = pattern "=>" expr ;
|
|
121
135
|
|
|
122
|
-
fileref = "@" [
|
|
123
|
-
|
|
136
|
+
fileref = ( "@" | "@@" ) [ path ] ; (* bare "@"/"@@" = current unit / its super *)
|
|
137
|
+
path = { ".." "/" } segment { "/" segment } ; (* one tight lexer token; ".fsn" implied *)
|
|
124
138
|
segment = identifier ;
|
|
125
139
|
|
|
126
140
|
pattern = p_error | p_guarded ;
|
|
@@ -128,7 +142,7 @@ p_error = "!" | "!" p_guarded ; (* bare "!" matches any e
|
|
|
128
142
|
p_guarded = p_core [ "?" predicate ] ;
|
|
129
143
|
predicate = pipe ; (* a `|` chain of functions; the matched value flows in *)
|
|
130
144
|
p_core = p_literal | p_bind | p_wildcard | p_array | p_object ;
|
|
131
|
-
p_literal = atom ;
|
|
145
|
+
p_literal = atom | "-" number ; (* "-" number is a negative literal *)
|
|
132
146
|
p_wildcard = "_" ;
|
|
133
147
|
p_bind = identifier ;
|
|
134
148
|
|
|
@@ -154,6 +168,60 @@ Object literals and object patterns may not repeat a fixed key. `{"a": …, "a":
|
|
|
154
168
|
is a `syntax_error`. Keys arriving through `...spread` / `...rest` are dynamic and
|
|
155
169
|
not checked.
|
|
156
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 → [a, b] | @OP.compare | @OP.lt (likewise <= / > / >= via @OP.lte / @OP.gt / @OP.gte)
|
|
198
|
+
a == b == c → [a, b, c] | @OP.equal
|
|
199
|
+
a && b && c → [a, b, c] | @OP.and
|
|
200
|
+
a || b || c → [a, b, c] | @OP.or
|
|
201
|
+
xs |: f → {"c": xs, "f": f} | @map
|
|
202
|
+
xs |? f → {"c": xs, "f": f} | @filter
|
|
203
|
+
xs |+ f → {"c": xs, "f": f} | @reduce
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Folding and associativity:
|
|
207
|
+
|
|
208
|
+
- A maximal run of `+`/`-` folds into one `@OP.sum` over all terms; each `-` term is
|
|
209
|
+
negated (a numeric literal folds to a negative literal, otherwise via `@OP.negate`).
|
|
210
|
+
- A maximal run of `*`/`/` folds into one `@OP.product`; each `/` term is inverted via
|
|
211
|
+
`@OP.invert` (never a literal, so `1/x` stays a float and `/0` stays a runtime error).
|
|
212
|
+
- Runs of `==`, `&&`, `||` fold n-ary into `@OP.equal` / `@OP.and` / `@OP.or`.
|
|
213
|
+
- `%`, `//`, `??`, `<`, `<=`, `>`, `>=` are binary and left-associative; they sit at
|
|
214
|
+
their level and break a fold run: `a * b % c` is `(a * b) % c`; `a ?? b == 0` is
|
|
215
|
+
`(a ?? b) == 0`. Comparisons do not chain: `a < b < c` is `(a < b) < c`, which
|
|
216
|
+
errors at runtime (a boolean is not comparable).
|
|
217
|
+
|
|
218
|
+
Because pipe binds tighter than the value operators, `x|@f + 1` is `(x|@f) + 1`; to pipe a
|
|
219
|
+
computed value onward, parenthesize it: `(a + b)|@f`, `(a < b)|@f`.
|
|
220
|
+
|
|
221
|
+
Comparisons: `a == b` is equality; `a < b` (and `<=` / `>` / `>=`) orders two numbers
|
|
222
|
+
or two strings. `??` exposes the underlying `-1`/`0`/`1` ordering that the comparison
|
|
223
|
+
desugaring reads through `@OP.lt` / `@OP.lte` / `@OP.gt` / `@OP.gte` (§7.2).
|
|
224
|
+
|
|
157
225
|
---
|
|
158
226
|
|
|
159
227
|
## 3. Functions and application
|
|
@@ -231,7 +299,7 @@ Rules:
|
|
|
231
299
|
`p_core ? predicate` matches when `p_core` matches structurally **and** piping the
|
|
232
300
|
matched value through `predicate` yields a **truthy** result. Truthiness is
|
|
233
301
|
Ruby-style: every value is truthy except `false` and `null` (so `0` and `""` are
|
|
234
|
-
truthy). The
|
|
302
|
+
truthy). The operators `@OP.and` / `@OP.or` / `@OP.not` apply the same test.
|
|
235
303
|
|
|
236
304
|
The predicate is a `|` chain of functions, and the matched value flows in from the
|
|
237
305
|
left: `a ? b | c` matches when `a` matches and `a | b | c` is truthy. A single-stage
|
|
@@ -313,8 +381,11 @@ particular built-ins:
|
|
|
313
381
|
is per-call: it is not enough for the function to have *some* error clause;
|
|
314
382
|
that clause must match the specific error received. An error of a shape no
|
|
315
383
|
clause catches propagates unchanged.
|
|
316
|
-
- **Built-in operations (`@
|
|
317
|
-
propagate** their input error without examining it.
|
|
384
|
+
- **Built-in and stdlib operations (`@math.divide`, `@OP.sum`, `@Integer`, …) all
|
|
385
|
+
propagate** their input error without examining it. The one deliberate
|
|
386
|
+
exception is the stdlib catcher `@safe` — an ordinary two-clause function
|
|
387
|
+
`(! => false, v => v)` that catches any error into `false` and passes regular
|
|
388
|
+
values through. To inspect or compare an
|
|
318
389
|
error's payload, you must catch it first and operate on the extracted payload:
|
|
319
390
|
`!42 | (!a => a) | @Integer` returns `true` (the payload `42` *is* an integer);
|
|
320
391
|
`!42 | @Integer` returns `!42` (the predicate doesn't handle the error,
|
|
@@ -323,7 +394,7 @@ particular built-ins:
|
|
|
323
394
|
evaluating an element/member. `[1, !"bad", 2]` evaluates to `!"bad"`, not to
|
|
324
395
|
an array of three things.
|
|
325
396
|
- **Constructing an error from an erroring expression** propagates the inner
|
|
326
|
-
error rather than wrapping it. `!([5,0] | @divide)` evaluates to the division
|
|
397
|
+
error rather than wrapping it. `!([5,0] | @math.divide)` evaluates to the division
|
|
327
398
|
error itself (a `math_error`, §6.5), never to an error wrapping an error. (This
|
|
328
399
|
preserves the rule that there is never more than one error simultaneously.)
|
|
329
400
|
- **When the function value itself is an error** (e.g. `value | @undefined_name`
|
|
@@ -334,9 +405,10 @@ particular built-ins:
|
|
|
334
405
|
If a `?` predicate evaluates to an error (the predicate function itself errored,
|
|
335
406
|
or it was a non-function error value), that error becomes the function's return
|
|
336
407
|
value immediately. Subsequent clauses are **not** tried — predicate-errors are
|
|
337
|
-
treated as program failures, not as "no match." This is the key reason to
|
|
338
|
-
|
|
339
|
-
|
|
408
|
+
treated as program failures, not as "no match." This is the key reason to keep a
|
|
409
|
+
`?` predicate from raising: a predicate that can crash will short-circuit your
|
|
410
|
+
whole function. A non-matching input is not a crash — it falls through to `null`
|
|
411
|
+
(falsey) — so a predicate needs no `_ => false` catch-all to be safe here.
|
|
340
412
|
|
|
341
413
|
### 6.5 The standardized error payload
|
|
342
414
|
|
|
@@ -354,46 +426,48 @@ There are two origins of error values, and they differ in payload:
|
|
|
354
426
|
#### Payload shape
|
|
355
427
|
|
|
356
428
|
```json
|
|
357
|
-
{"kind": "
|
|
429
|
+
{"kind": "argument_error", "origin": "builtin", "operation": "@math.divide", "status": 0, "input": [1, "x"], "expected": ["[_ ? @Number, _ ? @Number]"]}
|
|
358
430
|
```
|
|
359
431
|
|
|
360
432
|
| Field | Required | Meaning |
|
|
361
433
|
| ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
362
|
-
| `kind` | yes | The error category
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
434
|
+
| `kind` | yes | The error category. Possible values are defined below. |
|
|
435
|
+
| `origin` | yes | Where the failing operation is *defined*. Possible values are defined below. |
|
|
436
|
+
| `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`). |
|
|
437
|
+
| `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"`. |
|
|
438
|
+
| `status` | yes | `0` or `1`. Whether the operation received an ordinary value (`0`) or an error value (`1`) |
|
|
439
|
+
| `input` | yes | The operation's input. A 0-argument operation (every `@`-reference except `@load`) carries `null`. |
|
|
440
|
+
| `expected` | no | The acceptable inputs as a list of Fusion **patterns**. The input matched none of them. |
|
|
441
|
+
| `message` | no | Extra human-readable detail, e.g. `"division by zero"`. Absent whenever `expected` is present. |
|
|
367
442
|
|
|
368
|
-
#### `kind`
|
|
443
|
+
#### Possible values for `kind`
|
|
369
444
|
|
|
370
445
|
| `kind` | Raised when |
|
|
371
446
|
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
372
|
-
| `syntax_error` | source code
|
|
373
|
-
| `reference_error` | an `@`-reference cannot be resolved: unknown name, file not found, a file-system failure, a non-productive data cycle,
|
|
374
|
-
| `
|
|
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 [_, _]`). |
|
|
447
|
+
| `syntax_error` | source code or the JSON input fails to parse. |
|
|
448
|
+
| `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). |
|
|
449
|
+
| `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. |
|
|
376
450
|
| `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
|
|
451
|
+
| `access_error` | a missing object key or an out-of-range array index. |
|
|
378
452
|
| `math_error` | division or modulo by zero, or a non-finite number. |
|
|
379
453
|
| `conversion_error` | a value cannot be converted (`@toString` of an unconvertible type, `@parseNumber` of a non-numeric string). |
|
|
380
|
-
| `
|
|
381
|
-
| `
|
|
454
|
+
| `limit_error` | a runtime resource limit was exceeded. `"stack level too deep"`. |
|
|
455
|
+
| `internal_error` | an interpreter BUG. Please open an issue. |
|
|
456
|
+
| `serialization_error` | a result/error value has no JSON form. It contains functions or non-finite numbers. See §9. |
|
|
382
457
|
|
|
383
|
-
####
|
|
458
|
+
#### Possible values for `origin`
|
|
384
459
|
|
|
385
|
-
| `
|
|
386
|
-
|
|
|
387
|
-
| `builtin
|
|
388
|
-
| `stdlib
|
|
389
|
-
| `code
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `interpreter` | the interpreter itself, e.g. a stack overflow. |
|
|
460
|
+
| `origin` | Meaning |
|
|
461
|
+
| ------------- | ------------------------------------------------------------------------ |
|
|
462
|
+
| `builtin` | a built-in operation (named by its `@`-reference in `operation`). |
|
|
463
|
+
| `stdlib` | a standard-library function (named by its `@`-reference in `operation`). |
|
|
464
|
+
| `code` | user source core (a file or an inline expression (`-e`/REPL). |
|
|
465
|
+
| `input` | the input channel (stdin). Usually syntax errors. |
|
|
466
|
+
| `output` | the output channel. Usually serialization errors. |
|
|
467
|
+
| `interpreter` | the interpreter itself, e.g. a stack overflow. |
|
|
394
468
|
|
|
395
469
|
`input` and `output` name the data channels; they **never** refer to the program
|
|
396
|
-
source, which always reports as `code
|
|
470
|
+
source, which always reports as `code`.
|
|
397
471
|
|
|
398
472
|
User errors don't have to adhere to this standard.
|
|
399
473
|
|
|
@@ -402,59 +476,74 @@ User errors don't have to adhere to this standard.
|
|
|
402
476
|
## 7. Built-in functions
|
|
403
477
|
|
|
404
478
|
All built-ins are ordinary one-argument functions, **reached with an `@` prefix**
|
|
405
|
-
(`@
|
|
479
|
+
(`@size`, `@Integer`, …); see §9.2 for how `@name` resolves. The names in the tables
|
|
406
480
|
below are the built-in names; write `@` before them to use them. **Operations** return
|
|
407
481
|
`!` on type-invalid or domain-invalid input. **Predicates** return `false` on any
|
|
408
482
|
input that is not of the queried type (they never return `!`).
|
|
409
483
|
|
|
410
|
-
|
|
484
|
+
The operators (`+ - * / == < …` and the boolean ops) live in the shadowable `@OP`
|
|
485
|
+
object (§7.6); a directory reskins them by placing an `OP.fsn`.
|
|
486
|
+
|
|
487
|
+
### 7.1 Arithmetic
|
|
411
488
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
| `mod` | `[number, number]`| remainder; `!` if divisor is 0 |
|
|
419
|
-
| `negate` | `number` | negation |
|
|
420
|
-
| `floor` | `number` | floor (integer) |
|
|
489
|
+
In source you normally write the infix sugar of §2.7 — `a + b`, `-a`, `a * b`, `a % b`,
|
|
490
|
+
`a // b` — which desugars to the members below. Addition, multiplication and negation are
|
|
491
|
+
`@OP.sum` / `@OP.product` / `@OP.negate` (§7.6); subtraction is `[a, b | @OP.negate] | @OP.sum`.
|
|
492
|
+
Integer division/remainder are `@OP.quotient` / `@OP.modulo`. Numerically correct
|
|
493
|
+
division and the other numeric functions live in `@math` (§7.6a): `@math.divide`,
|
|
494
|
+
`@math.floor`, `@math.round`, `@math.abs`, `@math.log`, `@math.sqrt`, etc.
|
|
421
495
|
|
|
422
|
-
### 7.2 Comparison
|
|
496
|
+
### 7.2 Comparison
|
|
423
497
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
| `lessThan` | `[number, number]` or `[string, string]`| boolean; `!` on mismatched/invalid types |
|
|
498
|
+
Equality is `@OP.equal` (deep structural equality of a pair; §7.6) — used directly,
|
|
499
|
+
there is no `@eq` helper. Ordering is `@OP.compare`, which returns `-1`/`0`/`1`. The
|
|
500
|
+
comparison members **interpret that result** and are applied *after* it:
|
|
428
501
|
|
|
429
|
-
|
|
430
|
-
|
|
502
|
+
| Member | `-1` | `0` | `1` | `null` |
|
|
503
|
+
| -------- | ------ | ------ | ------ | ------ |
|
|
504
|
+
| `OP.lt` | `true` | `false`| `false`| `null` |
|
|
505
|
+
| `OP.gt` | `false`| `false`| `true` | `null` |
|
|
506
|
+
| `OP.lte` | `true` | `true` | `false`| `null` |
|
|
507
|
+
| `OP.gte` | `false`| `true` | `true` | `null` |
|
|
431
508
|
|
|
432
|
-
|
|
509
|
+
`a < b` desugars to exactly this pipeline: `[a, b] | @OP.compare | @OP.lt` (likewise
|
|
510
|
+
`<=` / `>` / `>=`; equality is `a == b` — see §2.7). Both steps resolve `@OP` per
|
|
511
|
+
directory, so an override reskins the ordering and its reading together. A partial
|
|
512
|
+
order whose `compare` returns `null` for incomparable operands passes that `null`
|
|
513
|
+
straight through; any other input is an `argument_error`. A type mismatch surfaces
|
|
514
|
+
as `@OP.compare`'s own error, before the reader runs.
|
|
433
515
|
|
|
434
|
-
|
|
435
|
-
is truthy except `false` and `null`), not strict booleans, and always return a boolean.
|
|
516
|
+
### 7.3 Boolean
|
|
436
517
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
518
|
+
The truthiness operators live in `@OP` (§7.6): `@OP.and`, `@OP.or`, `@OP.not`, written
|
|
519
|
+
with the sugar `a && b`, `a || b`, `~a` (§2.7). They
|
|
520
|
+
judge truthiness (every value is truthy except `false` and `null`), not strict
|
|
521
|
+
booleans, and always return a boolean. There are no top-level `@and`/`@or`/`@not`.
|
|
522
|
+
The stdlib helpers `@truthy` and `@falsey` reduce any single value to its truthiness
|
|
523
|
+
by **pattern matching** (independent of any `@OP` override): `@truthy` is `true` for
|
|
524
|
+
everything except `false`/`null`, and `@falsey` is its complement.
|
|
442
525
|
|
|
443
526
|
### 7.4 Strings and structure bridges (operations)
|
|
444
527
|
|
|
445
528
|
| Name | Input | Result |
|
|
446
529
|
| ------------- | --------------------------- | --------------------------------------- |
|
|
447
|
-
| `
|
|
448
|
-
| `
|
|
449
|
-
| `
|
|
450
|
-
| `join` | `[array-of-strings, string]`| joined string |
|
|
530
|
+
| `size` | string / array / object | element/character/key count (integer) |
|
|
531
|
+
| `join` | `[array-of-strings, separator]` | the elements joined by the separator string |
|
|
532
|
+
| `split` | `[string, separator]` | array split on the **literal** separator (an empty separator splits into characters), keeping empty fields |
|
|
451
533
|
| `toString` | any | string form of the value |
|
|
452
534
|
| `parseNumber` | string | integer or float; `!` if not numeric |
|
|
453
535
|
| `keys` | object | array of key strings |
|
|
454
536
|
| `values` | object | array of values |
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
537
|
+
|
|
538
|
+
`concat` (array of strings, any length → concatenation) and `chars` (string →
|
|
539
|
+
array of its characters) are **standard-library** functions built on `join` /
|
|
540
|
+
`split`, not built-ins. So are `entries` (object → array of its `[key, value]` entries, in
|
|
541
|
+
insertion order; built on `keys`) and `toObject` (`[[string-key, value], …]` →
|
|
542
|
+
object, later duplicate keys win; built on the `[=]` setter). They are inverses:
|
|
543
|
+
`obj | @entries | @toObject` is `obj`.
|
|
544
|
+
|
|
545
|
+
Indexed read (`x[k]`) and write (`x[k = v]`) are **core syntax**, not built-ins — there
|
|
546
|
+
is no `@get`/`@set`; see §8.
|
|
458
547
|
|
|
459
548
|
### 7.5 Type predicates (predicates)
|
|
460
549
|
|
|
@@ -471,6 +560,7 @@ functions provides a runtime type system.
|
|
|
471
560
|
| `String` | strings | |
|
|
472
561
|
| `Array` | arrays | `[_]` |
|
|
473
562
|
| `Object` | objects | `{_}` |
|
|
563
|
+
| `Collection`| arrays and objects | |
|
|
474
564
|
| `Function` | any function (builtin, stdlib, user) | |
|
|
475
565
|
| `NonFinite` | "Infinity", "-Infinity", "NaN" | |
|
|
476
566
|
|
|
@@ -478,7 +568,64 @@ Notes:
|
|
|
478
568
|
- Booleans are separate from numbers. There's no automatic type conversion (`false` <-> `0`, `true` <-> `1`).
|
|
479
569
|
- The set of values without JSON representation (§9.3) is exactly `Function` + `NonFinite`
|
|
480
570
|
|
|
481
|
-
### 7.6
|
|
571
|
+
### 7.6 The `@OP` object (the operators)
|
|
572
|
+
|
|
573
|
+
`@OP` is a built-in **object**, reached by member access (`@OP.sum`, `@OP.and`, …),
|
|
574
|
+
holding the arithmetic/comparison/boolean operators. Its members generalise to an
|
|
575
|
+
**array of any length** (`sum`/`product`/`and`/`or` fold; `equal` is deep over all
|
|
576
|
+
elements); `compare` reports an ordering. The infix operators (§2.7) desugar to these members.
|
|
577
|
+
|
|
578
|
+
`@OP` is **shadowable per directory**: place an `OP.fsn` sibling that overrides members
|
|
579
|
+
(spread the originals with `@@`) to reskin the operators — complex numbers, matrices,
|
|
580
|
+
ternary logic — for that directory only. The comparison members (§7.2: `lt`, `gt`,
|
|
581
|
+
`lte`, `gte`) interpret an `@OP.compare` *result* rather than comparing values
|
|
582
|
+
themselves, so an override of `compare` alone already reskins `a < b` (see the
|
|
583
|
+
how-to guide).
|
|
584
|
+
|
|
585
|
+
| Member | Input | Result |
|
|
586
|
+
| ------------- | ---------------------------------------- | -------------------------------------------------------- |
|
|
587
|
+
| `OP.sum` | array of numbers | sum (`0` for `[]`) |
|
|
588
|
+
| `OP.product` | array of numbers | product (`1` for `[]`) |
|
|
589
|
+
| `OP.negate` | number | negation |
|
|
590
|
+
| `OP.invert` | number | reciprocal `1/x`, always a float; `!` if `0` |
|
|
591
|
+
| `OP.quotient` | `[integer, integer]` | integer division; `!` on a non-integer or a `0` divisor |
|
|
592
|
+
| `OP.modulo` | `[integer, integer]` | integer remainder; `!` on a non-integer or a `0` divisor |
|
|
593
|
+
| `OP.equal` | array (any element types) | deep equality: `true` iff every element equals the first |
|
|
594
|
+
| `OP.compare` | `[number, number]` or `[string, string]` | `-1` / `0` / `1` (first smaller / equal / larger) |
|
|
595
|
+
| `OP.lt` | a compare result (`-1`/`0`/`1`/`null`) | `true` for `-1`, else `false`; `null` passes through |
|
|
596
|
+
| `OP.gt` | a compare result | `true` for `1` |
|
|
597
|
+
| `OP.lte` | a compare result | `true` for `-1` / `0` |
|
|
598
|
+
| `OP.gte` | a compare result | `true` for `0` / `1` |
|
|
599
|
+
| `OP.and` | array | `true` if every element is truthy (`true` for `[]`) |
|
|
600
|
+
| `OP.or` | array | `true` if any element is truthy (`false` for `[]`) |
|
|
601
|
+
| `OP.not` | `_` | `true` if the operand is falsey |
|
|
602
|
+
|
|
603
|
+
### 7.6a The `@math` object (numeric functions and constants)
|
|
604
|
+
|
|
605
|
+
`@math` is a built-in object (shadowable like `@OP`) of numeric functions and two
|
|
606
|
+
constants. `pi`/`e` are plain values; the rest are one-argument functions. A
|
|
607
|
+
non-finite input to `round`/`floor`/`ceil`, a `log` of a non-positive number, and a
|
|
608
|
+
`pow` with a complex result are `math_error`s.
|
|
609
|
+
|
|
610
|
+
| Member | Input | Result |
|
|
611
|
+
| ------------- | -------------------- | ---------------------------------------------------------- |
|
|
612
|
+
| `math.pi` | — | `3.141592653589793` (a value, not a function) |
|
|
613
|
+
| `math.e` | — | `2.718281828459045` (a value) |
|
|
614
|
+
| `math.round` | number | nearest integer (half away from zero) |
|
|
615
|
+
| `math.floor` | number | floor (integer) |
|
|
616
|
+
| `math.ceil` | number | ceiling (integer) |
|
|
617
|
+
| `math.divide` | `[number, number]` | quotient, always a **float**; `!` if divisor is 0 |
|
|
618
|
+
| `math.sign` | number | `-1` / `0` / `1` |
|
|
619
|
+
| `math.abs` | number | absolute value (keeps int/float) |
|
|
620
|
+
| `math.rand` | `null` or a positive integer `n` | float in `[0, 1)`, or integer in `[0, n)` |
|
|
621
|
+
| `math.sin` | number | sine (radians), float |
|
|
622
|
+
| `math.cos` | number | cosine (radians), float |
|
|
623
|
+
| `math.exp` | number | `e^x`, float |
|
|
624
|
+
| `math.log` | positive number | natural log, float; `!` on a non-positive number |
|
|
625
|
+
| `math.pow` | `[base, exponent]` | `base^exp` (integer when base and non-negative integer exponent; else float); `!` on a complex result |
|
|
626
|
+
| `math.sqrt` | non-negative number | square root (float); `!` on a negative number |
|
|
627
|
+
|
|
628
|
+
### 7.7 Special built-ins: `ENV` and `load`
|
|
482
629
|
|
|
483
630
|
These resolve in the `@name` chain like other built-ins (so a sibling file of the
|
|
484
631
|
same name shadows them), but they are not plain unary value functions:
|
|
@@ -495,12 +642,18 @@ same name shadows them), but they are not plain unary value functions:
|
|
|
495
642
|
|
|
496
643
|
## 8. Member and index access
|
|
497
644
|
|
|
645
|
+
Member access (`.`), index read (`[]`), and index write (`[=]`) are **core syntax**,
|
|
646
|
+
evaluated directly by the runtime. There are no `@get`/`@set` built-ins.
|
|
647
|
+
|
|
498
648
|
- `x.key` — if `x` is an object containing `key`, its value; otherwise `!`.
|
|
499
|
-
- `x[expr]` — if `x` is an array and `expr` is an integer in range, the element
|
|
500
|
-
(negative indices count from the end); if `x` is an object and `expr` is a string
|
|
501
|
-
|
|
649
|
+
- `x[expr]` — **read**: if `x` is an array and `expr` is an integer in range, the element
|
|
650
|
+
(negative indices count from the end); if `x` is an object and `expr` is a string key
|
|
651
|
+
that exists, its value; otherwise `!`.
|
|
652
|
+
- `x[key = value]` — **write**: a **new** array/object with that one entry set; `x` itself
|
|
653
|
+
is unchanged. An array index must already exist (arrays are not extended; negative
|
|
654
|
+
indices count from the end); an object key may be new. `!` on a bad index/type.
|
|
502
655
|
|
|
503
|
-
|
|
656
|
+
`.`, `[]`, and `[=]` are postfix and bind tighter than every operator (including `|`).
|
|
504
657
|
|
|
505
658
|
---
|
|
506
659
|
|
|
@@ -515,7 +668,19 @@ A `.fsn` file contains **exactly one expression**, which is its value. A file is
|
|
|
515
668
|
|
|
516
669
|
A `@` reference takes one of these forms:
|
|
517
670
|
|
|
518
|
-
- **`@`** (nothing after it) — the **current
|
|
671
|
+
- **`@`** (nothing after it) — the value of the **current top-level unit**: the
|
|
672
|
+
current file, or the inline (`-e`) / REPL entry being evaluated. Used for
|
|
673
|
+
self-recursion.
|
|
674
|
+
- **`@@`** (super) — the built-in or standard-library value the current file
|
|
675
|
+
**shadows**: the file's own name resolved by steps 2–3 below, skipping the
|
|
676
|
+
sibling step (which would be the file itself). Lets an override refer to the
|
|
677
|
+
original method, e.g. `add.fsn` containing `@@` refers to the stdlib `add`.
|
|
678
|
+
Outside a file (inline `-e` / REPL) there is no name to take super of, so it is a
|
|
679
|
+
`reference_error` (`no enclosing file`).
|
|
680
|
+
- **`@@name`, `@@dir/name`** — super with an explicit name: resolve `name` by
|
|
681
|
+
steps 2–3 below, skipping its sibling. The **stable** form of a reference — a
|
|
682
|
+
local shadow cannot intercept it (used inside an `OP.fsn` as `@@OP`, and by
|
|
683
|
+
error patterns that must stay canonical). `@@../…` is a syntax error.
|
|
519
684
|
- **`@ENV`** — an object of all environment variables (string keys, string values;
|
|
520
685
|
no parsing). Resolved in the `@name` chain below, so it is shadowable.
|
|
521
686
|
- **`@name`** — a single bare identifier (no `/`, no `../`).
|
|
@@ -529,9 +694,9 @@ in order, first match winning:
|
|
|
529
694
|
2. a **built-in** of that exact name (including `ENV` and `load`);
|
|
530
695
|
3. a **standard-library file** at `<stdlib root>/<name>.fsn`.
|
|
531
696
|
|
|
532
|
-
If none match, the result is `!`. Downward paths participate fully: `@
|
|
533
|
-
checks a sibling `
|
|
534
|
-
`
|
|
697
|
+
If none match, the result is `!`. Downward paths participate fully: `@util/helper`
|
|
698
|
+
checks a sibling `util/helper.fsn`, then a built-in named `util/helper`, then a stdlib
|
|
699
|
+
`util/helper.fsn`.
|
|
535
700
|
|
|
536
701
|
**Resolution of upward paths** (any reference containing `../`) is **file-only**: it
|
|
537
702
|
resolves solely to a file relative to the referencing directory and never falls back
|
|
@@ -542,7 +707,20 @@ is relative to the **referencing file's** directory; built-ins and the standard
|
|
|
542
707
|
library are global to the runtime but, per the order above, are shadowed by a sibling
|
|
543
708
|
file of the same name. That shadowing is per-directory, not global.
|
|
544
709
|
|
|
545
|
-
**
|
|
710
|
+
**Confinement (the jail).** File-backed resolution is confined to a *jail*: a directory
|
|
711
|
+
and its subtree, set by `-j`/`--jail` and defaulting to the program's directory (the
|
|
712
|
+
working directory for `-e` and the REPL). All `@`-references and the builtin `@load`
|
|
713
|
+
respect the jail. Referencing a file outside the jail is a `reference_error`
|
|
714
|
+
(`outside the jail`). An existing sibling outside the jail fails this way too — it does
|
|
715
|
+
*not* fall back to a built-in or the stdlib, so a forbidden file fails loudly rather than
|
|
716
|
+
silently resolving elsewhere. References still resolve relative to the referencing file;
|
|
717
|
+
the jail only filters the result. The standard library is always reachable regardless of
|
|
718
|
+
the jail, and stdin is never affected — it is plain JSON, never an `@`-reference.
|
|
719
|
+
Confinement is lexical (it normalises `..`) and follows existing symlinks. It confines
|
|
720
|
+
references to a directory tree; it is not a security sandbox and needs none, since Fusion
|
|
721
|
+
cannot write files. Pass `--jail '*'` to disable confinement entirely.
|
|
722
|
+
|
|
723
|
+
**Built-ins are reached through this same mechanism**: `@size`, `@Integer`, etc. are
|
|
546
724
|
`@name` references that resolve at step 2. A *bare* identifier (without `@`) is only
|
|
547
725
|
a pattern hole; it never denotes a built-in.
|
|
548
726
|
|
|
@@ -551,9 +729,8 @@ Two built-ins are special in how they resolve:
|
|
|
551
729
|
- **`@ENV`** resolves (at step 2) to a fresh object of environment variables.
|
|
552
730
|
- **`@load`** resolves (at step 2) to a function that loads a file by a **verbatim**
|
|
553
731
|
filename string — no `.fsn` is appended — relative to the referencing directory,
|
|
554
|
-
returning that file's value.
|
|
555
|
-
|
|
556
|
-
`{"kind":"file_not_found","path":...}` respectively). This is the only way to load
|
|
732
|
+
returning that file's value. A non-string argument is an `argument_error`; a
|
|
733
|
+
missing file is a `reference_error` (`file not found`). This is the only way to load
|
|
557
734
|
a file whose name is computed at runtime or is not a plain identifier (e.g.
|
|
558
735
|
`"data.config.fsn"`).
|
|
559
736
|
|
|
@@ -572,12 +749,19 @@ above. Recursion through functions is not a data cycle.
|
|
|
572
749
|
|
|
573
750
|
### 9.3 Runtime contract
|
|
574
751
|
|
|
575
|
-
The
|
|
576
|
-
|
|
577
|
-
standard output as JSON.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
752
|
+
The **pipe** use case (`--pipe`, and the default whenever any argument is given —
|
|
753
|
+
see §9.7) reads standard input as JSON, converts it to a Fusion value `v`,
|
|
754
|
+
computes `v | programFunction`, and prints the result on standard output as JSON.
|
|
755
|
+
When standard input is empty, the program get evaluated and immediately becomes
|
|
756
|
+
the result instead.
|
|
757
|
+
|
|
758
|
+
- Input always arrives on standard input; there is no input argument.
|
|
759
|
+
- **Empty input means "no input": the program's own value is the result.** A
|
|
760
|
+
`.fsn` file therefore doubles as enriched JSON data — it can compute, read
|
|
761
|
+
`@ENV`, and pull in `@`-references, then print the value with no pipeline
|
|
762
|
+
input. (Under `-!` the input is an error value instead; empty input then has no
|
|
763
|
+
payload to mark, which is a usage error — see §9.4.)
|
|
764
|
+
- Non-JSON input yields a `syntax_error` at `origin: "input"` (§6.5).
|
|
581
765
|
- **If the final result is an error**, the interpreter prints **nothing** to
|
|
582
766
|
standard output, prints the error's **payload** (as JSON) to standard error, and
|
|
583
767
|
exits with status `1`. Otherwise the result is printed to standard output and the
|
|
@@ -613,20 +797,25 @@ They are independent of each other and selected with the `--input` and `--output
|
|
|
613
797
|
flags:
|
|
614
798
|
|
|
615
799
|
- **`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
|
-
|
|
618
|
-
|
|
800
|
+
the whole input as an error value instead (its JSON becomes the payload). `-!`
|
|
801
|
+
therefore requires input: with empty input there is no payload to mark, which
|
|
802
|
+
is a usage error (the program does not run). Output: a value goes to stdout
|
|
803
|
+
with exit code `0`; an error's payload goes to stderr with exit code `1`
|
|
804
|
+
(§9.3).
|
|
619
805
|
- **`bang`** — a leading `!` marks an error value; the payload is the JSON after
|
|
620
806
|
the `!`. A lone `!` is `!null`, like the language's bare `!`. Output is always
|
|
621
807
|
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
|
|
808
|
+
that is the price of the most lightweight marking, so `bang` is recommended only
|
|
809
|
+
between Fusion programs — for anything that must stay valid JSON, use `array` or
|
|
810
|
+
`object`.
|
|
623
811
|
- **`array`** — everything is wrapped in an envelope: `[0, value]` for a value,
|
|
624
|
-
`[1, payload]` for an error.
|
|
812
|
+
`[1, payload]` for an error. Every line is valid JSON, which is why it is the
|
|
813
|
+
`--stream` default (§9.5). Output is always on stdout, exit code always `0`.
|
|
625
814
|
- **`object`** — the envelope is `{"value": value}` for a value, `{"error": payload}`
|
|
626
815
|
for an error. Output is always on stdout, exit code always `0`.
|
|
627
816
|
|
|
628
817
|
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 `
|
|
818
|
+
be exactly the integer `0` or `1`) is an `argument_error` at `origin: "input"`.
|
|
630
819
|
Like any input failure it flows into the program as an error and is catchable.
|
|
631
820
|
|
|
632
821
|
Mode support per use case (defaults in bold):
|
|
@@ -634,29 +823,45 @@ Mode support per use case (defaults in bold):
|
|
|
634
823
|
| Use case | `unix` | `bang` | `array` | `object` |
|
|
635
824
|
| ---------- | -------- | -------- | ------- | -------- |
|
|
636
825
|
| pipe | **yes** | yes | yes | yes |
|
|
637
|
-
| `--stream` | no | **yes**
|
|
826
|
+
| `--stream` | no | yes | **yes** | yes |
|
|
638
827
|
| `--repl` | — | — | — | — |
|
|
639
828
|
|
|
640
829
|
The unix mode spends the process's only exit code and both standard streams on a
|
|
641
830
|
single result, so it cannot mark errors per record in a stream; the stream use
|
|
642
|
-
case therefore excludes it.
|
|
831
|
+
case therefore excludes it. Stream defaults to `array` rather than `bang` so each
|
|
832
|
+
record stays valid JSON (NDJSON, §9.5); `bang` remains available as the cheapest
|
|
833
|
+
encoding for Fusion-to-Fusion pipelines. The REPL is interactive and has no modes
|
|
834
|
+
at all.
|
|
643
835
|
|
|
644
836
|
### 9.5 Streaming (`--stream`)
|
|
645
837
|
|
|
646
838
|
`fusion --stream` loads the program once, then treats standard input and output
|
|
647
|
-
as NDJSON streams: each input line is
|
|
648
|
-
the program, and printed as one output
|
|
839
|
+
as [NDJSON](https://github.com/ndjson/ndjson-spec) streams: each input line is
|
|
840
|
+
decoded per the input mode, piped through the program, and printed as one output
|
|
841
|
+
line encoded per the output mode. Input and output default to the **array** mode
|
|
842
|
+
(not `bang`) so every line is valid JSON. The media type is
|
|
843
|
+
`application/x-ndjson` and the file extension for storing such a stream should
|
|
844
|
+
be `.ndjson`.
|
|
845
|
+
|
|
846
|
+
NDJSON conformance:
|
|
847
|
+
- Every output record is a single JSON text in UTF-8, terminated by `\n`, and
|
|
848
|
+
never contains an embedded newline or carriage return.
|
|
849
|
+
- Both `\n` and `\r\n` are accepted as input line delimiters.
|
|
850
|
+
- A blank input line (empty or whitespace-only) carries no record, so the program
|
|
851
|
+
never runs on it. By default it is echoed as a blank output line, keeping input
|
|
852
|
+
and output aligned line-for-line. Pass `--skip-blank-lines` to drop blank lines
|
|
853
|
+
instead. Every non-blank line produces exactly one output line.
|
|
649
854
|
|
|
650
|
-
- Blank lines are skipped; every other input line produces exactly one output line.
|
|
651
855
|
- Errors stay in-band, so a failing record — including a stack overflow — becomes
|
|
652
856
|
that record's output line and the stream continues. The exit code is always `0`.
|
|
653
|
-
- A program that fails to load
|
|
857
|
+
- A program that fails to load will return the same load error for every record.
|
|
654
858
|
|
|
655
859
|
### 9.6 The REPL (`--repl`)
|
|
656
860
|
|
|
657
|
-
`fusion --repl` starts an interactive session
|
|
658
|
-
|
|
659
|
-
|
|
861
|
+
`fusion --repl` starts an interactive session — as does a bare `fusion` with no
|
|
862
|
+
arguments at all (§9.7). It loads no program, takes no pipeline input, has no
|
|
863
|
+
input/output mode, and always exits `0`. Each entry is read, evaluated, and its
|
|
864
|
+
result printed. An entry is one of:
|
|
660
865
|
|
|
661
866
|
- an **expression** — evaluated and printed; or
|
|
662
867
|
- a **statement** — an assignment that also binds a name:
|
|
@@ -677,43 +882,63 @@ relative to the working directory.
|
|
|
677
882
|
statement is an assignment, not a pattern match.)
|
|
678
883
|
- Rebinding a name is allowed; later entries see the new value.
|
|
679
884
|
- A bound function can call itself through its own name
|
|
680
|
-
(`fact = (0 => 1, n => [n, [n
|
|
885
|
+
(`fact = (0 => 1, n => [n, [n,-1] | @OP.sum | fact] | @OP.product)`), because
|
|
681
886
|
the name is looked up at application time.
|
|
682
|
-
- Entries report errors at `
|
|
887
|
+
- Entries report errors at `origin: "code"` with `file: "<inline>"`, like `-e` programs.
|
|
683
888
|
|
|
684
889
|
**Input editing.** An entry is submitted only once it parses as a complete
|
|
685
|
-
statement or expression; until then
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
890
|
+
statement or expression; until then the session opens a new line so the entry
|
|
891
|
+
can be finished or corrected. An entry may therefore span multiple lines
|
|
892
|
+
(continuation lines show `...> `); on an empty continuation line, backspace
|
|
893
|
+
returns to the previous line. The prompt and the echoed input render on **stderr**
|
|
894
|
+
(like a shell prompt), so stdout carries only the stream of results.
|
|
895
|
+
The prompt is shown in light blue, and each result is preceded **on stderr**
|
|
896
|
+
by a green `✔` (a value) or a red `✗` (an error); these
|
|
897
|
+
are decorations only — the result itself stays unstyled on stdout. End the
|
|
898
|
+
session with Ctrl-D; Ctrl-C discards the entry being typed.
|
|
692
899
|
|
|
693
900
|
### 9.7 Command-line interface
|
|
694
901
|
|
|
695
902
|
```
|
|
696
|
-
usage: fusion [options] <file.fsn>
|
|
697
|
-
fusion [options] -e '<source>'
|
|
903
|
+
usage: fusion [options] <file.fsn>
|
|
904
|
+
fusion [options] -e '<source>'
|
|
698
905
|
fusion --repl
|
|
699
906
|
|
|
700
|
-
use cases:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
--
|
|
907
|
+
use cases (default: --repl with no arguments, otherwise --pipe):
|
|
908
|
+
-p, --pipe apply the program to stdin; with no input, the
|
|
909
|
+
program's own value is the result
|
|
910
|
+
-s, --stream apply the program to each line of an NDJSON stream
|
|
911
|
+
-r, --repl interactive expressions and `identifier = expression`
|
|
704
912
|
|
|
705
913
|
options:
|
|
706
|
-
-e '<source>'
|
|
707
|
-
|
|
708
|
-
--
|
|
914
|
+
-e, --execute '<source>'
|
|
915
|
+
inline program instead of a file
|
|
916
|
+
-i, --input MODE
|
|
917
|
+
how the input marks an error value (§9.4)
|
|
918
|
+
-o, --output MODE
|
|
919
|
+
how the output marks an error value (§9.4)
|
|
920
|
+
-j, --jail DIR confine @-references to DIR and its subtree
|
|
921
|
+
(default: the program's directory; '*' disables it; §9.2)
|
|
709
922
|
-! treat the input as an error value (unix input mode only)
|
|
923
|
+
-b, --skip-blank-lines
|
|
924
|
+
drop blank input lines instead of echoing them (--stream, §9.5)
|
|
710
925
|
```
|
|
711
926
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
927
|
+
**Selecting a use case.** At most one of `--pipe`, `--stream`, `--repl` may be
|
|
928
|
+
given; passing two is a command-line misuse. With none, a bare `fusion` (no
|
|
929
|
+
arguments at all) starts the REPL, while any other invocation is a pipe run. So
|
|
930
|
+
`--pipe` is needed only to be explicit, `fusion file.fsn` already implicitly
|
|
931
|
+
use `--pipe`.
|
|
932
|
+
|
|
933
|
+
In the pipe use case, input comes from standard input; when standard input is
|
|
934
|
+
empty, the program's own value is the result (§9.3). The stream use case also
|
|
935
|
+
reads standard input. Neither accepts an input argument.
|
|
936
|
+
|
|
937
|
+
Every flag has a short and a long form (`-p`/`--pipe`, `-i`/`--input`, …), except
|
|
938
|
+
`-!`, which has only the short form. Each of `--input`/`--output` may only be used
|
|
939
|
+
once. Multiple different modes for one direction is a misuse.
|
|
715
940
|
|
|
716
|
-
A command-line misuse (an unknown flag,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
error.
|
|
941
|
+
A command-line misuse (an unknown flag, more than one use case, two different
|
|
942
|
+
modes for one direction, an unsupported mode combination, a missing program) is
|
|
943
|
+
reported as plain usage text on stderr with exit code `1`. It happens before the
|
|
944
|
+
input/output contract begins, so it is not a payloaded error.
|