fusion-lang 0.0.1.alpha1
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +79 -0
- data/Rakefile +8 -0
- data/docs/index.md +34 -0
- data/docs/lang/design.md +674 -0
- data/docs/lang/roadmap.md +97 -0
- data/docs/user/explanation.md +157 -0
- data/docs/user/how-to-guides.md +205 -0
- data/docs/user/reference.md +505 -0
- data/docs/user/tutorial.md +338 -0
- data/examples/double.fsn +4 -0
- data/examples/factorial.fsn +6 -0
- data/examples/first.fsn +4 -0
- data/examples/fizzbuzz.fsn +15 -0
- data/examples/palindrome.fsn +8 -0
- data/exe/fusion +57 -0
- data/lib/fusion/version.rb +3 -0
- data/lib/fusion.rb +1140 -0
- data/stdlib/map.fsn +4 -0
- data/stdlib/math/square.fsn +1 -0
- data/stdlib/range.fsn +4 -0
- metadata +67 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# Reference
|
|
2
|
+
|
|
3
|
+
## 1. Values
|
|
4
|
+
|
|
5
|
+
A Fusion value is one of:
|
|
6
|
+
|
|
7
|
+
| Kind | Examples | Notes |
|
|
8
|
+
| -------- | --------------------------------- | --------------------------------------------- |
|
|
9
|
+
| null | `null` | Ordinary data: a legitimate "absent" value. |
|
|
10
|
+
| boolean | `true`, `false` | |
|
|
11
|
+
| integer | `0`, `-7`, `42` | Distinct from float. |
|
|
12
|
+
| float | `3.14`, `1e9`, `-0.5` | Distinct from integer. |
|
|
13
|
+
| string | `"hi"`, `"a\nb"` | JSON string syntax and escapes. |
|
|
14
|
+
| array | `[]`, `[1, 2, 3]`, `[1, [2]]` | Ordered, heterogeneous. |
|
|
15
|
+
| object | `{}`, `{"k": 1}` | String keys; insertion order preserved. |
|
|
16
|
+
| function | `(p => o, ...)` | One input, one output. A first-class value. |
|
|
17
|
+
| error | `!42`, `!"oops"`, `!null` | An error with a payload. Not a regular value. |
|
|
18
|
+
|
|
19
|
+
The only atomic values are `null`, booleans, integers, floats and strings.
|
|
20
|
+
|
|
21
|
+
The only composite data structures are arrays and objects.
|
|
22
|
+
|
|
23
|
+
Functions are first-class values. They behave like all other values with 3 small exceptions:
|
|
24
|
+
- They can't cross the CLI boundary. All values on STDIN, STDOUT and STDERR may only
|
|
25
|
+
contain regular JSON values.
|
|
26
|
+
- In contrast to arrays and objects, there's no syntax for pattern matching on functions.
|
|
27
|
+
- Functions can't be an error payload.
|
|
28
|
+
|
|
29
|
+
Errors are not regular values:
|
|
30
|
+
- They contain a regular value as "payload", but aren't regular values themselves.
|
|
31
|
+
- They can't be stored in arrays or objects. They always "bubble" and turn that whole
|
|
32
|
+
data structure into an error.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Syntax
|
|
37
|
+
|
|
38
|
+
### 2.1 Expressions
|
|
39
|
+
|
|
40
|
+
Precedence, tightest to loosest:
|
|
41
|
+
|
|
42
|
+
1. Primary: literals, `[...]`, `{...}`, `(...)` grouping, function literals,
|
|
43
|
+
identifiers, `@`-references.
|
|
44
|
+
2. Postfix member/index access: `x.key`, `x[expr]`.
|
|
45
|
+
3. Error prefix: `!expr` (construct an error). Bare `!` (with no operand) is the
|
|
46
|
+
same as `!null`.
|
|
47
|
+
4. Pipe (application): `value | function`. Left-associative
|
|
48
|
+
(`a | f | g` ≡ `(a | f) | g`).
|
|
49
|
+
5. Clause arrow: `=>` (loosest, so the entire right-hand side of a clause is one
|
|
50
|
+
expression).
|
|
51
|
+
|
|
52
|
+
### 2.2 Function literals
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
( pattern => expr , pattern => expr , ... )
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
One or more clauses, comma-separated, parenthesized; an optional trailing comma is
|
|
59
|
+
allowed. A single parenthesized expression with no `=>` at the top level is a grouped
|
|
60
|
+
expression, not a function.
|
|
61
|
+
|
|
62
|
+
### 2.3 Array and object literals
|
|
63
|
+
|
|
64
|
+
Array elements and object members may be **spread** with `...`:
|
|
65
|
+
|
|
66
|
+
```fusion
|
|
67
|
+
# Splice an array's elements in place
|
|
68
|
+
[1, ...other, 9]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```fusion
|
|
72
|
+
# Merge another object's keys in place
|
|
73
|
+
{"a": 1, ...other}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
In a result, `...expr` requires `expr` to be an array (in an array literal) or an
|
|
77
|
+
object (in an object literal); otherwise the literal evaluates to `!`.
|
|
78
|
+
|
|
79
|
+
### 2.4 Comments
|
|
80
|
+
|
|
81
|
+
Fusion only has **whole-line comments**. A line is a comment iff its first non-whitespace
|
|
82
|
+
character is `#`. Comments can be stripped without parsing the language, because string
|
|
83
|
+
literals cannot span physical lines:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Canonical specification of comment stripping
|
|
87
|
+
grep -v '^[[:space:]]*#' program.fsn
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A shebang line needs no special treatment — `#!/usr/bin/env fusion` is just a comment
|
|
91
|
+
by the same rule.
|
|
92
|
+
|
|
93
|
+
### 2.5 Grammar (EBNF)
|
|
94
|
+
|
|
95
|
+
```ebnf
|
|
96
|
+
file = expr ;
|
|
97
|
+
|
|
98
|
+
expr = pipe ;
|
|
99
|
+
pipe = prefix { "|" prefix } ;
|
|
100
|
+
prefix = "!" [ prefix ] | postfix ; (* bare "!" -> !null *)
|
|
101
|
+
postfix = primary { "." identifier | "[" expr "]" } ;
|
|
102
|
+
primary = atom | array | object | function | identifier | fileref | "(" expr ")" ;
|
|
103
|
+
|
|
104
|
+
fileref = "@" [ refpath ] ; (* bare "@" = current file *)
|
|
105
|
+
refpath = { "../" } segment { "/" segment } ; (* ".fsn" implied *)
|
|
106
|
+
segment = identifier ;
|
|
107
|
+
|
|
108
|
+
atom = "null" | "true" | "false" | number | string ;
|
|
109
|
+
(* errors are `!`+payload (see prefix) *)
|
|
110
|
+
array = "[" [ elem { "," elem } [ "," ] ] "]" ;
|
|
111
|
+
elem = expr | spread ;
|
|
112
|
+
spread = "..." expr ;
|
|
113
|
+
object = "{" [ member { "," member } [ "," ] ] "}" ;
|
|
114
|
+
member = string ":" expr | spread ;
|
|
115
|
+
|
|
116
|
+
function = "(" clause { "," clause } [ "," ] ")" ;
|
|
117
|
+
clause = pattern "=>" expr ;
|
|
118
|
+
|
|
119
|
+
pattern = errpat | guardedpat ;
|
|
120
|
+
errpat = "!" | "!" guardedpat ; (* bare "!" matches any error, binds nothing *)
|
|
121
|
+
guardedpat = corepat [ "?" predicate ] ;
|
|
122
|
+
predicate = prefix ; (* any expression yielding a function *)
|
|
123
|
+
corepat = literalpat | bindpat | wildcard | arraypat | objectpat ;
|
|
124
|
+
literalpat = atom ;
|
|
125
|
+
wildcard = "_" ;
|
|
126
|
+
bindpat = identifier ;
|
|
127
|
+
arraypat = "[" [ pelem { "," pelem } [ "," ] ] "]" ;
|
|
128
|
+
pelem = guardedpat | "..." [ identifier ] ;
|
|
129
|
+
objectpat = "{" [ pmember { "," pmember } [ "," ] ] "}" ;
|
|
130
|
+
pmember = string ":" guardedpat | "..." [ identifier ] ;
|
|
131
|
+
|
|
132
|
+
identifier = letter { letter | digit | "_" } ;
|
|
133
|
+
number = int_lit | float_lit ;
|
|
134
|
+
int_lit = [ "-" ] digit { digit } ;
|
|
135
|
+
float_lit = int_lit "." digit { digit } [ exp ] | int_lit exp ;
|
|
136
|
+
exp = ("e" | "E") [ "+" | "-" ] digit { digit } ;
|
|
137
|
+
string = '"' { char | escape } '"' ; (* char excludes raw newline; use \n *)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 3. Functions and application
|
|
143
|
+
|
|
144
|
+
A function is an ordered list of clauses. Applying a function to a value `v`:
|
|
145
|
+
|
|
146
|
+
1. If the function value itself is an error (e.g. piping into an unresolved name),
|
|
147
|
+
that error is the result.
|
|
148
|
+
2. Otherwise clauses are tried in order. The first clause whose pattern matches
|
|
149
|
+
`v` produces the result, evaluated with that clause's bindings in scope.
|
|
150
|
+
3. If a `?` predicate evaluates to an error during matching, that error becomes
|
|
151
|
+
the function's result; no further clauses are tried (see §6.4).
|
|
152
|
+
4. **If no clause matches:** the result is `v` itself when `v` is an error
|
|
153
|
+
(propagation — an unmatched error is never silently swallowed), and `null`
|
|
154
|
+
otherwise (the lenient default).
|
|
155
|
+
|
|
156
|
+
A consequence of rule 4: a function with error clauses that only match *some*
|
|
157
|
+
shapes of error (e.g. `(!42 => "got 42", _ => "ok")`) still propagates any
|
|
158
|
+
other-shaped error it receives — `!"oops"` is not caught by `!42`, the `_`
|
|
159
|
+
clause rejects errors, no clause matches, so `!"oops"` propagates. To handle
|
|
160
|
+
"any unrecognized error" you must add a catch-all error clause: `!` (or
|
|
161
|
+
equivalently `!_`) for "catch any error, ignore the payload"; `!msg` to bind
|
|
162
|
+
the payload.
|
|
163
|
+
|
|
164
|
+
Functions take exactly one argument. Multiple arguments are passed as an array or
|
|
165
|
+
object. Currying is done by returning a function: `(x => (y => [x, y] | @add))`.
|
|
166
|
+
|
|
167
|
+
Functions are values: they may be elements of arrays, values of object keys, results
|
|
168
|
+
of clauses, and arguments to other functions.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 4. Patterns and binding
|
|
173
|
+
|
|
174
|
+
A pattern both tests structure and extracts parts. Pattern forms:
|
|
175
|
+
|
|
176
|
+
| Form | Matches | Binds |
|
|
177
|
+
| ------------------------------------------ | ----------------------------------------- | ---------------------------------- |
|
|
178
|
+
| literal (`42`, `"x"`, `true`, `null`) | exactly that value | nothing |
|
|
179
|
+
| `_` (wildcard) | anything **except** an error | nothing |
|
|
180
|
+
| identifier (`a`) | anything **except** an error | the value, under that name |
|
|
181
|
+
| Fixed size arrays, e.g. `[x_1, x_2]` | array of matching length, elementwise | each `x_i` binds |
|
|
182
|
+
| Variably arrays, e.g. `[p, ...rest]` | array with ≥ fixed elements | `rest` = remaining elements |
|
|
183
|
+
| Fixed member objects, e.g. `{"a": p}` | object having key `a` (and others) | as `p` binds |
|
|
184
|
+
| Variable objects, e.g. `{"a": p, ...rest}` | object | `rest` = unmatched key/value pairs |
|
|
185
|
+
| `corepat ? pred` | `corepat` matches **and** pred is `true` | as `corepat` binds |
|
|
186
|
+
| `!`, `!_`, `!pat` | an error; `!pat` destructures the payload | as `pat` binds |
|
|
187
|
+
|
|
188
|
+
Rules:
|
|
189
|
+
|
|
190
|
+
**Bare identifiers are holes**:
|
|
191
|
+
- In a pattern they bind. In an expression they read the value bound to that name in
|
|
192
|
+
the current clause.
|
|
193
|
+
- Reading an unbound bare identifier yields an error.
|
|
194
|
+
- A bare identifier never denotes a builtin. Builtins are reached with `@` (see §7,
|
|
195
|
+
§9.2).
|
|
196
|
+
|
|
197
|
+
**No sibling scope**:
|
|
198
|
+
- All bindings in a clause are produced simultaneously. A `?` predicate sees **only**
|
|
199
|
+
the value matched by the pattern it is attached to and never another part of the
|
|
200
|
+
same clause.
|
|
201
|
+
- For `!pat ? pred`, the `?` binds *inside* the `!` (the grammar parses it as
|
|
202
|
+
`!(pat ? pred)`), so the predicate runs against the error's payload and sees exactly
|
|
203
|
+
what `pat` binds. `(!a ? @Integer => ...)` checks whether the payload `a` is an integer.
|
|
204
|
+
- If a `?` predicate evaluates to an error, that error bubbles up as the function's
|
|
205
|
+
result (see §6.4).
|
|
206
|
+
|
|
207
|
+
**`...rest` in patterns**:
|
|
208
|
+
- May appear at most once.
|
|
209
|
+
- In an array pattern it may appear in any position and captures the start / middle / end
|
|
210
|
+
of the array.
|
|
211
|
+
- In an object pattern it may appear at the end and captures all keys not explicitly matched.
|
|
212
|
+
- A bare `...` with no name matches without binding.
|
|
213
|
+
- An object pattern matches if all its named keys are present; extra keys are allowed
|
|
214
|
+
(and captured by `...rest` if present).
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 5. The `?` predicate (refinement / types)
|
|
219
|
+
|
|
220
|
+
`corepat ? predicate` matches when `corepat` matches structurally **and** piping the
|
|
221
|
+
matched value into `predicate` yields exactly `true`. The predicate is any
|
|
222
|
+
expression evaluating to a function (a name, a member access, or an inline function
|
|
223
|
+
literal).
|
|
224
|
+
|
|
225
|
+
"Types" are not a separate construct: the built-in predicates `@Integer`, `@String`,
|
|
226
|
+
etc. are ordinary functions returning booleans, reached with `@` and used with `?`
|
|
227
|
+
(e.g. `n ? @Integer`). User-defined predicates work identically (e.g. `n ? @isEven`).
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 6. Errors
|
|
232
|
+
|
|
233
|
+
An **error value** is `!` followed by a payload: `!42`, `!"divide by zero"`,
|
|
234
|
+
`!{"kind":"missing_key","key":"id"}`, `!null`. The payload may be any regular Fusion
|
|
235
|
+
value (including `null`). Not allowed are functions and nested errors.
|
|
236
|
+
|
|
237
|
+
Error values are distinct from ordinary data:
|
|
238
|
+
- `null` means legitimate absence.
|
|
239
|
+
- An error means something went wrong.
|
|
240
|
+
|
|
241
|
+
### 6.1 Constructing errors
|
|
242
|
+
|
|
243
|
+
In expression position, `!` is a prefix operator that constructs an error from its
|
|
244
|
+
operand:
|
|
245
|
+
|
|
246
|
+
- `!42` is an error with payload `42`.
|
|
247
|
+
- `!"oops"` is an error with payload the string `"oops"`.
|
|
248
|
+
- `!` alone (with nothing following it) is shorthand for `!null`.
|
|
249
|
+
- `!expr` where `expr` itself evaluates to an error **propagates** that inner error
|
|
250
|
+
rather than wrapping it (so you cannot accidentally bury an error inside another
|
|
251
|
+
error's payload).
|
|
252
|
+
|
|
253
|
+
Precedence: `!` binds tighter than `|` so `!x | f` is `(!x) | f`; looser than
|
|
254
|
+
postfix `.`/`[]` so `!x.foo` is `!(x.foo)`.
|
|
255
|
+
|
|
256
|
+
### 6.2 Matching errors
|
|
257
|
+
|
|
258
|
+
In pattern position, `!` introduces an **error pattern**:
|
|
259
|
+
|
|
260
|
+
| Pattern | Matches |
|
|
261
|
+
| ------------------- | ------------------------------------------------------------------------ |
|
|
262
|
+
| `!` | any error; payload is not bound. Cannot carry a `?` predicate. |
|
|
263
|
+
| `!_` | any error; payload is not bound. Can carry a predicate (`!_ ? @pred`). |
|
|
264
|
+
| `!pattern` | any error with a **payload** that matches `pattern` |
|
|
265
|
+
|
|
266
|
+
The payload pattern (the `pattern` in `!pattern`) is a full `guardedpat`:
|
|
267
|
+
- It uses the same destructuring semantics you know from regular values.
|
|
268
|
+
- It fully supports `?` predicates. Predicates only refer to the payload, not the `!`.
|
|
269
|
+
Caution: `! ? @Integer` is a syntax error. The bare `!` has no payload pattern
|
|
270
|
+
for the predicate to refer to. Use `!_ ? @Integer` instead.
|
|
271
|
+
- It cannot itself contain another `!`. At runtime there is no error nested inside another error
|
|
272
|
+
or value. Errors propagate before they can sit inside a collection, so this syntax is rejected
|
|
273
|
+
at parse time.
|
|
274
|
+
|
|
275
|
+
### 6.3 Propagation
|
|
276
|
+
|
|
277
|
+
**Errors are not first-class values.** At any moment of execution there is either
|
|
278
|
+
a value in motion or an error in motion, never both. An error reaches user code
|
|
279
|
+
only through a clause whose pattern catches it; outside of that catch site, an
|
|
280
|
+
error encountered where a value is expected always propagates.
|
|
281
|
+
|
|
282
|
+
The propagation rule is uniform — there is no special handling for predicates or
|
|
283
|
+
particular built-ins:
|
|
284
|
+
|
|
285
|
+
- **Applying any function to an error** returns that same error (payload
|
|
286
|
+
preserved), **unless** a clause's pattern actually matches it. The matching
|
|
287
|
+
is per-call: it is not enough for the function to have *some* error clause;
|
|
288
|
+
that clause must match the specific error received. An error of a shape no
|
|
289
|
+
clause catches propagates unchanged.
|
|
290
|
+
- **Built-in operations (`@add`, `@divide`, `@equals`, `@Integer`, …) all
|
|
291
|
+
propagate** their input error without examining it. To inspect or compare an
|
|
292
|
+
error's payload, you must catch it first and operate on the extracted payload:
|
|
293
|
+
`!42 | (!a => a) | @Integer` returns `true` (the payload `42` *is* an integer);
|
|
294
|
+
`!42 | @Integer` returns `!42` (the predicate doesn't handle the error,
|
|
295
|
+
evaluates to `!42` and that becomes the return value of the whole function).
|
|
296
|
+
- **Building an array or object propagates** any error encountered while
|
|
297
|
+
evaluating an element/member. `[1, !"bad", 2]` evaluates to `!"bad"`, not to
|
|
298
|
+
an array of three things.
|
|
299
|
+
- **Constructing an error from an erroring expression** propagates the inner
|
|
300
|
+
error rather than wrapping it. `!([5,0] | @divide)` evaluates to
|
|
301
|
+
`!"divide: division by zero"`, never to an error wrapping an error. (This
|
|
302
|
+
preserves the rule that there is never more than one error simultaneously.)
|
|
303
|
+
- **When the function value itself is an error** (e.g. `value | @undefined_name`
|
|
304
|
+
where `@undefined_name` resolves to an error), that error is the result.
|
|
305
|
+
|
|
306
|
+
### 6.4 `?`-predicate errors bubble up
|
|
307
|
+
|
|
308
|
+
If a `?` predicate evaluates to an error (the predicate function itself errored,
|
|
309
|
+
or it was a non-function error value), that error becomes the function's return
|
|
310
|
+
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 make
|
|
312
|
+
your predicates *total* (end with `_ => false`): a predicate that can crash will
|
|
313
|
+
short-circuit your whole function.
|
|
314
|
+
|
|
315
|
+
### 6.5 Sources of errors
|
|
316
|
+
|
|
317
|
+
Built-in errors are produced by:
|
|
318
|
+
|
|
319
|
+
- A strict function (`_ => !`) on no match — payload `null`.
|
|
320
|
+
- Built-in operations on bad input — payload is a descriptive string,
|
|
321
|
+
e.g. `"divide: division by zero"`, `"add: expected a pair of numbers"`.
|
|
322
|
+
- Member access on a missing key or non-object, index out of range, or bad index
|
|
323
|
+
type — payload is a structured object like
|
|
324
|
+
`{"kind":"missing_key","key":"foo"}` or `{"kind":"index_out_of_range",...}`.
|
|
325
|
+
- Spread of a non-array or non-object — `{"kind":"spread_non_array",...}`.
|
|
326
|
+
- Unresolved `@`-reference — `{"kind":"unresolved_ref","name":"..."}`.
|
|
327
|
+
- Non-productive data cycle — `{"kind":"data_cycle","path":"..."}`.
|
|
328
|
+
- Applying a non-function — `{"kind":"apply_non_function","got":"..."}`.
|
|
329
|
+
|
|
330
|
+
User code constructs errors with `!payload` as described above.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 7. Built-in functions
|
|
335
|
+
|
|
336
|
+
All built-ins are ordinary one-argument functions, **reached with an `@` prefix**
|
|
337
|
+
(`@add`, `@Integer`, …); see §9.2 for how `@name` resolves. The names in the tables
|
|
338
|
+
below are the built-in names; write `@` before them to use them. **Operations** return
|
|
339
|
+
`!` on type-invalid or domain-invalid input. **Predicates** return `false` on any
|
|
340
|
+
input that is not of the queried type (they never return `!`).
|
|
341
|
+
|
|
342
|
+
### 7.1 Arithmetic (operations)
|
|
343
|
+
|
|
344
|
+
| Name | Input | Result |
|
|
345
|
+
| ---------- | ----------------- | -------------------------------------------------- |
|
|
346
|
+
| `add` | `[number, number]`| sum |
|
|
347
|
+
| `subtract` | `[number, number]`| difference |
|
|
348
|
+
| `multiply` | `[number, number]`| product |
|
|
349
|
+
| `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) |
|
|
353
|
+
|
|
354
|
+
### 7.2 Comparison (operations)
|
|
355
|
+
|
|
356
|
+
| Name | Input | Result |
|
|
357
|
+
| ----------- | --------------------------------------- | --------------- |
|
|
358
|
+
| `equals` | `[any, any]` | deep structural equality (boolean) |
|
|
359
|
+
| `lessThan` | `[number, number]` or `[string, string]`| boolean; `!` on mismatched/invalid types |
|
|
360
|
+
|
|
361
|
+
Other comparisons (`lessEq`, `greaterThan`, `greaterEq`, `notEquals`) are specified
|
|
362
|
+
for the standard library, derivable from `equals` and `lessThan`.
|
|
363
|
+
|
|
364
|
+
### 7.3 Boolean (operations)
|
|
365
|
+
|
|
366
|
+
| Name | Input | Result |
|
|
367
|
+
| ----- | -------------------- | ------------- |
|
|
368
|
+
| `and` | `[boolean, boolean]` | logical and |
|
|
369
|
+
| `or` | `[boolean, boolean]` | logical or |
|
|
370
|
+
| `not` | `boolean` | logical not |
|
|
371
|
+
|
|
372
|
+
### 7.4 Strings and structure bridges (operations)
|
|
373
|
+
|
|
374
|
+
| Name | Input | Result |
|
|
375
|
+
| ------------- | --------------------------- | --------------------------------------- |
|
|
376
|
+
| `length` | string / array / object | element/character/key count (integer) |
|
|
377
|
+
| `concat` | `[string, string]` | concatenation |
|
|
378
|
+
| `chars` | string | array of single-character strings |
|
|
379
|
+
| `join` | `[array-of-strings, string]`| joined string |
|
|
380
|
+
| `toString` | any | string form of the value |
|
|
381
|
+
| `parseNumber` | string | integer or float; `!` if not numeric |
|
|
382
|
+
| `keys` | object | array of key strings |
|
|
383
|
+
| `values` | object | array of values |
|
|
384
|
+
|
|
385
|
+
### 7.5 Type predicates (predicates)
|
|
386
|
+
|
|
387
|
+
`Integer`, `Float`, `Number`, `String`, `Boolean`, `Array`, `Object`, `Null`. Each
|
|
388
|
+
takes any value and returns a boolean. `Number` is true for integers and floats.
|
|
389
|
+
|
|
390
|
+
### 7.6 Special built-ins: `ENV` and `load`
|
|
391
|
+
|
|
392
|
+
These resolve in the `@name` chain like other built-ins (so a sibling file of the
|
|
393
|
+
same name shadows them), but they are not plain unary value functions:
|
|
394
|
+
|
|
395
|
+
- **`@ENV`** evaluates directly to an object mapping every environment variable name
|
|
396
|
+
to its value. All values are strings; nothing is parsed. Read one with member
|
|
397
|
+
access: `@ENV.PATH`. A missing variable yields `!`.
|
|
398
|
+
- **`@load`** evaluates to a function taking a filename **string verbatim** (no `.fsn`
|
|
399
|
+
appended), resolved relative to the referencing file's directory; it returns that
|
|
400
|
+
file's value, or `!` if the file does not exist. Use it to load files whose names
|
|
401
|
+
are computed at runtime or are not plain identifiers, e.g. `"a.b.fsn" | @load`.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## 8. Member and index access
|
|
406
|
+
|
|
407
|
+
- `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
|
+
key that exists, its value; otherwise `!`.
|
|
411
|
+
|
|
412
|
+
Both `.` and `[]` bind tighter than `|`.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 9. Files, references, and the runtime
|
|
417
|
+
|
|
418
|
+
### 9.1 Files
|
|
419
|
+
|
|
420
|
+
A `.fsn` file contains **exactly one expression**, which is its value. A file is
|
|
421
|
+
**executable** if that value is a function.
|
|
422
|
+
|
|
423
|
+
### 9.2 References
|
|
424
|
+
|
|
425
|
+
A `@` reference takes one of these forms:
|
|
426
|
+
|
|
427
|
+
- **`@`** (nothing after it) — the **current file**'s value. Used for self-recursion.
|
|
428
|
+
- **`@ENV`** — an object of all environment variables (string keys, string values;
|
|
429
|
+
no parsing). Resolved in the `@name` chain below, so it is shadowable.
|
|
430
|
+
- **`@name`** — a single bare identifier (no `/`, no `../`).
|
|
431
|
+
- **`@dir/name`, `@a/b/c`** — a downward path.
|
|
432
|
+
- **`@../name`, `@../../a/b`** — an upward path (contains one or more `../`).
|
|
433
|
+
|
|
434
|
+
**Resolution of `@name` and downward paths** (any reference *without* `../`) proceeds
|
|
435
|
+
in order, first match winning:
|
|
436
|
+
|
|
437
|
+
1. a **sibling file** at `<referencing dir>/<name>.fsn`;
|
|
438
|
+
2. a **built-in** of that exact name (including `ENV` and `load`);
|
|
439
|
+
3. a **standard-library file** at `<stdlib root>/<name>.fsn`.
|
|
440
|
+
|
|
441
|
+
If none match, the result is `!`. Downward paths participate fully: `@math/sqrt`
|
|
442
|
+
checks a sibling `math/sqrt.fsn`, then a built-in named `math/sqrt`, then a stdlib
|
|
443
|
+
`math/sqrt.fsn`.
|
|
444
|
+
|
|
445
|
+
**Resolution of upward paths** (any reference containing `../`) is **file-only**: it
|
|
446
|
+
resolves solely to a file relative to the referencing directory and never falls back
|
|
447
|
+
to a built-in or the standard library.
|
|
448
|
+
|
|
449
|
+
The `.fsn` extension is implied and never written in a `@` reference. File resolution
|
|
450
|
+
is relative to the **referencing file's** directory; built-ins and the standard
|
|
451
|
+
library are global to the runtime but, per the order above, are shadowed by a sibling
|
|
452
|
+
file of the same name. That shadowing is per-directory, not global.
|
|
453
|
+
|
|
454
|
+
**Built-ins are reached through this same mechanism**: `@add`, `@Integer`, etc. are
|
|
455
|
+
`@name` references that resolve at step 2. A *bare* identifier (without `@`) is only
|
|
456
|
+
a pattern hole; it never denotes a built-in.
|
|
457
|
+
|
|
458
|
+
Two built-ins are special in how they resolve:
|
|
459
|
+
|
|
460
|
+
- **`@ENV`** resolves (at step 2) to a fresh object of environment variables.
|
|
461
|
+
- **`@load`** resolves (at step 2) to a function that loads a file by a **verbatim**
|
|
462
|
+
filename string — no `.fsn` is appended — relative to the referencing directory,
|
|
463
|
+
returning that file's value. If the argument is not a string, or the file does not
|
|
464
|
+
exist, the result is an error (`{"kind":"load_bad_arg",...}` or
|
|
465
|
+
`{"kind":"file_not_found","path":...}` respectively). This is the only way to load
|
|
466
|
+
a file whose name is computed at runtime or is not a plain identifier (e.g.
|
|
467
|
+
`"data.config.fsn"`).
|
|
468
|
+
|
|
469
|
+
References are:
|
|
470
|
+
|
|
471
|
+
- **Lazy** — resolved when evaluated/applied, not when a file loads. A file may
|
|
472
|
+
reference itself (recursion) or another file may reference it back (mutual
|
|
473
|
+
recursion) without an infinite load loop.
|
|
474
|
+
- **Memoized** — each file path is loaded and evaluated once per run; shared
|
|
475
|
+
dependencies load once.
|
|
476
|
+
|
|
477
|
+
A **non-productive data cycle** (files whose values reference each other as data, not
|
|
478
|
+
through a function boundary) yields `!` at the point of the cyclic self-reference.
|
|
479
|
+
This error then immediatly bubbles up according to the propagation semantics described
|
|
480
|
+
above. Recursion through functions is not a data cycle.
|
|
481
|
+
|
|
482
|
+
### 9.3 Runtime contract
|
|
483
|
+
|
|
484
|
+
The interpreter reads standard input as JSON, converts it to a Fusion value `v`,
|
|
485
|
+
computes `v | programFunction`, and prints the result on standard output as JSON.
|
|
486
|
+
|
|
487
|
+
- Empty input is treated as `null`.
|
|
488
|
+
- Non-JSON input yields an error (payload `{"kind":"stdin_not_json"}`).
|
|
489
|
+
- **If the final result is an error**, the interpreter prints **nothing** to
|
|
490
|
+
standard output, prints the error's **payload** (as JSON) to standard error, and
|
|
491
|
+
exits with status `1`. Otherwise the result is printed to standard output and the
|
|
492
|
+
interpreter exits `0`. This makes Fusion programs first-class Unix filters: the
|
|
493
|
+
stdout stream carries the value-or-nothing, the stderr stream carries the failure
|
|
494
|
+
detail, and the exit code is `0`/`1` accordingly.
|
|
495
|
+
|
|
496
|
+
### 9.4 Command-line interface
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
fusion <file.fsn> [json-input]
|
|
500
|
+
fusion -e '<source>' [json-input]
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Input comes from the `[json-input]` argument if present, otherwise from standard
|
|
504
|
+
input. Setting the environment variable `FUSION_DEBUG` causes file-not-found and
|
|
505
|
+
parse errors during reference resolution to be reported on standard error.
|