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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0d1da5a83d2c41381cc04c4fd850a157469fdf91718dc306ed0ef3149ce9259
4
- data.tar.gz: 6c39994be87efa19c5a6dda7e4c61b9325dfb91b13f1a9c25999ec0435e22a09
3
+ metadata.gz: 4300463df5e82850cc75d27151c01a6b83e3cd66b90a5c820d40f27d54787250
4
+ data.tar.gz: 044f7a4883952b56aa4336067c994ab78a7dd84e8116f68501c229414c91512e
5
5
  SHA512:
6
- metadata.gz: e268696a925a14546b58fa19ab7f474fc7235b88051910ff861cb649fb6b6842ada3a62cbaf11b2f823026b86debf7431c4b95117b83b9b35f1e6f6601219cc1
7
- data.tar.gz: 539eb8f3e2f7de51f882c83aa4af6bde340aae4d4312d066ca05d7fc8151adf0a765565d1c8622d26ee2645a44e3cee3a50d8bc129562b876e58ce0c8b34eaf3
6
+ metadata.gz: 0d0cd98c87c04c9cc88694ad3d4e926103334fd03d5bfadcf147050c41f63bdb17b4b1bfaad9a5cd9bc456d5ea3ec17ded24db02e4d83d4035d1f403d4d669d6
7
+ data.tar.gz: 38d2637d43d8cbc5c17423a329e64605cde2cb2d154207174ec482611df43b1bc1f5d8435bd1bda751528bdc4a7d029b751b0e5f3fa11d10731afe82044b745a
data/README.md CHANGED
@@ -59,12 +59,16 @@ echo '5' | fusion examples/factorial.fsn # => 120
59
59
  echo '15' | fusion examples/fizzbuzz.fsn # => "FizzBuzz"
60
60
  fusion examples/factorial.fsn 5 # => 120 (input as an argument)
61
61
  fusion -e '(n => [n,2] | @multiply)' 21 # => 42 (inline program)
62
+ printf '[1, 2]\n[3, 4]\n' | fusion --stream examples/double.fsn # => [2,4] [6,8] (NDJSON, one value per line)
63
+ fusion --repl # interactive expressions and `name = expression`
62
64
  ```
63
65
 
64
66
  - Input is read from stdin (or the 2nd CLI arg) as JSON and parsed into a Fusion value.
65
67
  - The file's function gets applied to this value: `value | function`
66
68
  - The result gets printed as JSON to stdout.
67
69
  - Errors get printed to stderr instead and set exit code `1`.
70
+ - How errors cross the boundary is configurable per side (`--input` / `--output`);
71
+ see the [reference](docs/user/reference.md) §9.4.
68
72
 
69
73
  ## Documentation
70
74
 
@@ -74,6 +78,14 @@ Refer to the [Documentation](docs/index.md) for further information.
74
78
 
75
79
  After checking out the repo, run `bin/setup` to install dependencies. Run `bin/console` for an interactive prompt that will allow you to experiment.
76
80
 
81
+ ### Tests
82
+
83
+ ```sh
84
+ bundle exec rspec # fast suite (default) — skips the slow specs
85
+ bundle exec rspec spec/cli_spec.rb # naming a slow file runs it anyway
86
+ bundle exec rake spec:all # everything, including the slow specs
87
+ ```
88
+
77
89
  ## License
78
90
 
79
91
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -3,6 +3,15 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
+ # task :spec (skips slow files, see .rspec)
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
9
+ namespace :spec do
10
+ # task :all
11
+ desc "Run all specs, including the slow ones (real binary / pty)"
12
+ RSpec::Core::RakeTask.new(:all) do |task|
13
+ task.rspec_opts = "--options .rspec-ci"
14
+ end
15
+ end
16
+
8
17
  task default: :spec
data/docs/lang/design.md CHANGED
@@ -137,6 +137,7 @@ Future work and open questions are tracked separately in our [Roadmap](./roadmap
137
137
  - 🧑 ✅ Every function takes exactly one argument and returns one value.
138
138
  - 🧑 ✅ A function literal is `(pattern => result, pattern => result, ...)`.
139
139
  - 🧑 ✅ Clauses are tried top to bottom; the first match wins.
140
+ - 🧑 ✅ The clause list may be empty: `()` is the empty function (not an empty/invalid grouping).
140
141
 
141
142
  ### Alternatives
142
143
 
@@ -216,6 +217,7 @@ Future work and open questions are tracked separately in our [Roadmap](./roadmap
216
217
  - 🧑 ✅ Predicate functions double as a runtime type system: the built-in "types" (`Integer`, `String`, …) are ordinary predicate functions, so `a ? @Integer` matches only integers (inspired by the Ruby gem "literal").
217
218
  - 🔢 ✅ The `?` refinement may follow *any* pattern, not just a lone binder; the clause then matches iff the pattern matches structurally **and** the matched value piped into the predicate yields `true` (Claude offered a restrictive per-binder form vs. this permissive any-pattern form; the designer chose permissive).
218
219
  - 🧑 ✅ The predicate is any function.
220
+ - 🧑 ✅ A predicate may be a `|` chain of functions, the matched value flowing in from the left: `a ? b | c` matches when `a` matches and `a | b | c` is truthy. The grammar's `predicate` is a full `pipe` (was a single `prefix`).
219
221
 
220
222
  ### Alternatives
221
223
 
@@ -223,12 +225,14 @@ Future work and open questions are tracked separately in our [Roadmap](./roadmap
223
225
  - 🤖 ❌ Typed pattern keywords (`n: int`).
224
226
  - 🤖 💭 A separate static type system.
225
227
  - 🤖 ❌ `if`-style guards with arbitrary boolean expressions.
228
+ - 🤖 💭 Keep predicates single-function and compose via an inline `(x => x | b | c)`. Rejected: pure noise next to `b | c`.
226
229
 
227
230
  ### Pros
228
231
 
229
232
  - Unifies three things (structural matching, type checks, value guards) into one mechanism.
230
233
  - The "type system" is user-extensible with ordinary functions.
231
234
  - Nothing new to learn beyond `?`.
235
+ - Predicates compose directly (`a ? @ends | @equals`) instead of requiring a wrapping function (`a ? (x => x | @ends | @equals)`).
232
236
 
233
237
  ### Cons
234
238
 
@@ -292,7 +296,7 @@ Future work and open questions are tracked separately in our [Roadmap](./roadmap
292
296
  ### Cons
293
297
 
294
298
  - `_` does not mean "literally anything" (it excludes `!`), a subtle asymmetry.
295
- - 🩹 An uncaught error can travel far from its origin, which can make debugging harder (mitigated by `FUSION_DEBUG`, by the payload from 2.8, and by potential future diagnostics tracked in the roadmap).
299
+ - 🩹 An uncaught error can travel far from its origin, which can make debugging harder. Partially mitigated by more detailed error payloads in 2.9. Could be improved further with stack traces, see roadmap.
296
300
 
297
301
  ---
298
302
 
@@ -352,16 +356,117 @@ Future work and open questions are tracked separately in our [Roadmap](./roadmap
352
356
  - The catch site can dispatch on the error kind (`(!{"kind":"missing_key"} => ..., !msg => !msg)`).
353
357
  - Construction and matching are syntactically symmetric (`!42` builds on the right of `=>`, matches on the left).
354
358
  - Propagation remains uniform, with no carve-outs to remember.
359
+ - The previous error model with the single error value `!` can still be emulated:
360
+ - The expression `!` is equivalent to `!null`.
361
+ - The pattern `!` is equivalent to `!_`.
355
362
 
356
363
  ### Cons
357
364
 
358
- - The payload format is part of the language's surface built-ins use string payloads (`"divide: division by zero"`) while runtime mechanics use structured object payloads (`{"kind": ...}`), an inconsistency tracked in the roadmap.
365
+ - 🩹 The payload format is part of the language's surface. Builtins used string payloads (`"divide: division by zero"`) while the standard library used structured object payloads (`{"kind": ...}`). This inconsistency was resolved in 2.9.
359
366
  - A payload that itself contains sensitive data becomes part of the program's stderr stream.
360
367
  - The bare-`!`-means-`!null` rule preserves a simple expression form but means a careless `_ => !` clause gives a maximally unhelpful error.
361
368
  - Inspecting an error's payload requires a small catch-and-rebind (`(!a => a)`) rather than direct comparison.
362
369
 
363
370
  ---
364
371
 
372
+ ## 2.9 Standardized error payloads
373
+
374
+ ### Decisions
375
+
376
+ - 🧑 ✅ Every error payload produced by "the runtime" (the interpreter or a built-in function) has the **same shape**. This shape is enforced by constructing "internal errors" via `ErrorVal.internal`. The full schema is documented in [reference §6.5](../user/reference.md#65-the-standardized-error-payload).
377
+ - 🤖 ✅ The stdlib is "ordinary unpriviledged Fusion code" and doesn't produce internal errors.
378
+ - 🧑 ✅ However, all stdlib functions mirror the built-in error shape.
379
+ - 🔢 ✅ During function application we differentiate between:
380
+ - `argument_error`: bad *input shape* (e.g. the wrong number of inputs). Constraints that could be expressed as a pattern without `?`.
381
+ - `type_error`: unsupported *input type* (e.g. string instead of number) or constraints between multiple inputs. Usually would require a `?` to express as a pattern.
382
+ - 🧑 ✅ Member/index access reserves `access_error` for exactly `missing key` and `index out of range`:
383
+ - Accessing a member of a non-object or indexing with a wrong-typed key is a `type_error` instead.
384
+ - File-system access failures ("missing file", "directory instead of file", "permission denied") are a `reference_error` instead.
385
+
386
+ ### Alternatives
387
+
388
+ - 🔢 ⏪ Keep the previous split between builtin errors (string payload) and stdlib errors (object payloads). Only document the rule.
389
+ - 🧑 💭 Don't standardize the error payloads at all.
390
+
391
+ ### Pros
392
+
393
+ - Every catch site (`!` pattern) can rely on this default shape.
394
+ - The structured fields make errors self-describing (what failed, where, on what input).
395
+
396
+ ### Cons
397
+
398
+ - The new structured payloads are more verbose than the old bare strings.
399
+ - Some converted Ruby messages still leak host detail (e.g. an `Errno` message). Pending per-case cleanup.
400
+
401
+ ---
402
+
403
+ ## 2.10 Disallow duplicate binders
404
+
405
+ ### Decisions
406
+
407
+ - 🧑 ✅ Binding the same identifier twice in one clause is a `binding_error`.
408
+ - 🤖 ✅ It is detected in `match` as the binding is produced, so it surfaces only when the clause's shape otherwise matches. A non-matching shape just means the clause does not apply.
409
+
410
+ ### Alternatives
411
+
412
+ - 🧑 ❌ Binding the same identifier twice is a non-linear "must be equal". The pattern only matches, if all parts with the same identifier have the same value.
413
+
414
+ ### Pros
415
+
416
+ - Less implementation effort than "equal identifiers mean equal value". Already solved by fully generic `?` predicates.
417
+ - Runtime detection fits to this language's dynamic nature.
418
+
419
+ ### Cons
420
+
421
+ - Slightly less expressive pattern language.
422
+ - Invalid patterns will only raise an error as soon as a value actually matches. No static guarantees.
423
+
424
+ ---
425
+
426
+ ## 2.11 Stricter objects: unique keys, rest last, closed by default
427
+
428
+ ### Decisions
429
+
430
+ - 🧑 ✅ A fixed object key may not repeat (in expressions and patterns). Keys arriving via `...spread` / `...rest` are dynamic and not checked.
431
+ - 🧑 ✅ In an object pattern, `...rest` must come last.
432
+ - 🧑 ✅ An object pattern without a `...rest` is *closed*. It matches only an object whose keys are *exactly* the pattern's
433
+
434
+ ### Alternatives
435
+
436
+ - 🤖 💭 Last-write-wins for duplicate literal keys (JSON behavior). Rejected: silently dropping a written key hides mistakes.
437
+ - 🧑 ⏪ Object patterns always open — extra keys ignored regardless of a rest. Superseded: it gave no way to assert "exactly these keys".
438
+
439
+ ### Pros
440
+
441
+ - Duplicate keys and misplaced rests are caught statically with a precise message.
442
+ - Closed matching regains full-shape matching, with `...` the explicit opt-in to extra keys — symmetric with arrays.
443
+
444
+ ### Cons
445
+
446
+ - More verbose common case: ignoring extra keys now needs a trailing `...`.
447
+
448
+ ---
449
+
450
+ ## 2.12 Switching to Ruby's truthiness model
451
+
452
+ ### Decisions
453
+
454
+ - 🧑 ✅ Truthiness is Ruby-style: every value is truthy except `false` and `null`. This applies to the `@and`/`@or`/`@not` built-ins and `?` predicates.
455
+
456
+ ### Alternatives
457
+
458
+ - 🧑 ⏪ The builtins `@and`/`@or`/`@not` are strict and return a `type_error` for non booleans. Predicates match only on exactly `true`.
459
+
460
+ ### Pros
461
+
462
+ - Booleans operations return `type_error`s less frequently and are more useful. A lot more functions work as predicates.
463
+
464
+ ### Cons
465
+
466
+ - If you really wanted "strict booleans", you'd now need to build them yourselves.
467
+
468
+ ---
469
+
365
470
  # 3. @ references
366
471
 
367
472
  ## 3.1 A file contains exactly one value
@@ -560,7 +665,103 @@ All access goes through `@`:
560
665
  ### Cons
561
666
 
562
667
  - No streaming; whole input must be buffered and parsed.
563
- - 🩹 A bare `!` with nonzero exit gives little diagnostic detail (mitigated by `FUSION_DEBUG`).
668
+ - 🩹 A bare `!` with nonzero exit gives little diagnostic detail. Mitigated for internal errors by more detailed error payloads in 2.9.
669
+
670
+ ---
671
+
672
+ ## 4.2 No raw Ruby errors reach the user
673
+
674
+ ### Decisions
675
+
676
+ - 🧑 ✅ The CLI contract already spends stdout (the result) and stderr (the error payload). There is **no third channel**, so a raw Ruby backtrace on stderr would corrupt the contract. Fusion therefore **catches every Ruby error a program can trigger and converts it to a standardized payload**.
677
+ - 🔢 ✅ Conversion happens *both* **deep** (so an error becomes catchable by other code as soon as possible) *and* at a **top-level net** (so nothing escapes). Notably `SystemStackError` becomes a regular error value `stack_error` on stderr.
678
+ - 🧑 ✅ The internal "assertions" (`raise Unreachable`) are a deliberate exception. Reaching them is an interpreter bug. Interpreter bugs should surface and are allowed to violate our CLI contract.
679
+ - 🧑 ✅ The old `FUSION_DEBUG` env var (which wrote to stderr via `warn`) is removed entirely.
680
+
681
+ ### Alternatives
682
+
683
+ - 🤖 ❌ A top-level net only — rejected because a mid-program Ruby error would then become a final result rather than a catchable `!`.
684
+ - 🤖 💭 An `internal_error` kind for the invariant asserts. Rejected, because interpreter bugs shouldn't be catchable and shouldn't be a seemingly regular final result.
685
+
686
+ ### Pros
687
+
688
+ - The CLI contract holds unconditionally. A program can even crash the interpreter's host language via infinite recursion and the user still gets a clean JSON payload and exit `1`.
689
+
690
+ ### Cons
691
+
692
+ - Implementation effort.
693
+ - Might hide valuable debugging information. However, the "top-level net" could be made opt-out.
694
+ - The `location` for a stack overflow is only `"interpreter"`. No single owning file is knowable at that point.
695
+
696
+ ---
697
+
698
+ ## 4.3 Internal errors and lenient JSON serialization
699
+
700
+ ### Decisions
701
+
702
+ - 🔢 ✅ To serialize values without a valid JSON representation, we introduce "lenient serialization". Values without JSON representation get turned into string representations (`"<function>"` and `"<Infinity>"`/`"<-Infinity>"`/`"<NaN>"`).
703
+ - 🧑 ✅ The remaining data structure gets preserved.
704
+ - 🔢 ✅ Internal errors (`ErrorVal.internal_error?`) get serialized leniently by default, so their info isn't obscured by a `serialization_error`.
705
+ - 🤖 ✅ The stdlib doesn't produce internal errors.
706
+ - 🧑 ✅ However, all stdlib functions use `@sanitize` to mimick the lenient JSON serialization and preserve as much info as possible.
707
+ - 🧑 ✅ Ordinary values and user errors are serialized strictly to avoid surprising type conversions. If they fail to serialize, they get turned into an internal `serialization_error` and will subsequently get serialized leniently.
708
+
709
+ ### Alternatives
710
+
711
+ - 🤖 ❌ The standard library could create real "internal errors" via a dedicated `@raise` primitive. Declined, because it offers little over the `!` prefix and would let any code (not just the stdlib) create internal errors. The main reason for internal errors (apart from enforcing a consistent shape) is lenient serialization.
712
+ - 🧑 ❌ Internal errors also serialize strictly by default. Rejected, because this would turn too many errors into a `serialization_error` and would lose too much information.
713
+ - 🧑 💭 All errors serialize leniently by default. Rejected, because this hides real errors (e.g. `NaN` or a function as a result) behind an automatic type conversion.
714
+
715
+ ---
716
+
717
+ ## 4.4 CLI use cases and I/O modes
718
+
719
+ Extends the single runtime contract of 4.1 into three use cases and four ways an
720
+ error value can cross the boundary.
721
+
722
+ ### Decisions
723
+
724
+ **Use cases:**
725
+
726
+ - 🧑 ✅ The CLI supports three use cases: **pipe** (apply the program to one input — the 4.1 model, the default), **stream** (apply it to each line of an NDJSON stream), **repl** (interactive).
727
+ - 🧑 ✅ **stream** keeps errors in-band and always exits `0`; a failing record (including a stack overflow) becomes that record's output line and the stream continues. Blank input lines are skipped.
728
+
729
+ **I/O modes** — how an error is marked crossing the boundary; `--input` and `--output` are independent:
730
+
731
+ - 🧑 ✅ Four modes: **unix** (plain JSON; value → stdout/exit 0, error → stderr/exit 1, as in 4.1), **bang** (a leading `!` marks an error), **array** (`[0, value]` / `[1, payload]`), **object** (`{"value": _}` / `{"error": _}`).
732
+ - 🧑 ✅ The `-!` flag (unix input only) makes the whole input an error value.
733
+ - 🧑 ✅ pipe supports all four modes; stream all but unix; repl has none.
734
+ - 🤖 ✅ Defaults: pipe = unix/unix, stream = bang/bang. The non-unix modes always print to stdout and exit `0`.
735
+ - 🤖 ✅ Empty input is `null` in every input mode; `-!` on empty input is `!null`.
736
+ - 🤖 ✅ A malformed `array`/`object` input envelope is a catchable `argument_error` at `location: "input"` (the array tag must be exactly the integer `0`/`1`), flowing into the program like any input error.
737
+
738
+ **REPL:**
739
+
740
+ - 🧑 ✅ An entry is an **expression** (evaluated and printed) or a **statement** `identifier = expression` (evaluated, printed, and bound for later entries). The statement is the only construct that is not an expression.
741
+ - 🧑 ✅ An entry is evaluated only once it parses as a whole statement/expression; an incomplete or invalid buffer keeps the entry open to finish or correct.
742
+ - 🧑 ✅ A statement binds its identifier to the result **including an error result**; reading it back later propagates the error, exactly like reading an `@`-reference that resolved to one.
743
+ - 🤖 ✅ Results print leniently (a function as `"<function>"`, etc.); entries report errors at `location: "code <inline>"`, like `-e`.
744
+ - 🤖 ✅ Results go to stdout; the prompt and echoed input go to stderr (like a shell prompt), so stdout is a clean stream of results.
745
+ - 🤖 ✅ A command-line misuse (unknown flag, unsupported mode combination, missing program) is plain usage text on stderr with exit `1` — it precedes the I/O contract, so it is not a payloaded error.
746
+
747
+ ### Alternatives
748
+
749
+ - 🧑 ⏪ REPL accepts only statements (the original "introduces a single statement" sketch); widened so a bare expression is also an entry.
750
+ - 🧑 ⏪ Statements terminated by `;` (so several could share a line); dropped — completeness is decided by parsing, so a line that parses is submitted and no terminator is needed.
751
+ - 🤖 ⏪ A statement does **not** bind an error result (mirroring pattern binders, which never capture an error). Flipped: a statement is an assignment, not a pattern match; binding an error is harmless and needs no special case.
752
+ - 🤖 ❌ unix mode for stream — one exit code and one stdout/stderr pair cannot mark errors per record.
753
+ - 🤖 💭 A single `--mode` controlling both directions — input and output modes are deliberately independent.
754
+
755
+ ### Pros
756
+
757
+ - One small flag surface spans first-class Unix filters (pipe), bulk processing (stream), and interactive exploration (repl).
758
+ - Errors cross the boundary in whatever shape the surrounding tool wants — exit code, `!` sentinel, or envelope — with input and output chosen independently.
759
+ - The REPL reuses the whole language: an entry is just an expression, with assignment the one addition, and a bound error propagates like any other value-or-error (no carve-out).
760
+
761
+ ### Cons
762
+
763
+ - Four error-marking modes are more surface than the original single unix contract.
764
+ - Completeness-by-parsing submits a complete-but-maybe-unintended line (e.g. `x = 5` when more was meant) as-is.
564
765
 
565
766
  ---
566
767
 
data/docs/lang/roadmap.md CHANGED
@@ -22,26 +22,6 @@ a directory, since shadowing is invisible at the call site.
22
22
 
23
23
  ## 2. Error model
24
24
 
25
- **Payload shape consistency** *(open)*. The payload *shape* is inconsistent:
26
- built-ins use bare strings (`"divide: division by zero"`) while runtime
27
- mechanics use structured objects (`{"kind":"missing_key","key":"foo"}`). The
28
- string form is human-friendlier; the structured form is machine-friendlier
29
- (catchable via `!{"kind": k}`). Three plausible resolutions:
30
-
31
- - (a) all errors get structured payloads with a `kind` and an optional `message`;
32
- - (b) all errors get human strings, and structured matching is left to user code;
33
- - (c) keep both, document the rule that built-in operations use strings and
34
- runtime mechanics use objects.
35
-
36
- This is a small but irreversible decision (it shapes how catch clauses are
37
- written) and should be decided before any external code grows that depends on
38
- the current shapes.
39
-
40
- **Better diagnostics.** `FUSION_DEBUG` exists for file/parse errors; extend
41
- principled diagnostics to runtime error origins (where did this error first
42
- arise?). One option: attaching a source position to the payload (an extra
43
- `"at": "file.fsn:L:C"` field) when `FUSION_DEBUG` is set.
44
-
45
25
  **Stack traces** *(deferred)*. A propagated error tells you what happened, but
46
26
  not the chain of function applications it passed through. A capped trace
47
27
  (last N frames, accessible as an extra payload field, opt-in via env) would
@@ -54,7 +34,7 @@ help in deep pipelines.
54
34
  Populate Tier 1 (written in Fusion): `filter`, `reduce`/`fold`, `reverse`,
55
35
  `head`, `tail`, `last`, `init`, `take`, `drop`, `zip`, `flatten`, `member`,
56
36
  `find`, `all`, `any`, `count`; comparison derivatives `lessEq`, `greaterThan`,
57
- `greaterEq`, `notEquals`; object helpers `entries`, `get`, `set`, `merge`; an
37
+ `greaterEq`, `notEquals`; object helpers `entries`, `merge`; an
58
38
  `if` helper. This is also the best stress test of whether the language is
59
39
  pleasant to *write* in, not just to implement.
60
40
 
@@ -62,7 +42,6 @@ pleasant to *write* in, not just to implement.
62
42
 
63
43
  ## 4. Runtime and tooling
64
44
 
65
- - **Streaming I/O** (NDJSON): map a program over a stream of JSON values.
66
45
  - **A faster implementation** once semantics are frozen.
67
46
 
68
47
  ## 5. Open semantic questions to settle
@@ -8,18 +8,12 @@ basics. Scan for the problem you have and copy the solution.*
8
8
  ## Diagnose a program that returns an error unexpectedly
9
9
 
10
10
  When you run a program and see an error payload on stderr, the payload itself
11
- usually tells you what went wrong (e.g. `"divide: division by zero"` or
12
- `{"kind":"missing_key","key":"email"}`). For errors arising *during reference
13
- resolution* — typically a missing file or a parse error in an `@`-referenced file —
14
- enable extra diagnostics:
11
+ tells you what went wrong. Interpreter errors carry a standardized object whose
12
+ fields (`kind`, `location`, `operation`, `input`, `message`) are documented in
13
+ [reference §6.5](./reference.md#65-the-standardized-error-payload).
15
14
 
16
- ```sh
17
- FUSION_DEBUG=1 fusion program.fsn '...'
18
- ```
19
-
20
- With `FUSION_DEBUG` set, the interpreter prints to stderr the exact path it failed
21
- to find or the parse error it hit. The most common cause is that the `stdlib/`
22
- folder is not where the interpreter expects it, so `@`-references can't be resolved.
15
+ For a missing file or a parse error in an `@`-referenced file, the `location` and
16
+ `input` fields name the exact path that failed.
23
17
 
24
18
  ---
25
19