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