volaro 0.1.0-alpha.13 → 0.1.0-alpha.14
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.
- package/README.md +1 -1
- package/compiler/SOURCE_INFO.json +2 -2
- package/compiler/SOURCE_REV +1 -1
- package/compiler/validator/vlcheck/checks.py +51 -14
- package/compiler/validator/vlcheck/resolve.py +2 -2
- package/compiler/vlbuild/vlbuild/emit.py +37 -18
- package/language/crib.md +8 -0
- package/language/spec.md +4 -2
- package/language/supported.md +10 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Volaro
|
|
2
2
|
|
|
3
|
-
**Limited alpha** (this is `0.1.0-alpha.
|
|
3
|
+
**Limited alpha** (this is `0.1.0-alpha.14`), published under both the
|
|
4
4
|
`latest` and `alpha` dist-tags: `npm install volaro`.
|
|
5
5
|
|
|
6
6
|
Volaro is an experimental application language intended for AI authoring and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"revision": "
|
|
2
|
+
"revision": "e01dc1d92881e38143a73b21dfdf0660cb515906",
|
|
3
3
|
"source": "git",
|
|
4
4
|
"dirty": false,
|
|
5
|
-
"content_sha256": "
|
|
5
|
+
"content_sha256": "6476761bc8a3508734fee9684b80e0efe0ae151a6a40b5f5586bf1595e3a960d"
|
|
6
6
|
}
|
package/compiler/SOURCE_REV
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e01dc1d92881e38143a73b21dfdf0660cb515906
|
|
@@ -255,11 +255,14 @@ AUTH_ERROR_CODES = frozenset({
|
|
|
255
255
|
"provider_error", "invalid_token", "expired_token", "invalid_code",
|
|
256
256
|
"no_totp_enrolled",
|
|
257
257
|
})
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
# caught error itself (`catch e`).
|
|
261
|
-
#
|
|
262
|
-
|
|
258
|
+
# `catch <head>` tests the thrown error's code when <head> is one of these
|
|
259
|
+
# (or a declared union variant), in a route body and in a view handler
|
|
260
|
+
# alike; any other head binds the caught error itself (`catch e`). The code
|
|
261
|
+
# matches when it equals <head> or starts with "<head>." (`catch decode`
|
|
262
|
+
# catches `decode.missing_field`). resolve.py, the W-CATCH-NO-FALLBACK
|
|
263
|
+
# check and vlbuild's `_route_try` / `_handler_try` all read this set, so a
|
|
264
|
+
# catch arm means the same thing to check and build, client and server.
|
|
265
|
+
CATCH_CODES = (_ERROR_CODES | AUTH_ERROR_CODES
|
|
263
266
|
| frozenset({"bad_request", "unauthorized"}))
|
|
264
267
|
_ERR_ROUTE_FIX = ("`return err(not_found(\"...\"))` in a `service` or "
|
|
265
268
|
"page.server route body; a view reports a failure by "
|
|
@@ -414,6 +417,28 @@ def _walk(node):
|
|
|
414
417
|
yield from _walk(getattr(node, f))
|
|
415
418
|
|
|
416
419
|
|
|
420
|
+
def _h1_path(nodes) -> list:
|
|
421
|
+
"""The `h1` elements one render of `nodes` can show together, in source
|
|
422
|
+
order: siblings add up, while the branches of an `if` / `match` are
|
|
423
|
+
alternatives, so only the branch with the most `h1`s counts. Anything
|
|
424
|
+
else is searched whole, as before (a `list` body counts once)."""
|
|
425
|
+
out: list = []
|
|
426
|
+
for n in nodes or ():
|
|
427
|
+
if isinstance(n, A.Element):
|
|
428
|
+
out += ([n] if n.name == "h1" else []) + _h1_path(n.body)
|
|
429
|
+
elif isinstance(n, A.If):
|
|
430
|
+
branches = ([n.then] + [body for _c, body in n.elifs]
|
|
431
|
+
+ [n.orelse])
|
|
432
|
+
out += max((_h1_path(b) for b in branches), key=len)
|
|
433
|
+
elif isinstance(n, A.Match):
|
|
434
|
+
out += max((_h1_path(arm[-1]) for arm in n.arms),
|
|
435
|
+
key=len, default=[])
|
|
436
|
+
else:
|
|
437
|
+
out += [x for x in _walk(n)
|
|
438
|
+
if isinstance(x, A.Element) and x.name == "h1"]
|
|
439
|
+
return out
|
|
440
|
+
|
|
441
|
+
|
|
417
442
|
def _rendered_children(el):
|
|
418
443
|
"""Yield the element children `el` actually renders, in source order.
|
|
419
444
|
|
|
@@ -2398,7 +2423,6 @@ class Checker:
|
|
|
2398
2423
|
if not isinstance(it, A.ViewDecl):
|
|
2399
2424
|
continue
|
|
2400
2425
|
prev = 0
|
|
2401
|
-
first_h1 = None
|
|
2402
2426
|
navs: list = []
|
|
2403
2427
|
mains: list = []
|
|
2404
2428
|
# action-45.3: `aside` (role=complementary) has the same problem
|
|
@@ -2416,14 +2440,6 @@ class Checker:
|
|
|
2416
2440
|
"screen-reader users navigate by level; don't "
|
|
2417
2441
|
"skip one" % (prev, lvl), node,
|
|
2418
2442
|
"use h%d here, or restructure the section" % (prev + 1))
|
|
2419
|
-
if lvl == 1:
|
|
2420
|
-
if first_h1 is not None:
|
|
2421
|
-
self._d("W-HEADING-MULTI-H1", Severity.WARNING,
|
|
2422
|
-
"a view renders more than one `h1`; a page "
|
|
2423
|
-
"usually has exactly one top-level heading",
|
|
2424
|
-
node, "make the later one `h2`")
|
|
2425
|
-
else:
|
|
2426
|
-
first_h1 = node
|
|
2427
2443
|
prev = lvl
|
|
2428
2444
|
elif node.name == "nav":
|
|
2429
2445
|
navs.append(node)
|
|
@@ -2432,6 +2448,14 @@ class Checker:
|
|
|
2432
2448
|
mains.append(node)
|
|
2433
2449
|
elif node.name in landmarks:
|
|
2434
2450
|
landmarks[node.name].append(node)
|
|
2451
|
+
# Branches of an `if` / `match` render one at a time, so a view
|
|
2452
|
+
# with `h1 "Not found"` in one branch and `h1 found.title` in
|
|
2453
|
+
# the other renders one `h1`: count the branch with the most.
|
|
2454
|
+
for node in _h1_path(it.body)[1:]:
|
|
2455
|
+
self._d("W-HEADING-MULTI-H1", Severity.WARNING,
|
|
2456
|
+
"a view renders more than one `h1`; a page "
|
|
2457
|
+
"usually has exactly one top-level heading",
|
|
2458
|
+
node, "make the later one `h2`")
|
|
2435
2459
|
for lname, nodes in landmarks.items():
|
|
2436
2460
|
if lname == "nav" or len(nodes) < 2:
|
|
2437
2461
|
continue # `nav` keeps its own E-NAV-NAME below
|
|
@@ -3530,6 +3554,19 @@ class Checker:
|
|
|
3530
3554
|
"'?' inside 'try' is redundant -- the catch arms below "
|
|
3531
3555
|
"already handle the error, so nothing is propagated", t.expr,
|
|
3532
3556
|
"remove the trailing '?'")
|
|
3557
|
+
heads = [getattr(pat, "head", "") for pat, _body in t.catches]
|
|
3558
|
+
if (self._ctx == "view" and heads
|
|
3559
|
+
and all(h in CATCH_CODES or h in self._variants for h in heads)):
|
|
3560
|
+
# Every arm names a code, so any other error is rethrown out of
|
|
3561
|
+
# the handler. Through 0.1.0-alpha.13 a view handler's
|
|
3562
|
+
# `catch not_found` caught every error; code written against
|
|
3563
|
+
# that now lets the others through. A warning, not an error:
|
|
3564
|
+
# rethrowing can be what the author wants.
|
|
3565
|
+
self._d("W-CATCH-NO-FALLBACK", Severity.WARNING,
|
|
3566
|
+
"every `catch` arm here names an error code, so any other "
|
|
3567
|
+
"error (a 500, a network failure) is rethrown out of the "
|
|
3568
|
+
"handler", t,
|
|
3569
|
+
"add a final `catch e` arm to handle the rest")
|
|
3533
3570
|
self._expr(t.expr, scopes, fallible)
|
|
3534
3571
|
for pat, body in t.catches:
|
|
3535
3572
|
inner = scopes + [_Scope(getattr(pat, "binds", []), "catch binding")]
|
|
@@ -50,7 +50,7 @@ import os
|
|
|
50
50
|
import re
|
|
51
51
|
|
|
52
52
|
from . import ast_nodes as A
|
|
53
|
-
from .checks import (BUILTINS, CONTEXTUAL_WORDS,
|
|
53
|
+
from .checks import (BUILTINS, CONTEXTUAL_WORDS, CATCH_CODES,
|
|
54
54
|
STD_BUILT_FNS, _ERROR_CODES, _FN_NOT_BUILT, _PRIM_NAMES)
|
|
55
55
|
from .diagnostics import Diagnostic, LineIndex, Severity
|
|
56
56
|
from .elements import ELEMENT_NAMES
|
|
@@ -1057,7 +1057,7 @@ class Resolver:
|
|
|
1057
1057
|
head = getattr(pat, "head", "")
|
|
1058
1058
|
binds = list(getattr(pat, "binds", []) or [])
|
|
1059
1059
|
sym = self.table.get(head)
|
|
1060
|
-
if (head == "_" or head in
|
|
1060
|
+
if (head == "_" or head in CATCH_CODES
|
|
1061
1061
|
or (sym is not None and sym.kind == "variant")
|
|
1062
1062
|
or binds != [head]):
|
|
1063
1063
|
return {b: None for b in binds}
|
|
@@ -59,7 +59,7 @@ from vlcheck.elements import (
|
|
|
59
59
|
SPELLCHECK_HOMES, TABINDEX_ALLOWED, TEXT_STYLE_ATTRS,
|
|
60
60
|
UNSAFE_ELEMENTS,
|
|
61
61
|
)
|
|
62
|
-
from vlcheck.checks import (
|
|
62
|
+
from vlcheck.checks import (CATCH_CODES, STD_BUILT_FNS,
|
|
63
63
|
_ERROR_CODES, _lit_int, _lit_text,
|
|
64
64
|
_rendered_children)
|
|
65
65
|
from vlcheck.lexer import Lexer
|
|
@@ -171,7 +171,16 @@ WHERE_PREDICATES = {
|
|
|
171
171
|
"contains": 1,
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _catch_code_test(head: str) -> str:
|
|
177
|
+
"""The JS condition a `catch <code>` arm tests (spec section 6.2), in a
|
|
178
|
+
view handler and a route body alike: the caught error's `code` is
|
|
179
|
+
`head` or starts with `head.`, so `catch decode` also catches the
|
|
180
|
+
runtime's `decode.missing_field`."""
|
|
181
|
+
return ('_vlErr && typeof _vlErr.code === "string" && (_vlErr.code === %s'
|
|
182
|
+
' || _vlErr.code.indexOf(%s) === 0)'
|
|
183
|
+
% (json.dumps(head), json.dumps(head + ".")))
|
|
175
184
|
|
|
176
185
|
|
|
177
186
|
def _where_predicate_call(v):
|
|
@@ -3167,16 +3176,27 @@ class Emitter:
|
|
|
3167
3176
|
return ["var %s = _vlErr;" % self._bind_local(binds[0])]
|
|
3168
3177
|
return []
|
|
3169
3178
|
|
|
3179
|
+
def _is_catch_code(self, head: str) -> bool:
|
|
3180
|
+
"""`catch <head>` tests the error's code (an error code, an auth
|
|
3181
|
+
error code or a declared union variant); any other head (`catch
|
|
3182
|
+
e`, `catch _`) catches every error. One rule for view handlers and
|
|
3183
|
+
route bodies: `vlcheck.checks.CATCH_CODES`."""
|
|
3184
|
+
return head in CATCH_CODES or head in self.variant_params
|
|
3185
|
+
|
|
3170
3186
|
def _handler_try(self, st: A.Try, in_loop: bool = False) -> str:
|
|
3171
|
-
"""Lower Volaro's non-fall-through handled errors (§6.2).
|
|
3187
|
+
"""Lower Volaro's non-fall-through handled errors (§6.2). An arm
|
|
3188
|
+
headed by an error code runs for that code only (it was a
|
|
3189
|
+
catch-all for every code but the auth ones and declared variants,
|
|
3190
|
+
so `catch not_found` swallowed every error); an error no arm
|
|
3191
|
+
matches is rethrown."""
|
|
3172
3192
|
out = ["try { await %s; } catch (_vlErr) {" % self.expr(st.expr)]
|
|
3173
3193
|
fallback = False
|
|
3174
3194
|
for i, (pat, body) in enumerate(st.catches):
|
|
3175
3195
|
head = getattr(pat, "head", "")
|
|
3176
|
-
is_variant =
|
|
3196
|
+
is_variant = self._is_catch_code(head)
|
|
3177
3197
|
if is_variant:
|
|
3178
|
-
out.append("%s (
|
|
3179
|
-
|
|
3198
|
+
out.append("%s (%s) {" % ("if" if i == 0 else "else if",
|
|
3199
|
+
_catch_code_test(head)))
|
|
3180
3200
|
else:
|
|
3181
3201
|
out.append("{" if i == 0 else "else {")
|
|
3182
3202
|
fallback = True
|
|
@@ -3216,10 +3236,10 @@ class Emitter:
|
|
|
3216
3236
|
fallback = False
|
|
3217
3237
|
for i, (pat, body) in enumerate(try_node.catches):
|
|
3218
3238
|
head = getattr(pat, "head", "")
|
|
3219
|
-
is_variant =
|
|
3239
|
+
is_variant = self._is_catch_code(head)
|
|
3220
3240
|
if is_variant:
|
|
3221
|
-
out.append("%s (
|
|
3222
|
-
|
|
3241
|
+
out.append("%s (%s) {" % ("if" if i == 0 else "else if",
|
|
3242
|
+
_catch_code_test(head)))
|
|
3223
3243
|
else:
|
|
3224
3244
|
out.append("{" if i == 0 else "else {")
|
|
3225
3245
|
fallback = True
|
|
@@ -3629,11 +3649,11 @@ class Emitter:
|
|
|
3629
3649
|
`catch e` / `return err(e)` example could not be built.
|
|
3630
3650
|
|
|
3631
3651
|
- An arm headed by an error code (`catch not_found`), a declared
|
|
3632
|
-
union variant or an auth error code runs when the thrown error
|
|
3633
|
-
that `
|
|
3634
|
-
(`catch e`, `catch _`) catches
|
|
3635
|
-
|
|
3636
|
-
arm matches is rethrown.
|
|
3652
|
+
union variant or an auth error code runs when the thrown error's
|
|
3653
|
+
`code` is that head or starts with `head.`, binding the variant's
|
|
3654
|
+
fields by name. Any other head (`catch e`, `catch _`) catches
|
|
3655
|
+
every error and binds it to `e`; arms after it are unreachable,
|
|
3656
|
+
as in a view handler. An error no arm matches is rethrown.
|
|
3637
3657
|
- Statement form: a handled error does not fall through. The arm
|
|
3638
3658
|
ends the route: its last expression statement is the route's
|
|
3639
3659
|
value (like a route body's own last line); otherwise the route
|
|
@@ -3643,7 +3663,6 @@ class Emitter:
|
|
|
3643
3663
|
the `let` then run as usual.
|
|
3644
3664
|
- `err(e)` inside an arm rethrows the caught error unchanged
|
|
3645
3665
|
(`_ex_ServerErr`)."""
|
|
3646
|
-
codes = set(ROUTE_CATCH_CODES) | set(self.variant_params)
|
|
3647
3666
|
call_js = self.expr(t.expr)
|
|
3648
3667
|
out: list[str] = []
|
|
3649
3668
|
target = None
|
|
@@ -3656,10 +3675,10 @@ class Emitter:
|
|
|
3656
3675
|
fallback = False
|
|
3657
3676
|
for i, (pat, body) in enumerate(t.catches):
|
|
3658
3677
|
head = getattr(pat, "head", "")
|
|
3659
|
-
is_code = head
|
|
3678
|
+
is_code = self._is_catch_code(head)
|
|
3660
3679
|
if is_code:
|
|
3661
|
-
out.append("%s (
|
|
3662
|
-
|
|
3680
|
+
out.append("%s (%s) {" % ("if" if i == 0 else "else if",
|
|
3681
|
+
_catch_code_test(head)))
|
|
3663
3682
|
else:
|
|
3664
3683
|
out.append("{" if i == 0 else "else {")
|
|
3665
3684
|
fallback = True
|
package/language/crib.md
CHANGED
|
@@ -49,6 +49,8 @@ run either way goes *before* the `try`; fire-and-forget uses `spawn`.
|
|
|
49
49
|
try auth.sign_in(email: email, password: password)
|
|
50
50
|
catch invalid_credentials
|
|
51
51
|
error = "Wrong email or password." # no `return` needed
|
|
52
|
+
catch e
|
|
53
|
+
error = "Something went wrong."
|
|
52
54
|
route.to("/") # reached only when nothing was caught
|
|
53
55
|
```
|
|
54
56
|
`let v = try EXPR catch ...` above is different: it binds a value (whichever arm
|
|
@@ -59,6 +61,12 @@ fall-through bug this rule exists to prevent — put that logic in each `catch`
|
|
|
59
61
|
arm (or in an `if`/`else` reading a flag the `catch` set, declared `let mut`)
|
|
60
62
|
instead.
|
|
61
63
|
|
|
64
|
+
`catch not_found` (an error code, an auth code like `invalid_credentials`, or a
|
|
65
|
+
variant you declared) runs for that code only; `catch decode` also catches
|
|
66
|
+
`decode.missing_field`. Any other name (`catch e`) or `catch _` catches
|
|
67
|
+
everything, so it goes last. An error no arm matches is rethrown: in a view
|
|
68
|
+
handler, end the arms with `catch e` or check warns `W-CATCH-NO-FALLBACK`.
|
|
69
|
+
|
|
62
70
|
`if C then else E` is also an expression (`derive x = if ok "a" else "b"`):
|
|
63
71
|
`else` **required**, **binary only** (nest for 3+: `if a X else (if b Y else Z)`),
|
|
64
72
|
each branch **one simple expression**, and the whole `derive` on **one line**
|
package/language/spec.md
CHANGED
|
@@ -413,7 +413,9 @@ catch e
|
|
|
413
413
|
return err(e)
|
|
414
414
|
```
|
|
415
415
|
|
|
416
|
-
`?` requires the enclosing function to be fallible with a compatible error type. `??` takes a value of the success type. `try`/`catch` matches on the error union with the same exhaustiveness rules as `match
|
|
416
|
+
`?` requires the enclosing function to be fallible with a compatible error type. `??` takes a value of the success type. `try`/`catch` matches on the error union with the same exhaustiveness rules as `match`; `volaro check` does not enforce them yet.
|
|
417
|
+
|
|
418
|
+
A `catch` arm headed by an error code (`catch not_found`), an auth error code or a declared union variant runs when the error's `code` is that name or starts with that name and a dot, so `catch decode` also catches `decode.missing_field` (§9). Any other head (`catch e`, `catch _`) catches every error, and arms after it never run. An error no arm matches is rethrown, in a view handler and in a route body alike. In a view handler, a `try` whose arms all name codes is the warning `W-CATCH-NO-FALLBACK`, because a 500 or a network failure then leaves the handler: end it with `catch e` to handle the rest.
|
|
417
419
|
|
|
418
420
|
**A handled error does not fall through.** When a `catch` arm runs, the statements after the `try` block do **not** execute — the `try` statement is complete. This is the opposite of the usual imperative reading and it is chosen deliberately: the fall-through version produced a login form that showed an error message *and* redirected to the success page, which was the single most common bug in early Volaro examples.
|
|
419
421
|
|
|
@@ -1063,7 +1065,7 @@ page title:"Weekly Report"
|
|
|
1063
1065
|
|
|
1064
1066
|
`title:` is an expression evaluated at module scope (a literal, or something a module-level `fn` / config produces — not view state). It becomes the document `<title>` when it is a literal, and is assigned to `document.title` at boot regardless, so a full navigation to another page picks up that page's own title. `page` with no `title:`, or a statically empty one, is `E-PAGE-TITLE`; a build that renders a page with no `page` declaration at all is refused (`E-PAGE-TITLE` at build time). A `title:` expression that resolves to empty at load time raises `render.empty_page_title`. One `page` per module (`E-PAGE-DUP`).
|
|
1065
1067
|
|
|
1066
|
-
**Headings.** `h1`…`h6` are `std.ui` primitives that emit the real HTML heading elements. The level is **semantic and independent of visual size** — `h2 "…" size:28` is a second-level heading that happens to be large. Within one view, a heading that jumps more than one level below the previous one is `W-HEADING-SKIP`, and a second `h1` is `W-HEADING-MULTI-H1` — warnings, not errors, and scoped to a single view so a reusable component that legitimately starts at `h2` or `h3` under some page's `h1` is never flagged.
|
|
1068
|
+
**Headings.** `h1`…`h6` are `std.ui` primitives that emit the real HTML heading elements. The level is **semantic and independent of visual size** — `h2 "…" size:28` is a second-level heading that happens to be large. Within one view, a heading that jumps more than one level below the previous one is `W-HEADING-SKIP`, and a second `h1` the view can render at the same time is `W-HEADING-MULTI-H1` (one `h1` in each branch of an `if` is one `h1`) — warnings, not errors, and scoped to a single view so a reusable component that legitimately starts at `h2` or `h3` under some page's `h1` is never flagged.
|
|
1067
1069
|
|
|
1068
1070
|
**Landmarks.** `main` and `nav` are `std.ui` primitives emitting `<main>` / `<nav>`. There is one `main` region per page (`E-MAIN-DUP` for two in a view). A `nav` takes `aria_label:` (emitted as `aria-label`); when a view has more than one `nav`, each **must** carry a distinct name (`E-NAV-NAME`) so assistive tech can tell them apart. These rules hold on the **composed** document, not just within one view: a `main` in the page-root view and in a view it renders (or a view whose `main` is reused) is `E-MAIN-COMPOSED` — every `main` emits `id="vl-main"`, so a second one is also a duplicate id and an ambiguous skip target; two `<nav>`s assistive tech cannot tell apart — both unnamed, or sharing one `aria_label:` — from different composed views are likewise `E-NAV-NAME`; and one static `id:` rendered by more than one element is `E-ID-DUP`. The composed pass follows component instantiation within a module from the page root and treats `if` / `else` branches as mutually exclusive (a landmark in both branches counts once); it does not evaluate conditions, and cross-file `use` composition is not yet followed.
|
|
1069
1071
|
|
package/language/supported.md
CHANGED
|
@@ -472,6 +472,13 @@ audio video source track picture map area
|
|
|
472
472
|
prop or field (`on_retry()`) are not value methods and are unaffected.
|
|
473
473
|
- `try call()?` is a warning (`W-TRY-PROPAGATE`): the `catch` arms already
|
|
474
474
|
handle the error, so the `?` does nothing. Drop it.
|
|
475
|
+
- `catch not_found` in a view handler (an error code, an auth error code
|
|
476
|
+
or a declared variant) runs for that code only, the same as in a route
|
|
477
|
+
body; `catch decode` also catches `decode.missing_field`. `catch e` /
|
|
478
|
+
`catch _` catches anything. An error no arm matches is rethrown. Through
|
|
479
|
+
0.1.0-alpha.13, `catch not_found` and the other standard codes caught
|
|
480
|
+
every error in a view handler. A handler `try` whose arms all name codes
|
|
481
|
+
is a warning (`W-CATCH-NO-FALLBACK`): add a final `catch e`.
|
|
475
482
|
|
|
476
483
|
### Data / services
|
|
477
484
|
- `service` with `db.<model>` query builder (`where` / `order` / `limit` /
|
|
@@ -484,8 +491,9 @@ audio video source track picture map area
|
|
|
484
491
|
value. Any other statement (`match` as a statement, `par`, `spawn`) fails
|
|
485
492
|
the build, at any nesting depth. Use `?` to propagate errors.
|
|
486
493
|
- `try`/`catch` in a route body, both forms. `catch not_found` (an error
|
|
487
|
-
code, or a variant of a union you declare) runs for that code
|
|
488
|
-
binds the variant's fields; `catch e`
|
|
494
|
+
code, or a variant of a union you declare) runs for that code (or a
|
|
495
|
+
`not_found.` code) only and binds the variant's fields; `catch e`
|
|
496
|
+
catches anything. An error no arm
|
|
489
497
|
matches propagates. The bare statement form does not fall through: the
|
|
490
498
|
arm ends the route, and its last expression is the route's value.
|
|
491
499
|
`let x = try ...` gives `x` the arm's last expression and carries on.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "volaro",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.14",
|
|
4
4
|
"description": "Volaro \u2014 an application language written to be authored by an AI agent and read by a person. Ships the language reference and a working compiler for a supported subset (volaro check / build / dev). CLI: `volaro` (alias `vl`).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|