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