fusion-lang 0.0.1.alpha2 → 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.
- checksums.yaml +4 -4
- data/README.md +9 -8
- data/docs/lang/design.md +240 -51
- data/docs/lang/implementation.md +238 -0
- data/docs/lang/roadmap.md +20 -36
- data/docs/user/explanation.md +5 -10
- data/docs/user/how-to-guides.md +60 -15
- data/docs/user/reference.md +356 -142
- data/docs/user/tutorial.md +21 -19
- data/examples/double.fsn +1 -1
- data/examples/factorial.fsn +2 -2
- data/examples/fizzbuzz.fsn +1 -4
- data/examples/json_test.fsn +4 -0
- data/examples/palindrome.fsn +1 -1
- data/exe/fusion +10 -10
- data/lib/fusion/ast.rb +2 -1
- data/lib/fusion/cli/decoder.rb +10 -5
- data/lib/fusion/cli/options.rb +130 -60
- data/lib/fusion/cli/parser.rb +3 -3
- data/lib/fusion/cli/repl.rb +30 -25
- data/lib/fusion/cli/serializer.rb +5 -4
- data/lib/fusion/cli.rb +119 -48
- data/lib/fusion/interpreter/builtins.rb +260 -151
- 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 +239 -82
- data/lib/fusion/lexer.rb +69 -3
- data/lib/fusion/parser.rb +189 -51
- data/lib/fusion/version.rb +1 -1
- data/stdlib/all.fsn +13 -0
- data/stdlib/any.fsn +12 -0
- data/stdlib/chars.fsn +5 -0
- data/stdlib/compact.fsn +6 -0
- data/stdlib/concat.fsn +5 -0
- data/stdlib/falsey.fsn +6 -0
- data/stdlib/filter.fsn +12 -0
- data/stdlib/flatten.fsn +7 -0
- data/stdlib/gt.fsn +9 -0
- data/stdlib/gte.fsn +9 -0
- data/stdlib/lt.fsn +9 -0
- data/stdlib/lte.fsn +9 -0
- data/stdlib/map.fsn +6 -4
- data/stdlib/range.fsn +2 -2
- data/stdlib/reduce.fsn +8 -0
- data/stdlib/sanitize.fsn +2 -2
- data/stdlib/truthy.fsn +7 -0
- metadata +18 -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: `??`. 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,57 @@ 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 == 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
|
+
|
|
157
222
|
---
|
|
158
223
|
|
|
159
224
|
## 3. Functions and application
|
|
@@ -231,7 +296,7 @@ Rules:
|
|
|
231
296
|
`p_core ? predicate` matches when `p_core` matches structurally **and** piping the
|
|
232
297
|
matched value through `predicate` yields a **truthy** result. Truthiness is
|
|
233
298
|
Ruby-style: every value is truthy except `false` and `null` (so `0` and `""` are
|
|
234
|
-
truthy). The
|
|
299
|
+
truthy). The operators `@OP.and` / `@OP.or` / `@OP.not` apply the same test.
|
|
235
300
|
|
|
236
301
|
The predicate is a `|` chain of functions, and the matched value flows in from the
|
|
237
302
|
left: `a ? b | c` matches when `a` matches and `a | b | c` is truthy. A single-stage
|
|
@@ -313,7 +378,7 @@ particular built-ins:
|
|
|
313
378
|
is per-call: it is not enough for the function to have *some* error clause;
|
|
314
379
|
that clause must match the specific error received. An error of a shape no
|
|
315
380
|
clause catches propagates unchanged.
|
|
316
|
-
- **Built-in operations (`@
|
|
381
|
+
- **Built-in and stdlib operations (`@math.divide`, `@OP.sum`, `@Integer`, …) all
|
|
317
382
|
propagate** their input error without examining it. To inspect or compare an
|
|
318
383
|
error's payload, you must catch it first and operate on the extracted payload:
|
|
319
384
|
`!42 | (!a => a) | @Integer` returns `true` (the payload `42` *is* an integer);
|
|
@@ -323,7 +388,7 @@ particular built-ins:
|
|
|
323
388
|
evaluating an element/member. `[1, !"bad", 2]` evaluates to `!"bad"`, not to
|
|
324
389
|
an array of three things.
|
|
325
390
|
- **Constructing an error from an erroring expression** propagates the inner
|
|
326
|
-
error rather than wrapping it. `!([5,0] | @divide)` evaluates to the division
|
|
391
|
+
error rather than wrapping it. `!([5,0] | @math.divide)` evaluates to the division
|
|
327
392
|
error itself (a `math_error`, §6.5), never to an error wrapping an error. (This
|
|
328
393
|
preserves the rule that there is never more than one error simultaneously.)
|
|
329
394
|
- **When the function value itself is an error** (e.g. `value | @undefined_name`
|
|
@@ -334,9 +399,10 @@ particular built-ins:
|
|
|
334
399
|
If a `?` predicate evaluates to an error (the predicate function itself errored,
|
|
335
400
|
or it was a non-function error value), that error becomes the function's return
|
|
336
401
|
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
|
-
|
|
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.
|
|
340
406
|
|
|
341
407
|
### 6.5 The standardized error payload
|
|
342
408
|
|
|
@@ -354,46 +420,48 @@ There are two origins of error values, and they differ in payload:
|
|
|
354
420
|
#### Payload shape
|
|
355
421
|
|
|
356
422
|
```json
|
|
357
|
-
{"kind": "
|
|
423
|
+
{"kind": "argument_error", "origin": "builtin", "operation": "@math.divide", "status": 0, "input": [1, "x"], "expected": ["[_ ? @Number, _ ? @Number]"]}
|
|
358
424
|
```
|
|
359
425
|
|
|
360
426
|
| Field | Required | Meaning |
|
|
361
427
|
| ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
362
|
-
| `kind` | yes | The error category
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
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. |
|
|
367
436
|
|
|
368
|
-
#### `kind`
|
|
437
|
+
#### Possible values for `kind`
|
|
369
438
|
|
|
370
439
|
| `kind` | Raised when |
|
|
371
440
|
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
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 [_, _]`). |
|
|
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. |
|
|
376
444
|
| `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
|
|
445
|
+
| `access_error` | a missing object key or an out-of-range array index. |
|
|
378
446
|
| `math_error` | division or modulo by zero, or a non-finite number. |
|
|
379
447
|
| `conversion_error` | a value cannot be converted (`@toString` of an unconvertible type, `@parseNumber` of a non-numeric string). |
|
|
380
|
-
| `
|
|
381
|
-
| `
|
|
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. |
|
|
382
451
|
|
|
383
|
-
####
|
|
452
|
+
#### Possible values for `origin`
|
|
384
453
|
|
|
385
|
-
| `
|
|
386
|
-
|
|
|
387
|
-
| `builtin
|
|
388
|
-
| `stdlib
|
|
389
|
-
| `code
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `interpreter` | the interpreter itself, e.g. a stack overflow. |
|
|
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. |
|
|
394
462
|
|
|
395
463
|
`input` and `output` name the data channels; they **never** refer to the program
|
|
396
|
-
source, which always reports as `code
|
|
464
|
+
source, which always reports as `code`.
|
|
397
465
|
|
|
398
466
|
User errors don't have to adhere to this standard.
|
|
399
467
|
|
|
@@ -402,60 +470,75 @@ User errors don't have to adhere to this standard.
|
|
|
402
470
|
## 7. Built-in functions
|
|
403
471
|
|
|
404
472
|
All built-ins are ordinary one-argument functions, **reached with an `@` prefix**
|
|
405
|
-
(`@
|
|
473
|
+
(`@size`, `@Integer`, …); see §9.2 for how `@name` resolves. The names in the tables
|
|
406
474
|
below are the built-in names; write `@` before them to use them. **Operations** return
|
|
407
475
|
`!` on type-invalid or domain-invalid input. **Predicates** return `false` on any
|
|
408
476
|
input that is not of the queried type (they never return `!`).
|
|
409
477
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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.
|
|
442
521
|
|
|
443
522
|
### 7.4 Strings and structure bridges (operations)
|
|
444
523
|
|
|
445
524
|
| Name | Input | Result |
|
|
446
525
|
| ------------- | --------------------------- | --------------------------------------- |
|
|
447
|
-
| `
|
|
448
|
-
| `
|
|
449
|
-
| `
|
|
450
|
-
| `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 |
|
|
451
529
|
| `toString` | any | string form of the value |
|
|
452
530
|
| `parseNumber` | string | integer or float; `!` if not numeric |
|
|
453
531
|
| `keys` | object | array of key strings |
|
|
454
532
|
| `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
533
|
| `toObject` | `[[string-key, value], …]` | object built from entries; later duplicate keys win |
|
|
458
534
|
|
|
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.
|
|
538
|
+
|
|
539
|
+
Indexed read (`x[k]`) and write (`x[k = v]`) are **core syntax**, not built-ins — there
|
|
540
|
+
is no `@get`/`@set`; see §8.
|
|
541
|
+
|
|
459
542
|
### 7.5 Type predicates (predicates)
|
|
460
543
|
|
|
461
544
|
Each of these functions takes any input value and returns a boolean. This set of
|
|
@@ -478,7 +561,59 @@ Notes:
|
|
|
478
561
|
- Booleans are separate from numbers. There's no automatic type conversion (`false` <-> `0`, `true` <-> `1`).
|
|
479
562
|
- The set of values without JSON representation (§9.3) is exactly `Function` + `NonFinite`
|
|
480
563
|
|
|
481
|
-
### 7.6
|
|
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`
|
|
482
617
|
|
|
483
618
|
These resolve in the `@name` chain like other built-ins (so a sibling file of the
|
|
484
619
|
same name shadows them), but they are not plain unary value functions:
|
|
@@ -495,12 +630,18 @@ same name shadows them), but they are not plain unary value functions:
|
|
|
495
630
|
|
|
496
631
|
## 8. Member and index access
|
|
497
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
|
+
|
|
498
636
|
- `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
|
-
|
|
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.
|
|
502
643
|
|
|
503
|
-
|
|
644
|
+
`.`, `[]`, and `[=]` are postfix and bind tighter than every operator (including `|`).
|
|
504
645
|
|
|
505
646
|
---
|
|
506
647
|
|
|
@@ -515,7 +656,19 @@ A `.fsn` file contains **exactly one expression**, which is its value. A file is
|
|
|
515
656
|
|
|
516
657
|
A `@` reference takes one of these forms:
|
|
517
658
|
|
|
518
|
-
- **`@`** (nothing after it) — the **current
|
|
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.
|
|
519
672
|
- **`@ENV`** — an object of all environment variables (string keys, string values;
|
|
520
673
|
no parsing). Resolved in the `@name` chain below, so it is shadowable.
|
|
521
674
|
- **`@name`** — a single bare identifier (no `/`, no `../`).
|
|
@@ -529,9 +682,9 @@ in order, first match winning:
|
|
|
529
682
|
2. a **built-in** of that exact name (including `ENV` and `load`);
|
|
530
683
|
3. a **standard-library file** at `<stdlib root>/<name>.fsn`.
|
|
531
684
|
|
|
532
|
-
If none match, the result is `!`. Downward paths participate fully: `@
|
|
533
|
-
checks a sibling `
|
|
534
|
-
`
|
|
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`.
|
|
535
688
|
|
|
536
689
|
**Resolution of upward paths** (any reference containing `../`) is **file-only**: it
|
|
537
690
|
resolves solely to a file relative to the referencing directory and never falls back
|
|
@@ -542,7 +695,20 @@ is relative to the **referencing file's** directory; built-ins and the standard
|
|
|
542
695
|
library are global to the runtime but, per the order above, are shadowed by a sibling
|
|
543
696
|
file of the same name. That shadowing is per-directory, not global.
|
|
544
697
|
|
|
545
|
-
**
|
|
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
|
|
546
712
|
`@name` references that resolve at step 2. A *bare* identifier (without `@`) is only
|
|
547
713
|
a pattern hole; it never denotes a built-in.
|
|
548
714
|
|
|
@@ -572,12 +738,19 @@ above. Recursion through functions is not a data cycle.
|
|
|
572
738
|
|
|
573
739
|
### 9.3 Runtime contract
|
|
574
740
|
|
|
575
|
-
The
|
|
576
|
-
|
|
577
|
-
standard output as JSON.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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`,
|
|
743
|
+
computes `v | programFunction`, and prints the result on standard output as 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).
|
|
581
754
|
- **If the final result is an error**, the interpreter prints **nothing** to
|
|
582
755
|
standard output, prints the error's **payload** (as JSON) to standard error, and
|
|
583
756
|
exits with status `1`. Otherwise the result is printed to standard output and the
|
|
@@ -613,20 +786,25 @@ They are independent of each other and selected with the `--input` and `--output
|
|
|
613
786
|
flags:
|
|
614
787
|
|
|
615
788
|
- **`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
|
-
|
|
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).
|
|
619
794
|
- **`bang`** — a leading `!` marks an error value; the payload is the JSON after
|
|
620
795
|
the `!`. A lone `!` is `!null`, like the language's bare `!`. Output is always
|
|
621
796
|
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
|
|
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`.
|
|
623
800
|
- **`array`** — everything is wrapped in an envelope: `[0, value]` for a value,
|
|
624
|
-
`[1, payload]` for an error.
|
|
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`.
|
|
625
803
|
- **`object`** — the envelope is `{"value": value}` for a value, `{"error": payload}`
|
|
626
804
|
for an error. Output is always on stdout, exit code always `0`.
|
|
627
805
|
|
|
628
806
|
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 `
|
|
807
|
+
be exactly the integer `0` or `1`) is an `argument_error` at `origin: "input"`.
|
|
630
808
|
Like any input failure it flows into the program as an error and is catchable.
|
|
631
809
|
|
|
632
810
|
Mode support per use case (defaults in bold):
|
|
@@ -634,29 +812,45 @@ Mode support per use case (defaults in bold):
|
|
|
634
812
|
| Use case | `unix` | `bang` | `array` | `object` |
|
|
635
813
|
| ---------- | -------- | -------- | ------- | -------- |
|
|
636
814
|
| pipe | **yes** | yes | yes | yes |
|
|
637
|
-
| `--stream` | no | **yes**
|
|
815
|
+
| `--stream` | no | yes | **yes** | yes |
|
|
638
816
|
| `--repl` | — | — | — | — |
|
|
639
817
|
|
|
640
818
|
The unix mode spends the process's only exit code and both standard streams on a
|
|
641
819
|
single result, so it cannot mark errors per record in a stream; the stream use
|
|
642
|
-
case therefore excludes it.
|
|
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.
|
|
643
824
|
|
|
644
825
|
### 9.5 Streaming (`--stream`)
|
|
645
826
|
|
|
646
827
|
`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
|
|
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.
|
|
649
843
|
|
|
650
|
-
- Blank lines are skipped; every other input line produces exactly one output line.
|
|
651
844
|
- Errors stay in-band, so a failing record — including a stack overflow — becomes
|
|
652
845
|
that record's output line and the stream continues. The exit code is always `0`.
|
|
653
|
-
- A program that fails to load
|
|
846
|
+
- A program that fails to load will return the same load error for every record.
|
|
654
847
|
|
|
655
848
|
### 9.6 The REPL (`--repl`)
|
|
656
849
|
|
|
657
|
-
`fusion --repl` starts an interactive session
|
|
658
|
-
|
|
659
|
-
|
|
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:
|
|
660
854
|
|
|
661
855
|
- an **expression** — evaluated and printed; or
|
|
662
856
|
- a **statement** — an assignment that also binds a name:
|
|
@@ -677,43 +871,63 @@ relative to the working directory.
|
|
|
677
871
|
statement is an assignment, not a pattern match.)
|
|
678
872
|
- Rebinding a name is allowed; later entries see the new value.
|
|
679
873
|
- A bound function can call itself through its own name
|
|
680
|
-
(`fact = (0 => 1, n => [n, [n
|
|
874
|
+
(`fact = (0 => 1, n => [n, [n,-1] | @OP.sum | fact] | @OP.product)`), because
|
|
681
875
|
the name is looked up at application time.
|
|
682
|
-
- Entries report errors at `
|
|
876
|
+
- Entries report errors at `origin: "code"` with `file: "<inline>"`, like `-e` programs.
|
|
683
877
|
|
|
684
878
|
**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
|
-
|
|
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.
|
|
692
888
|
|
|
693
889
|
### 9.7 Command-line interface
|
|
694
890
|
|
|
695
891
|
```
|
|
696
|
-
usage: fusion [options] <file.fsn>
|
|
697
|
-
fusion [options] -e '<source>'
|
|
892
|
+
usage: fusion [options] <file.fsn>
|
|
893
|
+
fusion [options] -e '<source>'
|
|
698
894
|
fusion --repl
|
|
699
895
|
|
|
700
|
-
use cases:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
--
|
|
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`
|
|
704
901
|
|
|
705
902
|
options:
|
|
706
|
-
-e '<source>'
|
|
707
|
-
|
|
708
|
-
--
|
|
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)
|
|
709
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)
|
|
710
914
|
```
|
|
711
915
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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.
|
|
715
929
|
|
|
716
|
-
A command-line misuse (an unknown flag,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
error.
|
|
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.
|