rustyfi_syntax/cst_v1.rs
1//! The SATySFi **0.1.0** (`dev-0-1-0`) surface grammar — a *fork* of
2//! [`crate::cst`], not a version-gate of it (gating one shared `cst.rs` would
3//! mean hand-writing `Parse` for nearly every node, destroying the derive
4//! idiom and risking 0.0.6 on every 0.1 edit). [`crate::cst`] stays frozen:
5//! this module imports only its token-`Atom`-generic, non-recursive helpers
6//! ([`crate::cst::Header`], [`crate::cst::ParseFileError`]) and re-declares
7//! everything else — including its own `*ErasedV1` eraser leaves and its own
8//! copy of `render_parse_error` — so that touching `cst_v1.rs` never touches
9//! `cst.rs`.
10//!
11//! **Scope.** SATySFi 0.1's grammar adds a whole ML-style module system
12//! (`bind`/`modexpr`/`sigexpr`/`decl`) on top of an expr/pattern/type layer
13//! that is structurally close to 0.0.6's, plus five surface deltas:
14//! `,`-separated lists/records (not `;`), `EXACT_EQ` for both definitional
15//! and record `=` (reusing [`DefEqTok`] — no new `=` leaf), mandatory
16//! `match … with … end`, per-binding staging instead of a whole-file
17//! `@stage:` header, and no `when`/`while`/`before` at all. This module
18//! builds:
19//!
20//! * [`FileV1`] — `header* expr EOI` or `header* module Name
21//! option(sig_annot) = struct bind* end EOI`.
22//! * [`Bind`] — every arm of upstream `bind`: `val`/`val inline`/`val
23//! block`, `val math` (math-split — see
24//! [`Bind::ValueMath`]), `val rec … and …`/`val mutable`/`type … and …`,
25//! `module … = modexpr`/`signature … = sigexpr`/`include
26//! modexpr`.
27//! * [`ast::ModExpr`]/[`ast::SigExpr`]/[`ast::Decl`] — the full
28//! module/signature grammar: functor literals/
29//! application, module paths/aliases, `:>` coercion, `sig … end` with
30//! every `decl` form, `with type` refinement, `include`.
31//! * A copy of [`crate::cst::ast`]'s expr/pattern/type layer with the 0.1
32//! deltas applied (see each type's doc comment for the exact delta),
33//! including the full `let rec … and … in`/`let mutable … in` expression
34//! forms, a widened `TypeExpr` grammar (products, prefix application),
35//! `inline […]`/`block […]`/`math […]` command types
36//! (`parser_v1.mly:730-735`) and `LONG_LOWER` qualified type paths
37//! (`:720-728,742-743`; `TypeApp::AppliedLong`/`TypeAtom::LongName`).
38//!
39//! **Grammar shipped with placeholder semantics only.** Every
40//! module/signature construct beyond the struct-literal
41//! `ModExpr::Struct` body PARSES and round-trips, but lowers to a precise
42//! `LowerError` (`v1/lower.rs`) rather than real semantics — see that
43//! module's doc comment for the placeholder set and the seal rule.
44//!
45//! **Deliberately NOT built**: macro binds/decls
46//! and row quantifiers (`rowquant`). Staging DOES parse — the
47//! operand prefixes `&e`/`~e` ([`ast::StagePrefix`],
48//! `parser_v1.mly:870-873`) and the per-binding qualifier of `val ~x`/`val
49//! persistent ~x` ([`BindStageV1`], `:417-421` and the decl form
50//! `:600-603`), with `persistent` a 0.1-only keyword token.
51//!
52//! **The `#[recurse]` SCC story (five roots).** Five
53//! singleton, directly self-referential roots — the same shape
54//! [`crate::cst::ast`] uses, for the same reason (see its module doc comment
55//! for the measured compile-time blowup a naive transcription hits):
56//!
57//! * [`ast::Expr`] (its variants' own `Box<Expr>` children);
58//! * [`ast::PatBot`] (`CtorApplied`'s `Box<PatBot>` argument);
59//! * [`ast::TypeExpr`] (`Fun`'s right-recursive `Box<TypeExpr>` codomain);
60//! * [`ast::ModExpr`] (`Functor.body`'s `Box<ModExpr>` self-loop);
61//! * [`ast::SigExpr`] (`Functor.dom`/`Functor.cod`'s `Box<SigExpr>`
62//! self-loop — encoded left-recursion-safe, `with` is
63//! bot+suffix, never `With { base: Box<SigExpr> }`; see [`ast::SigExpr`]'s
64//! own doc comment).
65//!
66//! Every other recursion edge is routed through the erasers declared below
67//! ([`ExprErasedV1`], [`PatErasedV1`], [`PatBotErasedV1`], [`TyErasedV1`],
68//! [`MathErasedV1`], [`ModExprErasedV1`], [`SigExprErasedV1`],
69//! [`TypeBindsErasedV1`]), keeping each SCC a singleton and the wrapped
70//! grammar's recursion reborrowing one stream type (syan pins it, not us).
71//! [`ast::Decl`] is a satellite, not a root: it has no `Box<Self>` anywhere
72//! and no type inside the `#[recurse]` module ever names it — it is reached
73//! only through the hand-written [`StructDeclV1`] connector (an opaque leaf
74//! to the SCC analysis, mirroring [`StructBindV1`]), so `SigExpr ↔ Decl`
75//! never forms a rootless static sub-cycle.
76
77use crate::leaf::*;
78use newer_type::implement;
79use syan::parse::{Parse, Unparse};
80
81/// `@require:` / `@import:` header element — byte-identical between 0.0.6
82/// and `dev-0-1-0` (this port's own confirmation), so 0.1 simply reuses
83/// [`crate::cst`]'s definition rather than re-declaring an identical enum.
84/// 0.1 has no `@stage:` header at all (the shared lexer's `V0_1` path
85/// rejects it outright — see `lexer.rs`'s `lex_header`), so
86/// [`crate::cst::Header`]'s absence of a `Stage` variant costs nothing here.
87pub use crate::cst::Header;
88
89/// A 0.1 header element — the UNION of BOTH packaging generations' header
90/// forms (Axis B). `Legacy` is `dev-0-1-0`'s `@require:`/`@import:`
91/// (byte-identical to 0.0.6's, reusing [`Header`]); the three `Use*`
92/// forms are `saphe-split`'s `headerelem` (`parser.mly:371-380 @ b836d512`).
93/// Which family is *legal* is a `LoadMode` question the loader answers
94/// (`rustyfi_loader`), not a grammar question — this ONE `V0_1` grammar
95/// accepts both so the mode error can be raised at load time with a better
96/// message than a lex error would give.
97///
98/// Variant order is parse priority (syan ordered-alternatives, most-specific
99/// first): `UsePackage` (the `package` keyword disambiguates) precedes the
100/// `of`-suffixed `UseOf`, which precedes bare `Use` (longest-match: `use M of
101/// …` must claim its `of` before a bare `use M` matches), which precedes the
102/// token-disjoint `Legacy` (`@`-headers lex to distinct tokens).
103#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
104pub enum HeaderV1 {
105 /// `USE PACKAGE optional_open mod_chain` — depend on an installed package
106 /// by its consumer-chosen alias (`used_as`). Header attributes
107 /// (`#[test-only]` etc., upstream `list(attribute)`) are DEFERRED.
108 UsePackage {
109 use_kw: KwUse,
110 package_kw: KwPackage,
111 open_kw: Option<KwOpen>,
112 path: ast::ModChainV1,
113 },
114 /// `USE optional_open mod_chain OF STRING` — load a local file by
115 /// backtick-quoted relative path (the `@import:` analog).
116 UseOf {
117 use_kw: KwUse,
118 open_kw: Option<KwOpen>,
119 path: ast::ModChainV1,
120 of_kw: KwOf,
121 relpath: LiteralTok,
122 },
123 /// `USE optional_open mod_chain` — sibling module inside the same package
124 /// (closed resolution; only legal inside envelope source trees, enforced
125 /// by the loader).
126 Use {
127 use_kw: KwUse,
128 open_kw: Option<KwOpen>,
129 path: ast::ModChainV1,
130 },
131 /// `@require:`/`@import:` — Legacy packaging, unchanged shape.
132 Legacy(Header),
133}
134
135impl HeaderV1 {
136 /// A short human-readable name for this header, for loader diagnostics
137 /// (e.g. `use package Stdlib`, `@require: foo`).
138 pub fn display_name(&self) -> String {
139 match self {
140 Self::UsePackage { path, .. } => format!("use package {}", path.render()),
141 Self::UseOf { path, relpath, .. } => {
142 format!("use {} of `{}`", path.render(), relpath.body)
143 }
144 Self::Use { path, .. } => format!("use {}", path.render()),
145 Self::Legacy(Header::Require(t)) => format!("@require: {}", t.content),
146 Self::Legacy(Header::Import(t)) => format!("@import: {}", t.content),
147 Self::Legacy(Header::Stage(_)) => "@stage:".to_string(),
148 }
149 }
150}
151
152impl ast::ModChainV1 {
153 /// The dotted path as source text, e.g. `Stdlib.Logo` or `Local`.
154 pub fn render(&self) -> String {
155 match self {
156 Self::Long(t) => {
157 let mut parts = t.mods.clone();
158 parts.push(t.name.clone());
159 parts.join(".")
160 }
161 Self::Single(t) => t.name.clone(),
162 }
163 }
164
165 /// The HEAD component — the module/envelope identifier the loader keys
166 /// dependency resolution off (upstream's `used_as` map is keyed by it;
167 /// the tail is submodule access, a typecheck-time concern). For `A.B.C`
168 /// that is `A`; for a bare `A` it is `A`.
169 pub fn head_name(&self) -> String {
170 match self {
171 Self::Long(t) => t.mods.first().cloned().unwrap_or_else(|| t.name.clone()),
172 Self::Single(t) => t.name.clone(),
173 }
174 }
175}
176
177/// A binding-position NAME: `LOWER | ( binop )` — upstream 0.1's
178/// `bound_identifier` (`parser_v1.mly:358-363`) is the same nonterminal
179/// 0.0.6's `var` folds ([`crate::cst::BindName`]'s doc comment), and the
180/// leaf-level parse (`VarTok` | `OpNameTok`) is identical in both
181/// generations, so 0.1 reuses the type rather than re-declaring it —
182/// another token-generic, non-recursive import like [`Header`] above.
183pub use crate::cst::BindName;
184
185/// A whole 0.1 `.saty`/`.satyh` file (`main`, upstream `parser_v1.mly:364-
186/// 368`): a header list followed by either a library (`main_lib`) or a
187/// document expression. Unlike 0.0.6's [`crate::cst::File`] (a flat prelude
188/// of top-level `let`s with an optional trailing `in body`), 0.1 has no flat
189/// top-level binding sequence at all: a document body is *just* an
190/// [`ast::Expr`] (every `let` chains its own `in`), and a library is exactly
191/// one `module … = struct … end`.
192#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
193pub enum FileV1 {
194 /// `header* expr EOI` (`parser_v1.mly:367`).
195 Document {
196 headers: Vec<HeaderV1>,
197 body: ast::Expr,
198 eoi: EoiTok,
199 },
200 /// `header* MODULE UPPER option(sig_annot) EXACT_EQ STRUCT bind* END
201 /// EOI` (`parser_v1.mly:372-375`, `main_lib`; `sig_annot = COERCE
202 /// sigexpr`, `:555-557`). Note 0.1's annotation sigil is
203 /// `:>` (COERCE), never 0.0.6's `: sig … end`.
204 Library {
205 headers: Vec<HeaderV1>,
206 module_kw: KwModule,
207 name: CtorTok,
208 sig_annot: Option<SigAnnotV1>,
209 eq: DefEqTok,
210 struct_kw: KwStruct,
211 binds: Vec<Bind>,
212 end_kw: KwEnd,
213 eoi: EoiTok,
214 },
215}
216
217/// `COERCE sigexpr` — a signature annotation `:> S` (`sig_annot`,
218/// `parser_v1.mly:555-557`). 0.1's annotation sigil is `:>` (COERCE,
219/// `lexer_v1.mll:280`), NOT 0.0.6's `: sig … end` ([`crate::cst::SigAnnot`],
220/// `cst.rs:295-303`) — `module M : S = …` is a 0.1 parse error (pinned in
221/// tests). The signature body goes through [`SigExprErasedV1`]: `SigAnnotV1`
222/// lives outside the `#[recurse]` module, so this is a cross-boundary edge
223/// into the `SigExpr` root (see the module doc comment's SCC story).
224#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
225pub struct SigAnnotV1 {
226 pub coerce: CoerceTok,
227 pub sig_: SigExprErasedV1,
228}
229
230/// One parameter of a [`Bind`] (`param_unit`, `parser_v1.mly:635-646`): an
231/// optional `?(l = x, …)` labeled-optional binder bundle, then either a
232/// plain `patbot` or a `( pat : τ )` ascribed pattern
233/// ([`ast::ParamBody::Ascribed`]). Defined INSIDE [`mod@ast`]
234/// and re-exported here so that [`ast::Expr::Fun`]/[`ast::Expr::LetIn`]/
235/// [`ast::RecClauseV1`] can reference it without a boundary-crossing
236/// Parse-trait cycle (the `TypeBindsErasedV1` E0275 hazard).
237pub use ast::{AscribedInnerV1, OptParamEntryV1, OptParamsV1, Param, ParamBody};
238
239/// Every arm of `bind` (`parser_v1.mly:415-440`) — upstream's own
240/// nonterminal name (helper types like [`StructBindV1`]/
241/// [`TypeBindSingleV1`] keep a `V1` suffix). Every value arm's `=` is
242/// `EXACT_EQ` ([`DefEqTok`]) and body is an [`ast::Expr`]. `name` is a
243/// [`crate::cst::BindName`] wherever upstream's `bound_identifier` reaches it
244/// (`Value`, and the rec clauses inside [`ast::RecClauseV1`]); `ValueMutable`
245/// and `ValueInline`/`ValueBlock`'s `ctx` stay plain [`VarTok`]s — upstream's
246/// `MUTABLE LOWER …`/ctx-variable productions are a plain `LOWER`, not
247/// `bound_identifier` (see [`crate::cst::BindName`]'s doc comment for the
248/// ordered-choice-safety argument).
249#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
250pub enum Bind {
251 /// `VAL PERSISTENT? EXACT_TILDE? bind_value_nonrec`
252 /// (`parser_v1.mly:416-421,442,459-465`): `val <stage>? <name> <param>*
253 /// = <expr>`, where `<stage>` is `~` (stage 0) or `persistent ~` (the
254 /// persistent stage) and its absence means stage 1, the document stage.
255 ///
256 /// The prefix is an `Option<BindStageV1>` tried before `name`; on an
257 /// unstaged `val x = …` it fails at the first token, collapses to `None`
258 /// and steals nothing, so every existing fixture parses unchanged. It
259 /// also keeps this arm ordered-choice-safe against the keyword-headed
260 /// `Value*` arms below: `val ~rec …` still fails here (at `name`, which
261 /// cannot match the `rec` keyword) and falls through, exactly as `val
262 /// rec …` does.
263 Value {
264 kw: KwVal,
265 stage: Option<BindStageV1>,
266 name: BindName,
267 params: Vec<Param>,
268 eq: DefEqTok,
269 body: ast::Expr,
270 },
271 /// `VAL INLINE bind_inline` (`parser_v1.mly:422-431` dispatch → `448` →
272 /// `466-491`): `val inline <ctx> \cmd <param>* = <expr>` (the
273 /// heavyweight, ctx-explicit form — the only one `stdja-mini` uses;
274 /// `ctx` stays `Option` so the lightweight, ctx-synthesized form parses
275 /// too, for free).
276 ValueInline {
277 kw: KwVal,
278 /// See [`Bind::Value::stage`] — upstream's qualifier sits before the
279 /// whole `bind_value`, and `bind_value` is what `inline`/`block`/
280 /// `math`/`rec`/`mutable` select between (`parser_v1.mly:417-421` →
281 /// `:581-593`), so every arm below carries the same prefix.
282 stage: Option<BindStageV1>,
283 inline_kw: KwInline,
284 ctx: Option<VarTok>,
285 cmd: AnyHorzCmdTok,
286 params: Vec<Param>,
287 eq: DefEqTok,
288 body: ast::Expr,
289 },
290 /// `VAL BLOCK bind_block` (`parser_v1.mly:450` → `493-518`): `val block
291 /// <ctx> +cmd <param>* = <expr>`.
292 ValueBlock {
293 kw: KwVal,
294 /// See [`Bind::ValueInline::stage`].
295 stage: Option<BindStageV1>,
296 block_kw: KwBlock,
297 ctx: Option<VarTok>,
298 cmd: AnyVertCmdTok,
299 params: Vec<Param>,
300 eq: DefEqTok,
301 body: ast::Expr,
302 },
303 /// `VAL MATH bind_math` (`parser_v1.mly:452-453` dispatch → `520-531`):
304 /// `val math <ctx> \cmd <param>* [with <sub> <sup>] = <expr>`.
305 /// Unlike `ValueInline`/`ValueBlock`, `ctx` is MANDATORY — upstream
306 /// has no lightweight ctx-less form (contrast `bind_inline`'s two
307 /// productions, :466-491). Placed after
308 /// `ValueBlock`, ordered-choice-safe for the same reason as `Value`
309 /// above: `math`/`with` both lex as keyword tokens under V0_1, so no
310 /// arm can steal another's input.
311 ValueMath {
312 kw: KwVal,
313 /// See [`Bind::ValueInline::stage`].
314 stage: Option<BindStageV1>,
315 math_kw: KwMath,
316 ctx: VarTok,
317 /// `\cmd` — math commands share the `\` sigil with inline commands
318 /// (there is no separate math-command token; see `elaborate.rs`'s
319 /// `command_scheme` doc comment, which notes the same sharing on
320 /// the eval side).
321 cmd: AnyHorzCmdTok,
322 params: Vec<Param>,
323 scripts: Option<ScriptsParamV1>,
324 eq: DefEqTok,
325 body: ast::Expr,
326 },
327 /// `VAL REC bind_value_nonrec (AND bind_value_nonrec)*`
328 /// (`parser_v1.mly:444-445,455-465`): `val rec f p* = e (and g p* = e)*`.
329 /// With `rec`/`mutable`/`inline`/`block` all lexed as keyword tokens
330 /// under V0_1, no arm can steal another's input —
331 /// `Value.name: BindName` cannot match a keyword token — so declared
332 /// order is a documentation/perf choice; `Value` stays first because it
333 /// is the overwhelmingly common arm.
334 ValueRec {
335 kw: KwVal,
336 /// See [`Bind::ValueInline::stage`]. One qualifier covers the whole
337 /// `and`-chain, matching upstream's single `UTBindValue(stage,
338 /// UTRec(binds))`.
339 stage: Option<BindStageV1>,
340 rec_kw: KwRec,
341 first: ast::RecClauseV1,
342 ands: Vec<ast::AndClauseV1>,
343 },
344 /// `VAL MUTABLE LOWER REVERSED_ARROW expr` (`parser_v1.mly:446-447`):
345 /// `val mutable x <- e`. The name is a plain `LOWER` upstream (not
346 /// `bound_identifier`), hence `VarTok`, matching the cst target
347 /// (`cst::TopBinding::LetMutable.name`, `cst.rs:237`).
348 ValueMutable {
349 kw: KwVal,
350 /// See [`Bind::ValueInline::stage`].
351 stage: Option<BindStageV1>,
352 mutable_kw: KwMutable,
353 name: VarTok,
354 arrow: OverwriteEqTok,
355 value: ast::Expr,
356 },
357 /// `TYPE bind_type_single (AND bind_type_single)*`
358 /// (`parser_v1.mly:432-433,535-544`): `type t 'a* = body (and u 'a* =
359 /// body)*` — variant and synonym forms, mutually recursive across the
360 /// `and` chain.
361 Type {
362 kw: KwType,
363 first: TypeBindSingleV1,
364 ands: Vec<TypeAndV1>,
365 },
366 /// `MODULE UPPER option(sig_annot) EXACT_EQ modexpr` — upstream
367 /// `bind`'s MODULE arm (`parser_v1.mly:434-435`), with the FULL
368 /// `modexpr` body and the optional `:>` annotation. The
369 /// body goes
370 /// through [`ModExprErasedV1`]: `Bind` is outside the `#[recurse]`
371 /// module, and `Bind → ModExpr → StructBindV1 → Bind` is the runtime
372 /// cycle both connectors erase (one break per direction).
373 Module {
374 module_kw: KwModule,
375 name: CtorTok,
376 sig_annot: Option<SigAnnotV1>,
377 eq: DefEqTok,
378 body: ModExprErasedV1,
379 },
380 /// `SIGNATURE UPPER EXACT_EQ sigexpr` (`parser_v1.mly:436-437`).
381 Signature {
382 kw: KwSignature,
383 name: CtorTok,
384 eq: DefEqTok,
385 sig_: SigExprErasedV1,
386 },
387 /// `INCLUDE modexpr` (`:438-439`) — a bind-include includes a MODULE
388 /// (contrast [`ast::Decl::Include`], which includes a signature).
389 Include { kw: KwInclude, body: ModExprErasedV1 },
390}
391
392/// The stage qualifier of a `val` bind or `val` decl: `~` alone is stage 0,
393/// `persistent ~` is the persistent stage (`parser_v1.mly:417-421` for binds,
394/// `:600-603` for decls). No prefix at all is stage 1 — the document stage,
395/// where an ordinary `val` lives — which is why the field holding this is an
396/// `Option`.
397///
398/// This is 0.1's replacement for 0.0.6's whole-file `@stage:` header: the
399/// same three stages, chosen per binding instead of per file.
400#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
401pub struct BindStageV1 {
402 pub persistent: Option<KwPersistent>,
403 pub tilde: ExactTildeTok,
404}
405
406/// `scripts_param` (`parser_v1.mly:532-534`): `WITH sub=LOWER sup=LOWER` —
407/// `val math`'s optional `with sub sup` suffix, binding the two
408/// script-callback parameters directly rather than synthesizing the
409/// hidden `%math-attach-scripts` wrapper.
410#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
411pub struct ScriptsParamV1 {
412 pub with_kw: KwWith,
413 pub sub: VarTok,
414 pub sup: VarTok,
415}
416
417/// One `bind_type_single` (`parser_v1.mly:539-544`). **0.1 delta from
418/// [`crate::cst::TypeDecl`]:** the type parameters come AFTER the name
419/// (`type t 'a = …`, `tyident LOWER; tyvars list(TYPEVAR)`), where 0.0.6
420/// writes them before (`type 'a t = …`, `cst.rs:401-408`) — the lowering
421/// reorders the fields. No `constraint` suffix exists in 0.1's production.
422#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
423pub struct TypeBindSingleV1 {
424 pub name: VarTok,
425 pub tyvars: Vec<TypeVarTok>,
426 pub eq: DefEqTok,
427 pub body: TypeBodyV1,
428}
429
430/// An `and bind_type_single` continuation (`bind_type`'s
431/// `separated_nonempty_list(AND, …)`, `parser_v1.mly:535-537`).
432#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
433pub struct TypeAndV1 {
434 pub and_kw: KwAnd,
435 pub bind: TypeBindSingleV1,
436}
437
438/// One whole `bind_type` chain — `bind_type_single (AND bind_type_single)*`
439/// (`parser_v1.mly:535-537`) — grouped into a single struct so the sig
440/// layer ([`ast::SigExpr::WithType`], [`ast::Decl::Type`]) can reference the
441/// chain through ONE eraser ([`TypeBindsErasedV1`]). [`Bind::Type`] keeps
442/// its flattened `first`/`ands` fields unchanged (avoiding call-site
443/// churn); the two spellings are the same grammar.
444#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
445pub struct TypeBindsV1 {
446 pub first: TypeBindSingleV1,
447 pub ands: Vec<TypeAndV1>,
448}
449
450/// The right-hand side of one type bind: a variant's constructor list
451/// (`EXACT_EQ BAR? variants`, `parser_v1.mly:540-541,545-553`) or a
452/// transparent synonym (`EXACT_EQ typ`, `:542-543`). Variant-first is
453/// unambiguous for the same reason as [`crate::cst::TypeDeclBody`]
454/// (`cst.rs:410-418`): a variant list is `BarTok`/`CtorTok`-headed and no
455/// [`ast::TypeExpr`] can start with either.
456#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
457pub enum TypeBodyV1 {
458 Variant {
459 leading_bar: Option<BarTok>,
460 first: VariantDefV1,
461 rest: Vec<BarVariantDefV1>,
462 },
463 Synonym(ast::TypeExpr),
464}
465
466/// One `UPPER [OF typ]` variant (`parser_v1.mly:549-553`).
467#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
468pub struct VariantDefV1 {
469 pub ctor: CtorTok,
470 pub of_ty: Option<OfTypeV1>,
471}
472
473/// The `of typ` payload suffix.
474#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
475pub struct OfTypeV1 {
476 pub of_kw: KwOf,
477 pub ty: ast::TypeExpr,
478}
479
480/// A `| UPPER [OF typ]` continuation (`variants`' `separated_nonempty_
481/// list(BAR, variant)`, `parser_v1.mly:545-548`).
482#[derive(Parse, Unparse, Debug, Clone, PartialEq)]
483pub struct BarVariantDefV1 {
484 pub bar: BarTok,
485 pub def: VariantDefV1,
486}
487
488/// One declaration inside a `module … = struct … end` body.
489/// [`Bind`]'s own alternatives are exactly what a struct
490/// body may contain (`bind*`), so this simply re-parses a [`Bind`] — but
491/// *not* by naming `Bind` as a field type directly: [`Bind`] lives
492/// **outside** the `#[recurse]` module (below), so `Bind -> ModExpr ->
493/// Vec<StructBindV1> -> Bind` would be a self-recursive cycle through a
494/// plain `#[derive(Parse)]`, which (without the `#[recurse]` engine to back
495/// it) is an `E0275` hazard (an unbounded recursive trait-bound
496/// obligation) — exactly [`crate::cst::StructDecl`]'s own rationale
497/// (`cst.rs:262-269`). Hand-writing `Parse`/`Unparse` here — the same trick
498/// as the `erased_leaf_v1!` macro below — sidesteps that: the impl has no
499/// recursive where-bound for the compiler to try to satisfy, it just calls
500/// `Bind::parse` through the stream-erasing adapter at runtime.
501#[derive(Debug, Clone, PartialEq)]
502pub struct StructBindV1(pub Box<Bind>);
503
504impl Parse<crate::token::Atom> for StructBindV1 {
505 type Error = syan::error::ParseError<crate::span::Span>;
506
507 fn parse_stream<S: syan::parse::ParseStream<Atom = crate::token::Atom>>(
508 stream: &mut S,
509 ) -> Result<Self, Self::Error> {
510 let value = <Bind as Parse<_>>::parse_stream(stream)?;
511 Ok(StructBindV1(Box::new(value)))
512 }
513}
514
515impl Unparse<crate::token::Atom> for StructBindV1 {
516 fn unparse<S: syan::parse::unparse::Emitter<crate::token::Atom>>(
517 &self,
518 sink: &mut S,
519 ) -> Result<(), S::Error> {
520 self.0.unparse(sink)
521 }
522}
523
524/// One declaration inside a `sig … end` body (`list(decl)`,
525/// `parser_v1.mly:591`) — [`StructBindV1`]'s twin, hand-written `Parse`/
526/// `Unparse` for the same `E0275` reason. [`ast::Decl`] lives INSIDE the
527/// `#[recurse]` module and `SigExpr → SigBotV1 → StructDeclV1 → Decl →
528/// SigExpr` is a runtime cycle; naming `ast::Decl` as a plain derived field
529/// of [`ast::SigBotV1`] would re-enter the module's own SCC analysis. As an
530/// opaque leaf it closes that cycle at RUNTIME while keeping both SCCs
531/// singletons. NOTE: named after [`crate::cst::StructDecl`] (the mechanism),
532/// even though it carries a sig-`decl`, not a struct binding.
533#[derive(Debug, Clone, PartialEq)]
534pub struct StructDeclV1(pub Box<ast::Decl>);
535
536impl Parse<crate::token::Atom> for StructDeclV1 {
537 type Error = syan::error::ParseError<crate::span::Span>;
538
539 fn parse_stream<S: syan::parse::ParseStream<Atom = crate::token::Atom>>(
540 stream: &mut S,
541 ) -> Result<Self, Self::Error> {
542 let value = <ast::Decl as Parse<_>>::parse_stream(stream)?;
543 Ok(StructDeclV1(Box::new(value)))
544 }
545}
546
547impl Unparse<crate::token::Atom> for StructDeclV1 {
548 fn unparse<S: syan::parse::unparse::Emitter<crate::token::Atom>>(
549 &self,
550 sink: &mut S,
551 ) -> Result<(), S::Error> {
552 self.0.unparse(sink)
553 }
554}
555
556/// Recursion-edge eraser types for the expr/pattern/type layer —
557/// the `cst_v1` analogue of [`crate::cst`]'s `erased_leaf!` macro (see its
558/// doc comment for the measured compile-time blowup that makes this
559/// mandatory). Suffixed `V1` throughout so these never collide with
560/// [`crate::cst`]'s own erasers, even though the two live in sibling
561/// modules and could not actually name-clash. Defined *outside* the
562/// `#[recurse]` module so the macro treats them as opaque leaves.
563macro_rules! erased_leaf_v1 {
564 ($($(#[$doc:meta])* $name:ident => $target:ty;)*) => {
565 $(
566 $(#[$doc])*
567 #[implement(newer_type_std::ops::Deref)]
568 #[derive(Debug, Clone, PartialEq)]
569 pub struct $name(pub Box<$target>);
570
571 impl Parse<crate::token::Atom> for $name {
572 type Error = syan::error::ParseError<crate::span::Span>;
573
574 fn parse_stream<S: syan::parse::ParseStream<Atom = crate::token::Atom>>(
575 stream: &mut S,
576 ) -> Result<Self, Self::Error> {
577 // No erasure any more — see `cst.rs`'s `erased_leaf!`.
578 let value = <$target as Parse<_>>::parse_stream(stream)?;
579 Ok($name(Box::new(value)))
580 }
581 }
582
583 impl Unparse<crate::token::Atom> for $name {
584 fn unparse<S: syan::parse::unparse::Emitter<crate::token::Atom>>(
585 &self,
586 sink: &mut S,
587 ) -> Result<(), S::Error> {
588 self.0.unparse(sink)
589 }
590 }
591 )*
592 };
593}
594
595erased_leaf_v1! {
596 /// An [`ast::Expr`] behind a stream-erasing parse (see above).
597 ExprErasedV1 => ast::Expr;
598 /// An [`ast::Pattern`] behind a stream-erasing parse (see above).
599 PatErasedV1 => ast::Pattern;
600 /// An [`ast::PatBot`] behind a stream-erasing parse (see above). Kept
601 /// separate from [`PatErasedV1`] for the same reason
602 /// [`crate::cst`]'s `PatErased`/`PatBotErased` split exists: a
603 /// constructor pattern's argument is a `patbot`, not a full `patas`.
604 PatBotErasedV1 => ast::PatBot;
605 /// An [`ast::TypeExpr`] behind a stream-erasing parse (see above).
606 TyErasedV1 => ast::TypeExpr;
607 /// An [`ast::MathElemCst`] behind a stream-erasing parse (see above).
608 MathErasedV1 => ast::MathElemCst;
609 /// An [`ast::ModExpr`] behind a stream-erasing parse. Carries the
610 /// OUTSIDE→INSIDE edge `Bind::Module.body → ModExpr` — the one edge of
611 /// the `Bind → ModExpr → StructBindV1 → Bind` runtime cycle not already
612 /// erased by the connector (the erasers are for the INSIDE types, not
613 /// for `Bind`).
614 ModExprErasedV1 => ast::ModExpr;
615 /// An [`ast::SigExpr`] behind a stream-erasing parse. Used by
616 /// [`SigAnnotV1`] and [`Bind::Signature`] (outside → the SigExpr root).
617 SigExprErasedV1 => ast::SigExpr;
618 /// A [`TypeBindsV1`] behind a stream-erasing parse. Unlike every other
619 /// eraser this one targets an OUTSIDE type: `SigExpr::WithType` /
620 /// `Decl::Type` (inside) must reach `bind_type`, whose
621 /// `TypeBindSingleV1` re-enters the module through plain-derived
622 /// `ast::TypeExpr` fields — an inside→outside-plain-derive→inside-root
623 /// chain with no precedent in `cst.rs`'s discipline. Erasing at the
624 /// boundary keeps the re-entry cheap (one stream type, monomorphized
625 /// once), exactly like every other cross-boundary edge.
626 TypeBindsErasedV1 => TypeBindsV1;
627}
628
629impl ast::Pattern {
630 /// Whether this pattern is a lone variable — the 0.1 twin of
631 /// [`crate::cst::ast::Pattern::is_bare_var`], over this module's
632 /// identically-shaped pattern types.
633 pub fn is_bare_var(&self) -> bool {
634 self.as_clause.is_none()
635 && self.head.tail.is_empty()
636 && matches!(self.head.head, ast::PatBot::Var(_))
637 }
638}
639
640/// An [`ast::Pattern`] that is **not** a bare variable — upstream 0.1's own
641/// `pattern_non_var` (`parser_v1.mly:796`), and the 0.1 twin of
642/// [`crate::cst::PatNonVarErased`], whose doc comment carries the whole
643/// story.
644///
645/// 0.1 blows up the same way and slightly harder: over a chain of
646/// `let vN = N in` ending in a broken `let`, 5,755 serves at 3, 46,971 at 6,
647/// 376,699 at 9, 3,014,523 at 12, 24,117,115 at 15 — the same ×2.000 per
648/// `let`, off a larger constant. [`Expr::LetIn`] subsumes every
649/// bare-variable target here too (its `params` is greedy but no
650/// [`Param`](ast::Param) begins with `=`), so refusing one costs nothing and
651/// makes the two `let` alternatives disjoint.
652#[implement(newer_type_std::ops::Deref)]
653#[derive(Debug, Clone, PartialEq)]
654pub struct PatNonVarErasedV1(pub Box<ast::Pattern>);
655
656impl Parse<crate::token::Atom> for PatNonVarErasedV1 {
657 type Error = syan::error::ParseError<crate::span::Span>;
658
659 fn parse_stream<S: syan::parse::ParseStream<Atom = crate::token::Atom>>(
660 stream: &mut S,
661 ) -> Result<Self, Self::Error> {
662 let value = <ast::Pattern as Parse<_>>::parse_stream(stream)?;
663 if value.is_bare_var() {
664 // See `cst::PatNonVarErased` — same guard, same reasoning.
665 let ast::PatBot::Var(v) = &value.head.head else {
666 unreachable!("a bare-variable pattern has a variable head")
667 };
668 return Err(syan::error::ParseError::expected(
669 v.span,
670 "a destructuring pattern (a plain `let x = …` is not one)",
671 ));
672 }
673 Ok(PatNonVarErasedV1(Box::new(value)))
674 }
675}
676
677impl Unparse<crate::token::Atom> for PatNonVarErasedV1 {
678 fn unparse<S: syan::parse::unparse::Emitter<crate::token::Atom>>(
679 &self,
680 sink: &mut S,
681 ) -> Result<(), S::Error> {
682 self.0.unparse(sink)
683 }
684}
685
686/// The recursive expression/pattern/type/text grammar for SATySFi 0.1.
687/// A copy of [`crate::cst::ast`] with the deltas documented on each
688/// type; see the module doc comment for the SCC/root story.
689#[syan::parse::recurse]
690pub mod ast {
691 use crate::leaf::*;
692 use syan::parse::{Parse, Unparse};
693
694 /// One `param_unit` (`parser_v1.mly:635-646`): an optional `?(l = x, …)`
695 /// labeled-optional binder bundle, then a [`ParamBody`] (a plain
696 /// `patbot`, or a `( pat : τ )` ascribed pattern). Held inside `mod ast`
697 /// (re-exported at [`super::Param`]) so the roots that carry param lists
698 /// (`Expr::Fun`/`Expr::LetIn`/`RecClauseV1`) reference it without a
699 /// boundary Parse-trait cycle. A bare `patbot` param parses `Param {
700 /// opts: None, body: ParamBody::Pat(_) }` directly — the `?`-headed
701 /// `opts` `Option` is tried first, failing on a non-`?` head with no
702 /// token stolen, so an all-plain param list parses unchanged.
703 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
704 pub struct Param {
705 pub opts: Option<OptParamsV1>,
706 pub body: ParamBody,
707 }
708
709 /// A `param_unit`'s trailing shape (`parser_v1.mly:635-646`): either a
710 /// plain `patbot`, or a `( pattern : typ )` ascribed pattern
711 /// (`parser_v1.mly:641-645`).
712 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
713 pub enum ParamBody {
714 /// Tried FIRST: `( x : int )` fails `patbot`'s own paren body at the
715 /// `:` (a `patbot` paren group expects only more patterns/`,`/`)`)
716 /// and backtracks to `Ascribed` cleanly (ordered choice, no token
717 /// stolen).
718 Pat(PatBot),
719 /// `( pattern : typ )` — a FULL `pattern` (not `patbot`) ascribed
720 /// with a full `typ`, both via erasers (same cycle-avoidance
721 /// discipline as every other satellite in this module).
722 Ascribed {
723 paren: ParenGroup<()>,
724 #[group(self.paren)]
725 inner: AscribedInnerV1,
726 },
727 }
728
729 /// An ascribed param's group content: `pattern : typ` (`parser_v1.mly`'s
730 /// `param_unit`, `:641-645`).
731 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
732 pub struct AscribedInnerV1 {
733 pub pat: super::PatErasedV1,
734 pub colon: ColonTok,
735 pub ty: super::TyErasedV1,
736 }
737
738 /// A `?(l = x, …)` labeled-optional parameter bundle (`parser_v1.mly`'s
739 /// optional-`param_unit` head). The `?` reuses [`OptionalTypeTok`]
740 /// (SATySFi 0.1 dropped the fused `?:` sigil); the `(…)` is a paren
741 /// group of `,`-separated `label = binder` entries (non-empty enforced
742 /// at lowering).
743 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
744 pub struct OptParamsV1 {
745 pub q: OptionalTypeTok,
746 pub paren: ParenGroup<()>,
747 #[group(self.paren)]
748 pub entries: Vec<OptParamEntryV1>,
749 }
750
751 /// One `label = binder` entry of an [`OptParamsV1`] bundle (the last `,`
752 /// is optional; `=` is upstream's `EXACT_EQ`, reusing [`DefEqTok`]).
753 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
754 pub struct OptParamEntryV1 {
755 pub label: VarTok,
756 pub eq: DefEqTok,
757 pub var: VarTok,
758 pub comma: Option<CommaTok>,
759 }
760
761 /// `nxlet`-analogue: a let/if/match/lambda-headed expression, falling
762 /// through to the flattened operator chain ([`Expr::Ops`], [`OpChain`]) at the
763 /// bottom. Variant order is parse priority (ordered-choice
764 /// backtracking): every `let`-headed form is tried before the fallback
765 /// [`Expr::Overwrite`]/[`Expr::Ops`] (which may also start with a bare
766 /// variable), and `Ops` — having no distinguishing leading keyword —
767 /// must stay last.
768 ///
769 /// **0.1 deltas from [`crate::cst::ast::Expr`]:** `Match` gains a
770 /// mandatory trailing `end` (`parser_v1.mly:792`); `let-rec` becomes
771 /// `let rec … in …` (a plain `let` followed by the new [`KwRec`]
772 /// keyword) with full `and`-chained mutual recursion (see
773 /// [`Expr::LetRecIn`]); a new `LetMutableIn` form covers `let mutable x
774 /// <- init in body`; a new `LetPatternIn` form covers
775 /// `let pat = value in body` for any non-bare-variable pattern
776 /// (`parser_v1.mly:796`, `pattern_non_var`); `open` requires a leading
777 /// `let` (`parser_v1.mly:798`, `LET OPEN UPPER IN`) where 0.0.6 allows a
778 /// bare `open Name in body`; and `WhileDo`, the `Guard`/`when` match-arm
779 /// suffix, and `OpChain`'s `before` postfix are dropped entirely —
780 /// SATySFi 0.1's grammar has no `WHEN`/`WHILE`/`BEFORE` tokens at all
781 /// (confirmed by grep of `parser_v1.mly`). `Overwrite` (`name <- value`)
782 /// is kept unchanged (`parser_v1.mly:810-812`, `REVERSED_ARROW`) — it is
783 /// unrelated to the removed `before` postfix.
784 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
785 pub enum Expr {
786 /// `let rec clause (and clause)* in body` (`parser_v1.mly:794-795`
787 /// dispatching to `bind_value_rec`, `:455-458`) — full mutual
788 /// recursion.
789 LetRecIn {
790 let_kw: KwLet,
791 rec_kw: KwRec,
792 first: RecClauseV1,
793 ands: Vec<AndClauseV1>,
794 in_kw: KwIn,
795 body: Box<Expr>,
796 },
797 /// `let mutable x <- init in body` (`parser_v1.mly:794-795`
798 /// dispatching to `bind_value`'s MUTABLE arm, `:446-447`). Same
799 /// shape as [`crate::cst::ast::Expr::LetMutableIn`] minus the fused
800 /// keyword: 0.1 spells it `let mutable` (two tokens), 0.0.6
801 /// `let-mutable` (one). Disambiguated one token after `let` by the
802 /// V0_1-gated `mutable` keyword, so declared order relative to the
803 /// other `let`-headed arms is correctness-irrelevant.
804 LetMutableIn {
805 let_kw: KwLet,
806 mutable_kw: KwMutable,
807 name: VarTok,
808 arrow: OverwriteEqTok,
809 init: Box<Expr>,
810 in_kw: KwIn,
811 body: Box<Expr>,
812 },
813 /// `let name param* = value in body` (only a plain variable target
814 /// is supported here — a general pattern falls through to
815 /// [`Expr::LetPatternIn`]). `name` is a [`super::BindName`] —
816 /// upstream's expression-level `let` reaches the
817 /// same `bind_value_nonrec` (`:794-795` → `:459-465`) `val`/`val
818 /// rec` do, so `let (+++) a b = … in` is valid 0.1 here too.
819 LetIn {
820 kw: KwLet,
821 name: super::BindName,
822 params: Vec<Param>,
823 eq: DefEqTok,
824 value: Box<Expr>,
825 in_kw: KwIn,
826 body: Box<Expr>,
827 },
828 /// `let pat = value in body` (`parser_v1.mly:796`,
829 /// `pattern_non_var`) — any pattern shape EXCEPT a bare variable,
830 /// which [`Expr::LetIn`] already covers. The exclusion is upstream's
831 /// (the nonterminal is literally named `pattern_non_var`) and is
832 /// carried by the field's type, [`super::PatNonVarErasedV1`], rather
833 /// than by variant order: leaving the two alternatives overlapping
834 /// made a failure inside `body` cost ×2 per enclosing `let`.
835 LetPatternIn {
836 kw: KwLet,
837 pat: super::PatNonVarErasedV1,
838 eq: DefEqTok,
839 value: Box<Expr>,
840 in_kw: KwIn,
841 body: Box<Expr>,
842 },
843 /// `let open Name in body` (`parser_v1.mly:798`; unlike 0.0.6's
844 /// bare `open Name in body`, 0.1 requires the leading `let`).
845 OpenIn {
846 let_kw: KwLet,
847 open_kw: KwOpen,
848 name: CtorTok,
849 in_kw: KwIn,
850 body: Box<Expr>,
851 },
852 /// `if cond then a else b` (`else` is never optional, so there is
853 /// no dangling-else ambiguity).
854 If {
855 kw: KwIf,
856 cond: Box<Expr>,
857 then_kw: KwThen,
858 then_branch: Box<Expr>,
859 else_kw: KwElse,
860 else_branch: Box<Expr>,
861 },
862 /// `fun x y -> body`. Each parameter is a full `patbot` (through
863 /// `Param`), not a bare variable: upstream `parser_v1.mly:849-863`'s
864 /// `fun` genuinely binds a `patbot` per parameter (`ELambda(patbot,
865 /// e)` in `types.cppo.ml`), so `fun _ -> …` (wildcard) and `fun (a,
866 /// b) -> …` (tuple-destructuring) are legal upstream syntax (gaps
867 /// 2+3 of the V0_1-only language-completeness sweep). Reaching
868 /// `PatBot` from here is the same cross-root DAG edge
869 /// [`RecClauseV1::params`] already makes (both `Expr` and `PatBot`
870 /// are roots inside this `#[recurse]` module — see the module doc
871 /// comment), so no new SCC edge.
872 Fun {
873 kw: KwFun,
874 params: Vec<Param>,
875 arrow: ArrowTok,
876 body: Box<Expr>,
877 },
878 /// `match scrutinee with [|] pat -> body (| pat -> body)* end`
879 /// (`parser_v1.mly:792`). Mandatorily closed with `end`
880 /// (`tokR=END`); no `when` guards (0.1 has no `WHEN` token).
881 Match {
882 kw: KwMatch,
883 scrutinee: Box<Expr>,
884 with_kw: KwWith,
885 leading_bar: Option<BarTok>,
886 first: MatchArm,
887 rest: Vec<BarArm>,
888 end_kw: KwEnd,
889 },
890 /// `name <- value` (`expr_overwrite`, `parser_v1.mly:810-812`,
891 /// `REVERSED_ARROW`). Starts with a bare [`VarTok`], which is also
892 /// how [`Expr::Ops`] can start — must stay before `Ops` so
893 /// backtracking tries the `<-` shape first.
894 Overwrite {
895 name: VarTok,
896 arrow: OverwriteEqTok,
897 value: super::ExprErasedV1,
898 },
899 /// The flattened binary-operator chain — see
900 /// [`crate::cst::ast::Expr`]'s module doc comment on precedence
901 /// flattening (unchanged approach here). Must stay last (no leading
902 /// keyword).
903 Ops(OpChain),
904 }
905
906 /// One `name param* = value` clause of a `val rec`/`let rec` group —
907 /// upstream `bind_value_nonrec` (`parser_v1.mly:459-465`) as reached
908 /// from `bind_value_rec` (`:455-458`). **0.1 deltas from
909 /// [`crate::cst::ast::RecBinding`]:** no `: ty` ascription and no
910 /// multi-clause `| patbot* = value` sugar exist in 0.1 at all
911 /// (`bind_value_nonrec` has neither a `COLON ty` nor a `BAR`
912 /// alternative — 0.0.6's `recdecargpart` machinery has no 0.1
913 /// counterpart), so there are no `ascription`/`leading_bar`/`extra`
914 /// fields to mirror. `params` is upstream's `list(param_unit)` (see
915 /// [`super::Param`]'s doc comment), reaching `PatBot` through the same
916 /// cross-root DAG edge `cst::ast::RecBinding.params` makes
917 /// (`cst.rs:753-762`); `value` goes through [`super::ExprErasedV1`] (not
918 /// `Box<Expr>`) so this struct never joins `Expr`'s SCC — byte-for-byte
919 /// `cst.rs`'s own `RecBinding.value: ExprErased` discipline.
920 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
921 pub struct RecClauseV1 {
922 pub name: super::BindName,
923 pub params: Vec<Param>,
924 pub eq: DefEqTok,
925 pub value: super::ExprErasedV1,
926 }
927
928 /// An `and name param* = value` continuation of a `val rec`/`let rec`
929 /// group (`bind_value_rec`'s `separated_nonempty_list(AND, …)`,
930 /// `parser_v1.mly:455-458`). `and` lexes as `Token::LetAnd` → [`KwAnd`]
931 /// in both generations (`lexer.rs:141`), so no new token is needed.
932 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
933 pub struct AndClauseV1 {
934 pub and_kw: KwAnd,
935 pub clause: RecClauseV1,
936 }
937
938 /// One `pat -> body` match arm (`parser_v1.mly:959`). Unlike
939 /// [`crate::cst::ast::MatchArm`], has no `when` guard — 0.1's grammar
940 /// has none.
941 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
942 pub struct MatchArm {
943 pub pat: super::PatErasedV1,
944 pub arrow: ArrowTok,
945 pub body: super::ExprErasedV1,
946 }
947
948 /// A `| pat -> body` continuation of a match's arm list.
949 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
950 pub struct BarArm {
951 pub bar: BarTok,
952 pub arm: MatchArm,
953 }
954
955 /// A flattened binary-operator chain: `head (op rhs)*`, left-folded
956 /// (with correct per-operator precedence/associativity) during
957 /// elaboration — see [`crate::cst::ast::OpChain`]'s doc comment.
958 /// **Delta:** no `before` postfix field — 0.1 has no `BEFORE` token at
959 /// all (confirmed by grep of `parser_v1.mly`), so
960 /// [`crate::cst::ast::OpChain::before`] simply has no 0.1 counterpart.
961 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
962 pub struct OpChain {
963 pub head: AppExpr,
964 pub tail: Vec<OpRhs>,
965 }
966
967 /// One `op rhs` continuation of an [`OpChain`].
968 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
969 pub struct OpRhs {
970 pub op: BinOpTok,
971 pub rhs: AppExpr,
972 }
973
974 /// `nxun`/`nxapp`/`nxunsub`-analogue flattened: an optional leading
975 /// unary minus, an optional leading `!`/`!!`/... deref, an atomic head
976 /// with any `#label` field accesses, and an application-chain tail —
977 /// structurally identical to [`crate::cst::ast::AppExpr`] (`expr_app`,
978 /// `parser_v1.mly:849-863`, no 0.1 delta).
979 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
980 pub struct AppExpr {
981 pub minus: Option<ExactMinusTok>,
982 pub stage: Option<StagePrefix>,
983 pub excl: Option<UnopExclamTok>,
984 pub head: Atomic,
985 pub head_accesses: Vec<AccessSeg>,
986 pub args: Vec<AppArg>,
987 }
988
989 /// A staging prefix on a 0.1 operand: `&e` builds code for the next
990 /// stage, `~e` splices the result of a previous-stage computation
991 /// (`expr_un`, `parser_v1.mly:870-873`, `UTNext`/`UTPrev` — the same two
992 /// productions 0.0.6 has, unchanged).
993 ///
994 /// A fork of [`crate::cst::ast::StagePrefix`], not a re-export: it lives
995 /// inside 0.0.6's `#[recurse]` module, and this module's whole discipline
996 /// is that touching `cst_v1.rs` never touches `cst.rs` (module doc
997 /// comment). The two are token-identical, so `rustyfi-lang`'s
998 /// `v1::lower` maps one to the other by moving tokens.
999 ///
1000 /// Upstream puts these on `expr_un`, one level BELOW `expr_app`, so
1001 /// `&f x` is `(&f) x` and never `&(f x)`. This grammar flattens
1002 /// `expr_un`/`expr_app` into one node, so the prefix is an optional field
1003 /// on the *head* ([`AppExpr`]) and on each *argument* ([`AppArg`])
1004 /// independently — which reproduces exactly that reading.
1005 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1006 pub enum StagePrefix {
1007 /// `&e` — quote: the value is `e`'s code, to run one stage later.
1008 Next(ExactAmpTok),
1009 /// `~e` — splice: run `e` now and drop its code in here.
1010 Prev(ExactTildeTok),
1011 }
1012
1013 /// One `#label` field-access segment (`expr_bot ACCESS`,
1014 /// `parser_v1.mly:878`).
1015 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1016 pub struct AccessSeg {
1017 pub hash: AccessTok,
1018 pub label: VarTok,
1019 }
1020
1021 /// One application-chain argument. **0.1 delta:** the 0.0.6 `?:`/`?*`
1022 /// (`Optional`/`Omission`) forms are gone — SATySFi 0.1 dropped the fused
1023 /// `?:` sigil (`?:`/`?*` now lex as `?` + `:`/`*`, a downstream parse
1024 /// error), replaced by the labeled `?(l = e, …)` bundle
1025 /// ([`AppArg::Bundled`]).
1026 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1027 pub enum AppArg {
1028 /// `?(l = e, …) atom` — a labeled-optional bundle paired with the
1029 /// positional argument it precedes (pairing them rejects a dangling
1030 /// trailing bundle at parse time). `?(`-headed, token-disjoint from
1031 /// the `Atom`/`Ctor` arms.
1032 Bundled {
1033 opts: OptArgsV1,
1034 excl: Option<UnopExclamTok>,
1035 atom: Atomic,
1036 accesses: Vec<AccessSeg>,
1037 },
1038 /// `?(l = e, …) Ctor` — as [`AppArg::Bundled`] but the positional
1039 /// argument is a bare constructor.
1040 BundledCtor { opts: OptArgsV1, ctor: CtorTok },
1041 Atom {
1042 stage: Option<StagePrefix>,
1043 excl: Option<UnopExclamTok>,
1044 atom: Atomic,
1045 accesses: Vec<AccessSeg>,
1046 },
1047 Ctor(CtorTok),
1048 }
1049
1050 /// A `?(l = e, …)` labeled-optional application bundle: the `?` sigil
1051 /// (reusing [`OptionalTypeTok`]), then a paren group of `,`-separated
1052 /// `label = expr` entries (non-empty enforced at lowering).
1053 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1054 pub struct OptArgsV1 {
1055 pub q: OptionalTypeTok,
1056 pub paren: ParenGroup<()>,
1057 #[group(self.paren)]
1058 pub entries: Vec<OptArgEntryV1>,
1059 }
1060
1061 /// One `label = expr` entry of an [`OptArgsV1`] bundle — a FULL
1062 /// expression (`?(bias = 1 + n)`), routed through [`super::ExprErasedV1`]
1063 /// so this satellite never joins `Expr`'s SCC.
1064 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1065 pub struct OptArgEntryV1 {
1066 pub label: VarTok,
1067 pub eq: DefEqTok,
1068 pub value: super::ExprErasedV1,
1069 pub comma: Option<CommaTok>,
1070 }
1071
1072 /// `expr_bot`-analogue: an atomic expression. **Delta:** [`Atomic::List`]
1073 /// and [`Atomic::Record`] are now `,`-separated
1074 /// (`optterm_list(COMMA, …)`, `parser_v1.mly:935,942`) rather than
1075 /// 0.0.6's `;`-separated forms — see [`ListItem`]/[`RecordField`].
1076 /// Parenthesized/tuple bodies were already `,`-separated in 0.0.6 and
1077 /// are unchanged (`parser_v1.mly:914`).
1078 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1079 pub enum Atomic {
1080 Length(LengthTok),
1081 Float(FloatTok),
1082 Int(IntTok),
1083 Literal(LiteralTok),
1084 True(KwTrue),
1085 False(KwFalse),
1086 /// A bare constructor, e.g. `None`, or the head of `Some 1`.
1087 Ctor(CtorTok),
1088 Var(VarTok),
1089 /// `Mod.x` — a module-qualified variable.
1090 VarWithMod(VarWithModTok),
1091 /// `command \cmd` (upstream `parser_v1.mly:906`, `L_PAREN COMMAND
1092 /// backslash_cmd R_PAREN` — the parens arrive via [`Atomic::Paren`]
1093 /// here, exactly like [`crate::cst::ast::Atomic::Command`], which
1094 /// this reproduces verbatim; the `plus_cmd` alternative at :908 is
1095 /// deferred with the same rationale as the 0.0.6 comment). Needed by
1096 /// the transliterated `v01-mini.satyh`'s `(command \math)`.
1097 Command { kw: CommandTok, name: AnyHorzCmdTok },
1098 /// `()`
1099 Unit { paren: UnitParen },
1100 /// `( expr )` or `( expr, expr, … )` (the latter elaborates to a
1101 /// tuple).
1102 Paren {
1103 paren: ParenGroup<()>,
1104 #[group(self.paren)]
1105 inner: Box<ParenBody>,
1106 },
1107 /// `(| label = expr, … |)` or `(| base with label = expr, … |)`
1108 /// (`,`-separated — see [`RecordBody`]).
1109 Record {
1110 rec: RecordGroup<()>,
1111 #[group(self.rec)]
1112 body: RecordBody,
1113 },
1114 /// `[ expr, … ]` (`,`-separated — see [`ListItem`]).
1115 List {
1116 list: ListGroup<()>,
1117 #[group(self.list)]
1118 items: Vec<ListItem>,
1119 },
1120 /// `{ inline text }`
1121 InlineText {
1122 igrp: InlineGroup<()>,
1123 #[group(self.igrp)]
1124 elems: Vec<InlineElem>,
1125 },
1126 /// `'< block text >`
1127 BlockText {
1128 bgrp: BlockGroup<()>,
1129 #[group(self.bgrp)]
1130 elems: Vec<BlockElem>,
1131 },
1132 /// `${ math }`. Parses the same math grammar as 0.0.6; the
1133 /// `math-text`/`math-boxes` value split is a lowering/typing
1134 /// concern, not a cst_v1 shape change.
1135 MathText {
1136 mgrp: MathGroup<()>,
1137 #[group(self.mgrp)]
1138 elems: Vec<super::MathErasedV1>,
1139 },
1140 }
1141
1142 /// `(| … |)`'s content: either a plain field list, or a *record
1143 /// update* `base with l = e, …` (`parser_v1.mly:942-957`). `Update` is
1144 /// tried first (backtracks cleanly to `Fields`, same rationale as
1145 /// [`crate::cst::ast::RecordBody`]).
1146 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1147 pub enum RecordBody {
1148 Update {
1149 base: super::ExprErasedV1,
1150 with_kw: KwWith,
1151 fields: Vec<RecordField>,
1152 },
1153 Fields(Vec<RecordField>),
1154 }
1155
1156 /// The parenthesized-expression group's content: one expression, plus
1157 /// any `, expr` continuations (present only for a tuple) — unchanged
1158 /// from 0.0.6 (already `,`-separated, `parser_v1.mly:914`).
1159 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1160 pub struct ParenBody {
1161 pub first: super::ExprErasedV1,
1162 pub rest: Vec<CommaExpr>,
1163 }
1164
1165 /// A `, expr` continuation inside a parenthesized tuple.
1166 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1167 pub struct CommaExpr {
1168 pub comma: CommaTok,
1169 pub value: super::ExprErasedV1,
1170 }
1171
1172 /// One record field `label = expr,` (the last `,` is optional; `=` is
1173 /// upstream's `EXACT_EQ`, reusing [`DefEqTok`]). **Delta from
1174 /// [`crate::cst::ast::RecordField`]:** `,` separator, not `;`.
1175 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1176 pub struct RecordField {
1177 pub name: VarTok,
1178 pub eq: DefEqTok,
1179 pub value: super::ExprErasedV1,
1180 pub comma: Option<CommaTok>,
1181 }
1182
1183 /// One list element `expr,` (the last `,` is optional). **Delta from
1184 /// [`crate::cst::ast::ListItem`]:** `,` separator, not `;`.
1185 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1186 pub struct ListItem {
1187 pub value: super::ExprErasedV1,
1188 pub comma: Option<CommaTok>,
1189 }
1190
1191 /// One inline-text element — identical shape to
1192 /// [`crate::cst::ast::InlineElem`] (no 0.1 delta; text-mode content is
1193 /// untouched by the comma/`end` deltas).
1194 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1195 pub enum InlineElem {
1196 Char(CharTok),
1197 /// A backtick literal written inside inline text (`` `…` ``). Its own
1198 /// arm, not a `Char` run, so the elaborator can dispatch it through the
1199 /// context's code-text command — see `Token::CodeText`.
1200 CodeText(CodeTextTok),
1201 Space(SpaceTok),
1202 Break(BreakTok),
1203 /// `#var;` — embeds a program variable's value as inline content.
1204 Embed { var: VarInHorzTok, semi: EndActiveTok },
1205 /// `${ math }` — embeds math content as inline text.
1206 EmbedMath {
1207 mgrp: MathGroup<()>,
1208 #[group(self.mgrp)]
1209 elems: Vec<super::MathErasedV1>,
1210 },
1211 /// `\cmd …` (`name` also accepts the module-qualified `\Mod.cmd`
1212 /// form).
1213 Cmd { name: AnyHorzCmdTok, tail: CmdTail },
1214 /// An itemize bullet (`*`+) marker.
1215 ItemBullet(ItemTok),
1216 /// A `|` separator marker.
1217 Sep(SepTok),
1218 }
1219
1220 /// One block-text element — identical shape to
1221 /// [`crate::cst::ast::BlockElem`] (no 0.1 delta).
1222 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1223 pub enum BlockElem {
1224 /// `#var;` — embeds a program variable's value as block content.
1225 Embed { var: VarInVertTok, semi: EndActiveTok },
1226 /// `+cmd …` (`name` also accepts the module-qualified `+Mod.cmd`
1227 /// form).
1228 Cmd { name: AnyVertCmdTok, tail: CmdTail },
1229 }
1230
1231 /// A command's arguments — identical shape to
1232 /// [`crate::cst::ast::CmdTail`] — **0.1 delta:** an optional LEADING
1233 /// `?(l = e, …)` bundle. A command
1234 /// applied with an optional on its FIRST argument (`\cmd ?(l = e){arg}`,
1235 /// `+sec ?(label = t){title}<body>` — the ONLY shape the capstone census
1236 /// finds) can't ride inside `args` (an `expr_app` application chain whose
1237 /// *head* must be a bare `Atomic`, never a `?`-headed bundle — the head
1238 /// slot has no place for a leading bundle), so it is peeled off here as
1239 /// `lead_opts` and re-attached to the first argument at lowering
1240 /// (`v1::lower::lower_cmd_tail`). A bundle on a LATER argument
1241 /// (`\cmd{a} ?(l = e){b}`) still rides inside `args` as an ordinary
1242 /// [`AppArg::Bundled`]. `?(`-headed, token-disjoint from every
1243 /// `args` head shape, so a bundle-less tail parses `lead_opts: None`.
1244 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1245 pub enum CmdTail {
1246 /// `;` — no arguments.
1247 Semi(EndActiveTok),
1248 /// The argument chain, optionally prefixed by a leading `?(l = e, …)`
1249 /// bundle on the first argument.
1250 Args {
1251 lead_opts: Option<OptArgsV1>,
1252 args: super::ExprErasedV1,
1253 semi: Option<EndActiveTok>,
1254 },
1255 }
1256
1257 /// `patas`-analogue: a pattern, plus an optional `as name` binding —
1258 /// identical shape to [`crate::cst::ast::Pattern`] (no 0.1 delta).
1259 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1260 pub struct Pattern {
1261 pub head: PatCons,
1262 pub as_clause: Option<AsClause>,
1263 }
1264
1265 /// The `as name` suffix of a pattern.
1266 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1267 pub struct AsClause {
1268 pub as_kw: KwAs,
1269 pub name: VarTok,
1270 }
1271
1272 /// `pattr`-analogue: a `patbot`, followed by any number of `:: patbot`
1273 /// segments — identical shape to [`crate::cst::ast::PatCons`] (see its
1274 /// doc comment for why this is a flattened `Vec` rather than right
1275 /// recursion; no 0.1 delta).
1276 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1277 pub struct PatCons {
1278 pub head: PatBot,
1279 pub tail: Vec<ConsSeg>,
1280 }
1281
1282 /// One `:: patbot` continuation of a cons pattern.
1283 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1284 pub struct ConsSeg {
1285 pub cons: ConsTok,
1286 pub tail: PatBot,
1287 }
1288
1289 /// `patbot`, plus the constructor-pattern forms `pattr` adds —
1290 /// identical shape to [`crate::cst::ast::PatBot`], except
1291 /// [`PatBot::List`] is now `,`-separated (`parser_v1.mly:990-1015`,
1292 /// comma-sep list/tuple patterns).
1293 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1294 pub enum PatBot {
1295 /// `Ctor patbot` — a constructor applied to one argument pattern.
1296 /// This field is `PatBot`'s own self-loop (the root SCC).
1297 CtorApplied { ctor: CtorTok, arg: Box<PatBot> },
1298 /// A bare (nullary) constructor pattern.
1299 Ctor(CtorTok),
1300 Int(IntTok),
1301 True(KwTrue),
1302 False(KwFalse),
1303 Str(LiteralTok),
1304 Wild(WildcardTok),
1305 Var(VarTok),
1306 /// `()`
1307 Unit { paren: UnitParen },
1308 /// `( pat )` or `( pat, pat, … )` (the latter elaborates to a tuple
1309 /// pattern; already `,`-separated in 0.0.6, unchanged).
1310 Paren {
1311 paren: ParenGroup<()>,
1312 #[group(self.paren)]
1313 inner: Box<PatternParenBody>,
1314 },
1315 /// `[ pat, … ]` (also matches `[]`). **Delta:** `,` separator, not
1316 /// `;`.
1317 List {
1318 plist: ListGroup<()>,
1319 #[group(self.plist)]
1320 items: Vec<PatListItem>,
1321 },
1322 }
1323
1324 /// The parenthesized-pattern group's content: one pattern, plus any
1325 /// `, pat` continuations (present only for a tuple pattern).
1326 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1327 pub struct PatternParenBody {
1328 pub first: super::PatErasedV1,
1329 pub rest: Vec<CommaPattern>,
1330 }
1331
1332 /// A `, pat` continuation inside a parenthesized tuple pattern.
1333 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1334 pub struct CommaPattern {
1335 pub comma: CommaTok,
1336 pub value: super::PatErasedV1,
1337 }
1338
1339 /// One list-pattern element `pat,` (the last `,` is optional). **Delta
1340 /// from [`crate::cst::ast::PatListItem`]:** `,` separator, not `;`.
1341 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1342 pub struct PatListItem {
1343 pub value: super::PatErasedV1,
1344 pub comma: Option<CommaTok>,
1345 }
1346
1347 /// A type-expression grammar (`typ`/`typ_prod`/`typ_app`/`typ_bot`,
1348 /// `parser_v1.mly:685-752`, simplified — same scope as
1349 /// [`crate::cst::ast::TypeExpr`]). Spells products (`length * length`,
1350 /// [`TypeProd`]) and prefix type application (`list int`, [`TypeApp`]) —
1351 /// without them a `type` bind could declare almost nothing — plus the
1352 /// `?(…)` labeled-optional domain prefix
1353 /// ([`TypeExpr::OptRowFun`]), where a
1354 /// row-variable TAIL (`?(… | ?'r) ->`) parses but is rejected at
1355 /// lowering (it needs signature-level row quantification, not yet
1356 /// implemented). Self-recursive only through `Fun`'s/`OptRowFun`'s
1357 /// codomain (right recursion); parenthesized nesting goes through
1358 /// [`super::TyErasedV1`].
1359 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1360 pub enum TypeExpr {
1361 /// `?(l : ty, … [| ?'r]) dom -> cod` (`typ` `:688-693`, `typ_opt_dom`
1362 /// `:753-758`). `?`-headed — neither
1363 /// `Fun`/`Atom` (headed by `TypeProd`) can start with
1364 /// `OptionalTypeTok`, so declared order relative to them is
1365 /// safety-neutral; declared first to mirror the upstream `typ`
1366 /// production order. Lowered (`v1/lower.rs`) to
1367 /// `cst::ast::TypeExpr::OptRowFun`, thence (`typecheck.rs`) to
1368 /// `MonoType::Func(Row::Cons(l1, ty1, … Row::Empty), dom, cod)` — a
1369 /// CLOSED row, matching what `Ast::LambdaOpt` infers,
1370 /// so an explicit `?(l:τ)->` signature unifies against an actual
1371 /// `?(l=x)`-taking function.
1372 OptRowFun {
1373 opt_dom: TypeOptDomV1,
1374 dom: TypeProd,
1375 arrow: ArrowTok,
1376 cod: Box<TypeExpr>,
1377 },
1378 /// `dom -> cod` (right-associative). This field is `TypeExpr`'s own
1379 /// self-loop (the root SCC). `dom` widened from [`TypeAtom`] to
1380 /// [`TypeProd`] so e.g. `'a option -> 'b option` and
1381 /// `'a * 'b -> 'c` both parse at their expected precedence.
1382 Fun {
1383 dom: TypeProd,
1384 arrow: ArrowTok,
1385 cod: Box<TypeExpr>,
1386 },
1387 /// The non-arrow fallthrough — widened from [`TypeAtom`] to
1388 /// [`TypeProd`]: a product/application with no
1389 /// enclosing arrow is still just "the whole type expression minus
1390 /// `->`".
1391 Atom(TypeProd),
1392 }
1393
1394 /// `?(l : ty, … [| ?'r])` — the (possibly row-tailed) labeled-optional
1395 /// domain prefix of a [`TypeExpr::OptRowFun`] (`typ_opt_dom`,
1396 /// `parser_v1.mly:753-758`).
1397 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1398 pub struct TypeOptDomV1 {
1399 pub q: OptionalTypeTok,
1400 pub paren: ParenGroup<()>,
1401 #[group(self.paren)]
1402 pub inner: TypeOptDomInnerV1,
1403 }
1404
1405 /// A [`TypeOptDomV1`]'s group content: one or more `label : typ` entries
1406 /// (nonempty enforced at lowering), then an optional `| ?'r` row-variable
1407 /// tail (`typ_opt_dom` `:756-757`) — parsed, but rejected with a
1408 /// `LowerError` (needs signature-level row quantification, not
1409 /// implemented; contrast [`TypeRecordInnerV1`]'s own
1410 /// `row_tail`, which IS fully supported, since a bare
1411 /// record-typed value has no `quant`-list obligation to satisfy).
1412 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1413 pub struct TypeOptDomInnerV1 {
1414 pub entries: Vec<TypeOptEntryV1>,
1415 pub row_tail: Option<RowTailV1>,
1416 }
1417
1418 /// One `label : typ,` entry of a [`TypeOptDomV1`] (last `,` optional;
1419 /// `typ_opt_dom_entry`, `parser_v1.mly:759-762` — COLON, unlike the
1420 /// value-level `?(l = e)` bundle's `=`).
1421 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1422 pub struct TypeOptEntryV1 {
1423 pub label: VarTok,
1424 pub colon: ColonTok,
1425 pub ty: super::TyErasedV1,
1426 pub comma: Option<CommaTok>,
1427 }
1428
1429 /// `| ?'r` — a row-variable tail (shared by [`TypeOptDomInnerV1`] and
1430 /// [`TypeRecordInnerV1`]; `parser_v1.mly:748-749`/`:756-757`).
1431 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1432 pub struct RowTailV1 {
1433 pub bar: BarTok,
1434 pub var: RowVarTok,
1435 }
1436
1437 /// `typ_prod` (`parser_v1.mly:696-709`): one or more `*`-separated
1438 /// [`TypeApp`]s, flattened to head+`Vec` exactly like
1439 /// [`crate::cst::ast::TypeProd`] (`cst.rs:1284-1295`) — the same
1440 /// deferred-fold technique as `OpChain`/`PatCons`, keeping `TypeExpr` a
1441 /// singleton SCC.
1442 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1443 pub struct TypeProd {
1444 pub first: TypeApp,
1445 pub rest: Vec<StarType>,
1446 }
1447
1448 /// A `* ty` continuation of a [`TypeProd`].
1449 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1450 pub struct StarType {
1451 pub star: ExactTimesTok,
1452 pub ty: TypeApp,
1453 }
1454
1455 /// `typ_app` (`parser_v1.mly:711-739`). **0.1 delta from
1456 /// [`crate::cst::ast::TypeApp`] (`cst.rs:1297-1312`):** application is
1457 /// PREFIX and n-ary (`list int`, `pair int bool`), not 0.0.6's postfix
1458 /// single-argument (`int list`) — the prefix→postfix bridge (with an
1459 /// arity-1 guard: arity ≥ 2 is a `LowerError`, not a parse error) lives
1460 /// in `v1/lower.rs`. `Applied`/`AppliedLong` (needing at least one
1461 /// argument atom) are tried before `Atom` — a bare name has no argument
1462 /// atom to consume and falls through cleanly (a following
1463 /// keyword/`=`/`and`/`->`/`*` never parses as a [`TypeAtom`]).
1464 ///
1465 /// Four further arms are keyword- or token-headed and so
1466 /// disjoint from `Applied`/`Atom`'s `VarTok`-headed shapes (ordering them
1467 /// BEFORE those is cosmetic, not load-bearing):
1468 ///
1469 /// - [`TypeApp::InlineCmdTy`]/[`TypeApp::BlockCmdTy`]/
1470 /// [`TypeApp::MathCmdTy`]: `inline [τ, …]`/
1471 /// `block [τ, …]`/`math [τ, …]` command types (`parser.mly:730-735`,
1472 /// `typ_cmd_arg` `:763-774`; `math […]`: `parser.mly:830-831`),
1473 /// `KwInline`/`KwBlock`/`KwMath`-headed (all three are V0_1 keywords
1474 /// already — `val inline`/`val block` binds; `math` since the
1475 /// math-split). One deliberate superset of upstream remains: each
1476 /// bracketed slot is a full [`super::TyErasedV1`] (`TypeExpr`), not upstream's
1477 /// narrower `typ_prod`. The `?(label: τ, …)` optional-labeled-slot
1478 /// prefix is modeled — see [`TypeCmdArgItemV1::opts`];
1479 /// `MathCmdTy` reuses `TypeCmdArgItemV1` as-is, so
1480 /// `math [?(l : τ) …]` sig rows come for free.
1481 /// - [`AppliedLong`](TypeApp::AppliedLong): `M.t τ…` — the `LONG_LOWER`
1482 /// qualified-head twin of `Applied` (`parser.mly:720-728`,
1483 /// `LONG_LOWER` `lexer.mll:318`), `VarWithModTok`-headed (lexed by the
1484 /// program-mode capital-head scan, `lexer.rs:753-777`). Needed to NAME
1485 /// an abstract type from outside its sealing module — without
1486 /// it, an opaque `M.t` could never appear in another module's
1487 /// signature at all.
1488 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1489 pub enum TypeApp {
1490 /// `inline [τ, …]` — see the enum doc comment.
1491 //
1492 // NOTE the group fields are `ilist`/`blist`/`mlist`, not three `list`s:
1493 // syan names a group substruct after (group-field name, ENUM name) with
1494 // no variant component, so same-named groups in one enum collide
1495 // (E0428 + E0119).
1496 InlineCmdTy {
1497 kw: KwInline,
1498 ilist: ListGroup<()>,
1499 #[group(self.ilist)]
1500 args: Vec<TypeCmdArgItemV1>,
1501 },
1502 /// `block [τ, …]` — see the enum doc comment.
1503 BlockCmdTy {
1504 kw: KwBlock,
1505 blist: ListGroup<()>,
1506 #[group(self.blist)]
1507 args: Vec<TypeCmdArgItemV1>,
1508 },
1509 /// `math [τ, …]` (upstream
1510 /// `parser.mly:830-831` `MATH L_SQUARE optterm_list(COMMA,
1511 /// typ_cmd_arg) R_SQUARE → MMathCommandType(mncmdargtys)` — same
1512 /// `typ_cmd_arg` as inline/block). `KwMath`-headed, so this arm is
1513 /// disjoint from `Applied`/`Atom` and ambiguity-free: a bare `math`
1514 /// can never lex as a `VarTok` under V0_1 at all.
1515 MathCmdTy {
1516 kw: KwMath,
1517 mlist: ListGroup<()>,
1518 #[group(self.mlist)]
1519 args: Vec<TypeCmdArgItemV1>,
1520 },
1521 /// `M.t τ…` — see the enum doc comment. Mirrors
1522 /// `Applied`'s n-ary shape (`v1/lower.rs`'s prefix→postfix bridge
1523 /// rejects arity ≥ 2 identically for both).
1524 AppliedLong {
1525 ctor: VarWithModTok,
1526 first: TypeAtom,
1527 rest: Vec<TypeAtom>,
1528 },
1529 Applied {
1530 ctor: VarTok,
1531 first: TypeAtom,
1532 rest: Vec<TypeAtom>,
1533 },
1534 Atom(TypeAtom),
1535 }
1536
1537 /// One `[…]`-bracketed command-type argument slot: an optional
1538 /// `?(l : τ, …)` labeled-optional bundle PREFIX (upstream
1539 /// `typ_cmd_arg : option(typ_opt_dom) typ_prod`,
1540 /// `parser.mly:753-773`), then the mandatory `τ,` (`,`-separated, last
1541 /// `,` optional — the [`ListItem`] pattern). A full [`super::TyErasedV1`] per
1542 /// slot (permissive superset of upstream's narrower `typ_prod`). `opts`
1543 /// is `Option`-tried first: a non-`?`-headed slot fails the `?` head with
1544 /// no token stolen, so a plain slot parses `opts: None`.
1545 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1546 pub struct TypeCmdArgItemV1 {
1547 pub opts: Option<TypeCmdOptDomV1>,
1548 pub ty: super::TyErasedV1,
1549 pub comma: Option<CommaTok>,
1550 }
1551
1552 /// `?(l : τ, …)` — a CLOSED command-type optional bundle (upstream
1553 /// `typ_opt_dom`, `parser.mly:755-761`,
1554 /// minus the `| ?'r` row-variable tail: command optional-argument types
1555 /// are closed maps, never rows — upstream itself silently DISCARDS a
1556 /// written row variable here, `parser.mly:859-869`'s literal `TODO
1557 /// (error)` — so this port doesn't model one either; a stray `?'r` inside
1558 /// a command-type bracket is a parse error, faithfully matching
1559 /// upstream's "never actually usable" treatment of it). Mirrors
1560 /// [`crate::cst::ast::CstTypeOptDom`]-shaped satellites elsewhere in this file.
1561 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1562 pub struct TypeCmdOptDomV1 {
1563 pub q: OptionalTypeTok,
1564 pub paren: ParenGroup<()>,
1565 #[group(self.paren)]
1566 pub entries: Vec<TypeCmdOptEntryV1>,
1567 }
1568
1569 /// One `label : τ,` entry of a [`TypeCmdOptDomV1`] bundle (the last `,`
1570 /// is optional).
1571 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1572 pub struct TypeCmdOptEntryV1 {
1573 pub label: VarTok,
1574 pub colon: ColonTok,
1575 pub ty: super::TyErasedV1,
1576 pub comma: Option<CommaTok>,
1577 }
1578
1579 /// An atomic type expression. `parser_v1.mly:740-752`'s record forms are
1580 /// fully modeled: both the closed form and the open (row-var-tailed) form
1581 /// share [`TypeAtom::Record`], distinguished by
1582 /// [`TypeRecordInnerV1::row_tail`].
1583 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1584 pub enum TypeAtom {
1585 /// `( ty )`
1586 Paren {
1587 paren: ParenGroup<()>,
1588 #[group(self.paren)]
1589 inner: super::TyErasedV1,
1590 },
1591 /// `(| l1 : ty1, l2 : ty2, … |)` (closed) or `(| l1 : ty1, … | ?'r |)`
1592 /// (open — a row-variable tail)
1593 /// (`typ_bot`'s two `L_RECORD` arms, `parser_v1.mly:746-749`;
1594 /// `typ_record_elem` `:775-777` — COLON fields, unlike record
1595 /// EXPRESSIONS' `l = e`). Lowered (`v1/lower.rs`): the closed form to
1596 /// the existing `cst::ast::TypeAtom::Record` (`cst.rs:1344`) and
1597 /// thence to a closed `MonoType::Record` row (`typecheck.rs:512`);
1598 /// the open form to the additive `cst::ast::TypeAtom::RecordOpen`
1599 /// and thence to an OPEN `MonoType::Record(Row::Var(…))` — a fresh
1600 /// row variable, using the existing generic `Row`/`RowVarRef`/
1601 /// `unify_row` machinery (no new type machinery needed).
1602 Record {
1603 rec: RecordGroup<()>,
1604 #[group(self.rec)]
1605 inner: TypeRecordInnerV1,
1606 },
1607 /// A type variable, e.g. `'a`.
1608 Var(TypeVarTok),
1609 /// `M.t` — a qualified type name (upstream
1610 /// `LONG_LOWER`, `parser.mly:742-743`). `VarWithModTok`-headed,
1611 /// token-disjoint from `Var`/`Name`/`Paren` (`TypeVarTok`/`VarTok`/
1612 /// `LParenTok`) — see [`TypeApp::AppliedLong`]'s doc comment.
1613 LongName(VarWithModTok),
1614 /// A (possibly qualified) type name, e.g. `int`, `string`.
1615 Name(VarTok),
1616 }
1617
1618 /// A [`TypeAtom::Record`]'s group content: the field list, plus an
1619 /// optional `| ?'r` row-variable tail (present ⇒ an OPEN record type;
1620 /// absent ⇒ closed).
1621 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1622 pub struct TypeRecordInnerV1 {
1623 pub fields: Vec<TypeRecordFieldV1>,
1624 pub row_tail: Option<RowTailV1>,
1625 }
1626
1627 /// One `l : ty,` field (last `,` optional — the [`ListItem`] pattern).
1628 /// **Deltas from [`crate::cst::ast::TypeRecordField`] (`cst.rs:1363`):**
1629 /// `,` separator, not `;` (same delta as [`RecordField`]); field type is
1630 /// a full [`super::TyErasedV1`] (upstream `typ_record_elem :776` takes a
1631 /// full `typ`) — erased, not a direct `TypeExpr`, for the same
1632 /// cycle-avoidance reason `cst.rs:1355-1362` documents.
1633 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1634 pub struct TypeRecordFieldV1 {
1635 pub name: VarTok,
1636 pub colon: ColonTok,
1637 pub ty: super::TyErasedV1,
1638 pub comma: Option<CommaTok>,
1639 }
1640
1641 /// `mathtop`-analogue: one math element — identical shape to
1642 /// [`crate::cst::ast::MathElemCst`] (no 0.1 delta; see its doc comment
1643 /// for why this needs no direct self-loop of its own).
1644 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1645 pub struct MathElemCst {
1646 pub base: MathBot,
1647 pub scripts: Vec<MathScript>,
1648 }
1649
1650 /// `mathbot` — identical shape to [`crate::cst::ast::MathBot`] (no 0.1
1651 /// delta; `name` accepts a module-qualified `\Mod.cmd` math command
1652 /// too, `AnyMathCmdTok::Mod` — the lexer already emits
1653 /// `Token::MathCmdWithMod` for one, `lexer.rs`'s `\\` arm in `Mode::
1654 /// Math`, since `${\Math.paren{…}}`-shaped
1655 /// qualified references need it to parse at all).
1656 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1657 pub enum MathBot {
1658 /// `\cmd matharg*`, sigil-only or module-qualified (`\Mod.cmd
1659 /// matharg*`).
1660 Cmd { name: AnyMathCmdTok, args: Vec<MathArg> },
1661 Chars(MathCharTok),
1662 /// `#var` (math mode never trails this with `;`).
1663 Embed(VarInMathTok),
1664 /// A `|` separator marker (flat; elaborator regroups).
1665 Sep(SepTok),
1666 /// `{ … }` — re-enters the math grammar.
1667 Group {
1668 mgrp: MathGroup<()>,
1669 #[group(self.mgrp)]
1670 elems: Vec<super::MathErasedV1>,
1671 },
1672 }
1673
1674 /// One postfix script combo of a [`MathElemCst`] — identical shape to
1675 /// [`crate::cst::ast::MathScript`] (no 0.1 delta).
1676 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1677 pub enum MathScript {
1678 /// `^ group`
1679 Super { hat: SuperscriptTok, group: MathGroupArg },
1680 /// `_ group`
1681 Sub { under: SubscriptTok, group: MathGroupArg },
1682 /// A run of `'` marks — sugar for a superscript of primes
1683 /// characters.
1684 Primes(PrimesTok),
1685 }
1686
1687 /// `mathgroup`-analogue: a script's operand is either a bracketed math
1688 /// group or a bare `mathbot`.
1689 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1690 pub enum MathGroupArg {
1691 Group {
1692 mgrp: MathGroup<()>,
1693 #[group(self.mgrp)]
1694 elems: Vec<super::MathErasedV1>,
1695 },
1696 Bot(Box<MathBot>),
1697 }
1698
1699 /// `matharg`-analogue: one command argument in math mode — identical
1700 /// shape to [`crate::cst::ast::MathArg`] (no 0.1 delta; the escape
1701 /// bodies reuse the now-comma-separated [`ParenBody`]/[`ListItem`]/
1702 /// [`RecordBody`] defined above).
1703 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1704 pub enum MathArg {
1705 /// `{ math }`.
1706 Math {
1707 mgrp: MathGroup<()>,
1708 #[group(self.mgrp)]
1709 elems: Vec<super::MathErasedV1>,
1710 },
1711 /// `!{ inline text }`.
1712 Inline {
1713 igrp: InlineGroup<()>,
1714 #[group(self.igrp)]
1715 elems: Vec<InlineElem>,
1716 },
1717 /// `!<block text>`.
1718 Block {
1719 bgrp: BlockGroup<()>,
1720 #[group(self.bgrp)]
1721 elems: Vec<BlockElem>,
1722 },
1723 /// `!(e)` / `!(e, e, …)`.
1724 ParenEscape {
1725 paren: ParenGroup<()>,
1726 #[group(self.paren)]
1727 inner: Box<ParenBody>,
1728 },
1729 /// `![e, …]`.
1730 ListEscape {
1731 list: ListGroup<()>,
1732 #[group(self.list)]
1733 items: Vec<ListItem>,
1734 },
1735 /// `!(|l = e, …|)`.
1736 RecordEscape {
1737 rec: RecordGroup<()>,
1738 #[group(self.rec)]
1739 body: RecordBody,
1740 },
1741 }
1742
1743 // ---- the module/signature layer --------------------------------------
1744
1745 /// `mod_chain`: `UPPER | LONG_UPPER` (`parser_v1.mly:404-414`). `M.N.P`
1746 /// arrives as ONE [`LongUpperTok`] (the V0_1 lexer branch), so a chain is
1747 /// always exactly one token — which is what makes [`ModExpr::App`]'s two-
1748 /// chain juxtaposition (`F X`, `F.G X.Y`) unambiguous at the token level.
1749 /// Token-disjoint arms; order is cosmetic.
1750 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1751 pub enum ModChainV1 {
1752 Long(LongUpperTok),
1753 Single(CtorTok),
1754 }
1755
1756 /// `modexpr` (`parser_v1.mly:380-403`). SELF-LOOP ROOT: `Functor.body:
1757 /// Box<ModExpr>` (`:381-382`). Variant order is parse priority:
1758 /// `Functor` (`fun`-headed) and `Struct` (`struct`-headed) are
1759 /// keyword-disjoint from everything; `Coerce` (`UPPER :>`) must precede
1760 /// `App`/`Var` so the `:>` suffix is claimed before a bare chain matches;
1761 /// `App` (two chains, `modexpr_app` `:388-394`) precedes `Var` (one
1762 /// chain, `modexpr_bot` `:398-400`) for longest-match. Struct bodies go
1763 /// through [`super::StructBindV1`] (the struct-body connector, erased), so
1764 /// `ModExpr` never statically references [`super::Bind`] — see the
1765 /// module doc comment's SCC story.
1766 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1767 pub enum ModExpr {
1768 /// `FUN ( UPPER : sigexpr ) ARROW modexpr` (`parser_v1.mly:381-382`).
1769 Functor {
1770 fun_kw: KwFun,
1771 lp: LParenTok,
1772 param: CtorTok,
1773 colon: ColonTok,
1774 dom: Box<SigExpr>,
1775 rp: RParenTok,
1776 arrow: ArrowTok,
1777 body: Box<ModExpr>,
1778 },
1779 /// `UPPER COERCE sigexpr` (`:383-384`) — coercion applies to a BARE
1780 /// module name only, upstream-faithfully (`A.B :> S` is a parse
1781 /// error there too).
1782 Coerce {
1783 name: CtorTok,
1784 coerce: CoerceTok,
1785 sig_: Box<SigExpr>,
1786 },
1787 /// `mod_chain mod_chain` — functor application (`:389-394`).
1788 App { func: ModChainV1, arg: ModChainV1 },
1789 /// `mod_chain` — a (possibly long) module path (`:399-400`).
1790 Var(ModChainV1),
1791 /// `STRUCT list(bind) END` (`:401-402`) — the only form `v1/lower.rs`
1792 /// gives real semantics to; reuses the struct-body connector.
1793 Struct {
1794 struct_kw: KwStruct,
1795 binds: Vec<super::StructBindV1>,
1796 end_kw: KwEnd,
1797 },
1798 }
1799
1800 /// `sigexpr` (`parser_v1.mly:558-573`). SELF-LOOP ROOT: `Functor.dom`/
1801 /// `Functor.cod: Box<SigExpr>` (`:570-571`).
1802 ///
1803 /// **Left-recursion note (load-bearing).** The naive sketch would write
1804 /// `With { base: Box<SigExpr>, … }` — as a syan2 ordered-choice
1805 /// production that is LEFT RECURSION (`SigExpr` would begin by parsing
1806 /// `SigExpr`; syan2 gives no diagnostic, it just recurses/fails at parse
1807 /// time — a known consumer hazard). Upstream is *not* left-recursive:
1808 /// the `with` base is `sigexpr_bot` (`:559,564`) and `with` cannot chain
1809 /// (the result of a `with` is never itself a valid `with` base). So the
1810 /// faithful encoding is bot + one optional-shaped suffix arm, tried
1811 /// before the bare-bot fallthrough: `S with type t = int with type u =
1812 /// bool` is a parse error here exactly as upstream (pinned in tests).
1813 /// NO arm of this enum may ever begin with `Box<SigExpr>`/`SigExpr` as
1814 /// its first field — reviewer checklist item.
1815 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1816 pub enum SigExpr {
1817 /// `( UPPER : sigexpr ) ARROW sigexpr` (`:570-571`) — the functor
1818 /// signature. `(`-headed; no [`SigBotV1`] starts with `(`, so this
1819 /// is token-disjoint from the other arms.
1820 Functor {
1821 lp: LParenTok,
1822 param: CtorTok,
1823 colon: ColonTok,
1824 dom: Box<SigExpr>,
1825 rp: RParenTok,
1826 arrow: ArrowTok,
1827 cod: Box<SigExpr>,
1828 },
1829 /// `sigexpr_bot WITH TYPE bind_type` (`:559-563`) /
1830 /// `sigexpr_bot WITH mod_chain TYPE bind_type` (`:564-569`). The
1831 /// `Option<ModChainV1>` is greedy-then-backtrack: on `with type` the
1832 /// chain fails (`type` is a keyword token, not `UPPER`/`LONG_UPPER`)
1833 /// and collapses to `None`. `binds` goes through
1834 /// [`super::TypeBindsErasedV1`].
1835 WithType {
1836 base: SigBotV1,
1837 with_kw: KwWith,
1838 path: Option<ModChainV1>,
1839 type_kw: KwType,
1840 binds: super::TypeBindsErasedV1,
1841 },
1842 /// A bare `sigexpr_bot` (`:572-573`). Must come after [`SigExpr::WithType`]
1843 /// (maximal munch of the `with` suffix).
1844 Bot(SigBotV1),
1845 }
1846
1847 /// `sigexpr_bot` (`parser_v1.mly:575-595`) — a satellite (no self-loop;
1848 /// no edge back to `SigExpr`). Sig bodies go through
1849 /// [`super::StructDeclV1`] (opaque hand-written connector), so
1850 /// `SigBotV1` never statically references [`Decl`].
1851 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1852 pub enum SigBotV1 {
1853 /// `LONG_UPPER` — a signature path `M.N.S` (`:581-590`).
1854 Path(LongUpperTok),
1855 /// `UPPER` — a signature name (`:576-580`).
1856 Var(CtorTok),
1857 /// `SIG list(decl) END` (`:591-595`). `sig` is a version-independent
1858 /// keyword (`lexer.rs`).
1859 Sig {
1860 sig_kw: KwSig,
1861 decls: Vec<super::StructDeclV1>,
1862 end_kw: KwEnd,
1863 },
1864 }
1865
1866 /// `decl` (`parser_v1.mly:597-621`) — one item of a `sig … end` body.
1867 /// NOT a root: no arm contains `Decl`; reached only through
1868 /// [`super::StructDeclV1`], so `SigExpr ↔ Decl` never forms a rootless
1869 /// static sub-cycle (the shape `cst.rs`'s `AppArgErased` doc warns the
1870 /// engine rejects). Its recursion-bearing edges are plain DAG edges INTO
1871 /// roots: `ty: TypeExpr` (the same satellite→root shape as
1872 /// `RecClauseV1.params: Vec<PatBot>`) and `sig_: Box<SigExpr>`.
1873 ///
1874 /// Deferred arms (parse errors): macro decls `val \m : macro-type`
1875 /// (`:608-611`), row quantifiers (`rowquant`, `:631-633` — no
1876 /// `ROWVAR` token for this position yet).
1877 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1878 pub enum Decl {
1879 /// `VAL PERSISTENT? EXACT_TILDE? bound_identifier quant COLON typ`
1880 /// (`:598-603`; `quant`'s tyvar list `:623-630` — `val map 'a 'b :
1881 /// ('a -> 'b) -> …`). The stage prefix is the decl-side twin of
1882 /// [`super::Bind::Value`]'s own `stage` field, with the same
1883 /// ordered-choice argument (see that arm's doc comment).
1884 Val {
1885 kw: KwVal,
1886 stage: Option<super::BindStageV1>,
1887 name: super::BindName,
1888 quant: Vec<TypeVarTok>,
1889 colon: ColonTok,
1890 ty: TypeExpr,
1891 },
1892 /// `VAL BACKSLASH_CMD quant COLON typ` (`:604-605`). Plain
1893 /// [`HorzCmdTok`] — upstream uses the bare token, and program mode
1894 /// already lexes `\cmd`. Naming mirrors
1895 /// [`crate::cst::SigItem::ValHorzCmd`].
1896 ValHorzCmd {
1897 kw: KwVal,
1898 cmd: HorzCmdTok,
1899 quant: Vec<TypeVarTok>,
1900 colon: ColonTok,
1901 ty: TypeExpr,
1902 },
1903 /// `VAL PLUS_CMD quant COLON typ` (`:606-607`).
1904 ValVertCmd {
1905 kw: KwVal,
1906 cmd: VertCmdTok,
1907 quant: Vec<TypeVarTok>,
1908 colon: ColonTok,
1909 ty: TypeExpr,
1910 },
1911 /// `TYPE LOWER CONS kind` — an OPAQUE type (`:612-613`). Tried
1912 /// before the transparent [`Decl::Type`]: the two share the `type
1913 /// name` prefix and are told apart by `::` vs `=`/tyvars
1914 /// (backtracking is two tokens deep, cheap).
1915 TypeOpaque {
1916 kw: KwType,
1917 name: VarTok,
1918 cons: ConsTok,
1919 kind: KindV1,
1920 },
1921 /// `TYPE bind_type` — transparent type(s) (`:614-615`), sharing the
1922 /// grouped chain with [`SigExpr::WithType`].
1923 Type {
1924 kw: KwType,
1925 binds: super::TypeBindsErasedV1,
1926 },
1927 /// `MODULE UPPER COLON sigexpr` (`:616-617`) — note `:` here (a
1928 /// decl constrains), vs `:>` on binds (a bind seals).
1929 Module {
1930 kw: KwModule,
1931 name: CtorTok,
1932 colon: ColonTok,
1933 sig_: Box<SigExpr>,
1934 },
1935 /// `SIGNATURE UPPER EXACT_EQ sigexpr` (`:618-619`).
1936 Signature {
1937 kw: KwSignature,
1938 name: CtorTok,
1939 eq: DefEqTok,
1940 sig_: Box<SigExpr>,
1941 },
1942 /// `INCLUDE sigexpr` (`:620-621`) — a decl-include includes a
1943 /// SIGNATURE (contrast [`super::Bind::Include`], which includes a
1944 /// MODULE).
1945 Include { kw: KwInclude, sig_: Box<SigExpr> },
1946 }
1947
1948 // Ordered-choice safety of `Decl`: all arms are keyword-headed
1949 // (`val`/`type`/`module`/`signature`/`include`); within `val`, the
1950 // second token (`BindName`'s `Var`-or-`LParen` vs `HorzCmdTok` vs
1951 // `VertCmdTok`) is disjoint; within `type`, `TypeOpaque`-before-`Type`
1952 // as documented.
1953
1954 /// `kind` (`parser_v1.mly:672-677`): `kind_base (ARROW kind_base)*`
1955 /// flattened head+`Vec` — the same deferred-fold shape as
1956 /// [`TypeProd`]/[`PatCons`], keeping the type acyclic. `kind_base` is a
1957 /// bare LOWER (`:678-681`, `MKindName`), so the whole kind grammar is
1958 /// token-only. (`kind_row`, `:682-683`, arrives with row quantifiers,
1959 /// not yet implemented.)
1960 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1961 pub struct KindV1 {
1962 pub first: VarTok,
1963 pub rest: Vec<KindArrowV1>,
1964 }
1965
1966 /// An `-> kind_base` continuation of a [`KindV1`].
1967 #[derive(Parse, Unparse, Debug, Clone, PartialEq)]
1968 pub struct KindArrowV1 {
1969 pub arrow: ArrowTok,
1970 pub base: VarTok,
1971 }
1972
1973 // (`Quant` needs no struct: upstream `quant = list(tyquant)
1974 // list(rowquant)` (`:623-625`), and with rowquants deferred it is
1975 // exactly `Vec<TypeVarTok>` — inlined into `Decl::Val*` above.)
1976}
1977
1978/// Lex ([`crate::lexer::lex_with_version`] under [`crate::version::RustyfiVersion::V0_1`])
1979/// and parse a whole 0.1 `.saty`/`.satyh` source file. Mirrors
1980/// [`crate::cst::parse_file`]'s two-step shape exactly, sharing its
1981/// [`crate::cst::ParseFileError`] (no new error type).
1982pub fn parse_file_v1(src: &str) -> Result<FileV1, crate::cst::ParseFileError> {
1983 let atoms = crate::lexer::lex_with_version(src, crate::version::RustyfiVersion::V0_1)
1984 .map_err(crate::cst::ParseFileError::from_lex)?;
1985 let mut stream = crate::stream::AtomStream::new(atoms);
1986 match <FileV1 as Parse<_>>::parse(&mut stream) {
1987 Ok(file) => Ok(file),
1988 // The one shared reducer, not the private copy this used to keep: a
1989 // 0.1 library is ONE top-level `module` binding, so it is the
1990 // generation that most needs the high-water mark. See
1991 // [`crate::parse_error`].
1992 Err(e) => Err(crate::parse_error::locate(src, &stream, &e)),
1993 }
1994}