rustyfi_lang/prim_types.rs
1//! Type signatures for every primitive registered in `primitives.rs`'s
2//! `prims!` table (plus the `inline-fil` constant), transcribed from
3//! v0.0.6's `tools/gencode/vminst.ml` `~type_:` fields (cited by line
4//! number at each entry below) and from `src/frontend/primitives.cppo.ml`
5//! for the handful of names vminst.ml doesn't define directly (`::`, `!`,
6//! the comparison trio derived in `general_table`).
7//!
8//! `document`, `+p` and `\emph` have no entry here: they are ordinary
9//! `stdja-mini` stdlib bindings
10//! (`lib-rustyfi/dist/packages/stdja-mini.satyh`), built out of *other*
11//! primitives below and typechecked like any other `.satyh` library.
12//!
13//! Also provides `builtin_variants_with_version`, the seed set of variant
14//! type declarations ([`VariantDecl`]) primitives.cppo.ml registers before
15//! any user code runs.
16
17use crate::types::{self, BaseType, CmdArgType, MonoType, PolyType, TyVarRef};
18use rustyfi_syntax::RustyfiVersion;
19use std::collections::HashMap;
20
21// ============================================================================
22// Constructor helpers — read the table below like vminst.ml's own `tI`,
23// `tB`, `@->`, etc.
24// ============================================================================
25
26pub(crate) fn t_unit() -> MonoType {
27 MonoType::Base(BaseType::Unit)
28}
29pub fn t_bool() -> MonoType {
30 MonoType::Base(BaseType::Bool)
31}
32pub fn t_int() -> MonoType {
33 MonoType::Base(BaseType::Int)
34}
35pub(crate) fn t_float() -> MonoType {
36 MonoType::Base(BaseType::Float)
37}
38pub(crate) fn t_length() -> MonoType {
39 MonoType::Base(BaseType::Length)
40}
41pub fn t_string() -> MonoType {
42 MonoType::Base(BaseType::String)
43}
44pub(crate) fn t_inline_text() -> MonoType {
45 MonoType::Base(BaseType::InlineText)
46}
47pub(crate) fn t_block_text() -> MonoType {
48 MonoType::Base(BaseType::BlockText)
49}
50/// `math` (v0.0.6, vminst.ml's `tMATH`) / `math-text` (V0_1, upstream's
51/// literal rename of 0.0.6's `math`, dev-0-1-0 `tMT`) — same `MonoType`
52/// (`BaseType::MathText`) under both versions; only the surface NAME differs
53/// (`typecheck.rs`'s `name_to_mono`, version-gated). `${…}`'s unparsed
54/// source, in both generations.
55pub(crate) fn t_math_text() -> MonoType {
56 MonoType::Base(BaseType::MathText)
57}
58/// `math` — v0.0.6-only alias of [`t_math_text`] (both name the same
59/// `BaseType::MathText`). V0_1 call sites should read `t_math_text()`
60/// instead; this alias exists only for 0.0.6 signatures written against it.
61fn t_math() -> MonoType {
62 t_math_text()
63}
64/// `math-boxes` (V0_1 only; dev-0-1-0 vminst.ml's `tMB`) — the evaluated
65/// math tree `read-math` produces. 0.0.6 has no name
66/// for this type (its `math` conflates both halves).
67pub(crate) fn t_math_boxes() -> MonoType {
68 MonoType::Base(BaseType::MathBoxes)
69}
70/// `context -> math-boxes` (V0_1 only) — the script-callback type
71/// `math-sup`/`math-sub`/`math-upper`/`math-lower` take in 0.1
72/// (vminst.ml:208-353), evaluated under `enter_script`.
73fn t_math_script_fn() -> MonoType {
74 arrow(t_context(), t_math_boxes())
75}
76/// `math-class` (`primitives.cppo.ml:162-170`) — a built-in
77/// **variant** (same shape as `t_color()`/`t_script()` above), registered by
78/// `builtin_variants_with_version`. `math-char`/`math-group`/… all take this as an
79/// argument. **Distinct** from `t_math_char_class()` below (the
80/// `MathItalic`/… styling variant) — do not conflate the two.
81fn t_math_class() -> MonoType {
82 MonoType::Variant("math-class".to_string(), Vec::new())
83}
84/// `math-char-class` (`horzBox.ml:147`) — a built-in variant, registered by
85/// `builtin_variants_with_version`. Needed for `math.satyh`'s `sig` (`\math-style :
86/// [math-char-class; math] math-cmd`) and its
87/// `\mathrm`/`\mathbf`/`\mathcal`/… definitions to type-check —
88/// the actual Unicode-math-block restyling this variant names is resolved
89/// (`rustyfi_backend:: MathCharClass`/`resolve_variant_char`) once
90/// evaluation reaches a value of this type. This TYPE itself
91/// (`math-char-class`, nominal, no parameters) is version-blind; its
92/// CONSTRUCTOR SET is not — see [`builtin_variants_with_version`]'s
93/// `math_char_class_decl` (V0_1 registers 14
94/// ctors, V0_0 exactly 9).
95fn t_math_char_class() -> MonoType {
96 MonoType::Variant("math-char-class".to_string(), Vec::new())
97}
98/// `paren` — version-forked, the same shape as
99/// `t_deco(version)`/`t_graphics_output(version)`/`t_decoset(version)`:
100/// - `V0_0` (`pervasives.satyh`'s `type paren`) — structural, like
101/// `t_point()`/`t_dash()`/`t_deco()` above: directly the expanded function
102/// type, not a nominal reference.
103/// - `V0_1` (`primitives.cppo.ml:91`'s `tPAREN`) — args are (inner height,
104/// inner depth SIGNED, the context) → (the delimiter boxes, the
105/// script-kern function). The 0.0.6→0.1 delta: the closure now extracts
106/// fontsize / axis-ratio (via `get-math-axis-height-ratio`) / color FROM
107/// the context instead of receiving them as separate explicit arguments —
108/// see `make_paren_run` (`primitives.rs`) for the corresponding runtime
109/// protocol fork.
110///
111/// `math-paren`'s first two arguments (`math.satyh`'s
112/// `paren-left`/`paren-right`, `\paren`/`\brace`/`\abs`/…) are typed against
113/// this shape directly; this port's type-synonym expansion resolves a
114/// `paren`-named annotation to the same shape (V0_0: pervasives synonym
115/// expansion; V0_1: the `name_to_mono("paren", …)` nominal case,
116/// `typecheck.rs`), so what must match is `paren-left`/`paren-right`'s own
117/// INFERRED type, which it does by construction. Gated on `math_is_split()`,
118/// the same predicate that forks `math-paren` itself, so type-env and
119/// runtime stay keyed on one capability.
120pub(crate) fn t_paren(version: RustyfiVersion) -> MonoType {
121 if version.math_is_split() {
122 arrows(
123 vec![t_length(), t_length(), t_context()],
124 product(vec![t_inline_boxes(), arrow(t_length(), t_length())]),
125 )
126 } else {
127 arrows(
128 vec![t_length(), t_length(), t_length(), t_length(), t_color()],
129 product(vec![t_inline_boxes(), arrow(t_length(), t_length())]),
130 )
131 }
132}
133/// A math-char kern function (fontsize, y-position -> kern amount).
134/// `math-char-with-kern`/`math-big-char-with-kern`'s 3rd/4th arguments.
135fn t_math_kern_func() -> MonoType {
136 arrows(vec![t_length(), t_length()], t_length())
137}
138/// `math-variant-char`'s 9-field per-style codepoint record (`value.rs`'s
139/// `MathVariantStyle`) — a closed record row, structural like
140/// `t_pbinfo()`/`t_page_content_scheme()` above. Field order doesn't matter
141/// (records are structural), only the label set; matches `math.satyh`'s
142/// `greek-lowercase`/`greek-uppercase` record literals field-for-field.
143fn t_math_variant_style() -> MonoType {
144 const LABELS: [&str; 9] = [
145 "italic",
146 "bold-italic",
147 "roman",
148 "bold-roman",
149 "script",
150 "bold-script",
151 "fraktur",
152 "bold-fraktur",
153 "double-struck",
154 ];
155 let mut row = types::Row::Empty;
156 for label in LABELS.iter().rev() {
157 row = types::Row::Cons(label.to_string(), Box::new(t_string()), Box::new(row));
158 }
159 MonoType::Record(row)
160}
161/// `image` (vminst.ml's `tIMG`) — `load-image`'s result;
162pub(crate) fn t_image() -> MonoType {
163 MonoType::Base(BaseType::Image)
164}
165pub(crate) fn t_inline_boxes() -> MonoType {
166 MonoType::Base(BaseType::InlineBoxes)
167}
168pub(crate) fn t_block_boxes() -> MonoType {
169 MonoType::Base(BaseType::BlockBoxes)
170}
171pub(crate) fn t_context() -> MonoType {
172 MonoType::Base(BaseType::Context)
173}
174pub(crate) fn t_document() -> MonoType {
175 MonoType::Base(BaseType::Document)
176}
177/// `color` (`primitives.cppo.ml:187-190`'s `Gray of float | RGB of
178/// (float*float*float) | CMYK of (float*float*float*float)`) — a built-in
179/// **variant**, not a `BaseType`: it costs a `VariantDecl` (registered by
180/// `builtin_variants_with_version` below) and no `BaseType::Color`. `Gray`/`RGB`/`CMYK`
181/// typecheck and evaluate as ordinary `Ast::Ctor`/`Value::Ctor` values;
182/// `fill`/`stroke` are its first consumers.
183fn t_color() -> MonoType {
184 MonoType::Variant("color".to_string(), Vec::new())
185}
186/// `hyphenation` (dev-0-1-0 `types.cppo.ml`'s opaque `HyphenationType`) —
187/// stand-in: NOT a declared `BaseType` or
188/// `VariantDecl`, just the nominal-`Variant` fallthrough `name_to_mono`
189/// already gives any unrecognized sig type name (`typecheck.rs:500`), the
190/// same shape the already-vendored `color.satyh` seals through.
191/// `load-hyphenation-dictionary`'s return type and
192/// `set-hyphenation-dictionary`'s domain both spell this helper, so sealing
193/// subsumption unifies them structurally — no `types.rs` touch needed.
194fn t_hyphenation() -> MonoType {
195 MonoType::Variant("hyphenation".to_string(), Vec::new())
196}
197/// `unicode-char-database` (dev-0-1-0 `types.cppo.ml`'s opaque
198/// `UnidataType`) — a stand-in, same nominal-`Variant` shape as
199/// [`t_hyphenation`] just above.
200fn t_unicode_char_database() -> MonoType {
201 MonoType::Variant("unicode-char-database".to_string(), Vec::new())
202}
203/// `pre-path` (vminst.ml's `tPRP`; v0.0.6 `PrePathType`).
204pub(crate) fn t_prepath() -> MonoType {
205 MonoType::Base(BaseType::PrePath)
206}
207/// `path` (vminst.ml's `tPATH`; v0.0.6 `PathType`).
208pub(crate) fn t_path() -> MonoType {
209 MonoType::Base(BaseType::Path)
210}
211/// `graphics` (vminst.ml's `tGR`; v0.0.6 `GraphicsType`).
212pub(crate) fn t_graphics() -> MonoType {
213 MonoType::Base(BaseType::Graphics)
214}
215/// `point = length * length` (vminst.ml's `tPT = tPROD[tLN;tLN]`) —
216/// structural, not a `BaseType`: a point is just a 2-tuple of lengths,
217/// matching the runtime representation (`Value::Tuple([Length, Length])`,
218/// see `primitives.rs`'s `as_point`/`make_point_value`).
219fn t_point() -> MonoType {
220 product(vec![t_length(), t_length()])
221}
222/// `page-break-info` (vminst.ml's `tPBINFO`) — a `hook-page-break` closure's
223/// first argument. The port has first-class row-typed records
224/// (`types::MonoType::Record`) and `#field` access, so this type-checks
225/// structurally with no nominal `tPBINFO` variant needed.
226/// Runtime: `Value::Record`, built by `fire_hooks` (`lib.rs`).
227fn t_pbinfo() -> MonoType {
228 MonoType::Record(types::Row::Cons(
229 "page-number".to_string(),
230 Box::new(t_int()),
231 Box::new(types::Row::Empty),
232 ))
233}
234/// `tDOCINFODIC` (dev-0-1-0 `src/frontend/primitives.cppo.ml:98-107`):
235/// `register-document-information`'s argument, upstream's named record type
236/// `document-information-dictionary`. Structural here, the same `t_pbinfo`
237/// precedent above: upstream registers a `SynonymType` name for the
238/// identical closed row, which this port deliberately doesn't mirror
239/// nominally (cosmetic deviation — revisit only if a 0.1 package names the
240/// type in a signature).
241fn t_doc_info_dictionary() -> MonoType {
242 MonoType::Record(types::Row::Cons(
243 "title".to_string(),
244 Box::new(t_option(t_string())),
245 Box::new(types::Row::Cons(
246 "subject".to_string(),
247 Box::new(t_option(t_string())),
248 Box::new(types::Row::Cons(
249 "author".to_string(),
250 Box::new(t_option(t_string())),
251 Box::new(types::Row::Cons(
252 "keywords".to_string(),
253 Box::new(list(t_string())),
254 Box::new(types::Row::Empty),
255 )),
256 )),
257 )),
258 ))
259}
260/// `page` (vminst.ml's `tPG`) — a nominal variant, `primitives.cppo.ml:203-212`,
261/// registered by `builtin_variants_with_version`. `page-break`'s first argument
262/// selects the whole document's paper size.
263fn t_page() -> MonoType {
264 MonoType::Variant("page".to_string(), Vec::new())
265}
266/// `page-break`/`page-break-multicolumn`/`page-break-two-column`'s
267/// first-argument type, forked: `t_page()` (the v0.0.6 9-ctor ADT)
268/// under `has_page_adt()`, a plain `length * length` tuple otherwise.
269/// `RustyfiVersion::has_page_adt()` is the single source of truth for
270/// which shape a given version's `page-break*` family admits —
271/// `builtin_variants_with_version` below gates the ADT's own *registration* on the
272/// exact same method, so the two can never disagree (a `V0_1` program can
273/// never see a type that calls `t_page()` while `page`'s `VariantDecl` is
274/// absent from its `builtin_variants_with_version` result).
275fn t_page_or_geometry(version: RustyfiVersion) -> MonoType {
276 if version.has_page_adt() {
277 t_page()
278 } else {
279 product(vec![t_length(), t_length()])
280 }
281}
282/// `page-content-scheme` (vminst.ml's `tPAGECONT`) — what a `page-break`
283/// content-scheme closure returns, applied once per page with that page's
284/// `pbinfo`. Structural, like `t_pbinfo` above — no nominal type needed.
285fn t_page_content_scheme() -> MonoType {
286 MonoType::Record(types::Row::Cons(
287 "text-origin".to_string(),
288 Box::new(t_point()),
289 Box::new(types::Row::Cons(
290 "text-height".to_string(),
291 Box::new(t_length()),
292 Box::new(types::Row::Empty),
293 )),
294 ))
295}
296/// `page-parts` (vminst.ml's `tPAGEPARTS`) — what a `page-break`
297/// parts-scheme closure returns, applied once per page with that page's
298/// `pbinfo`.
299fn t_page_parts() -> MonoType {
300 MonoType::Record(types::Row::Cons(
301 "header-origin".to_string(),
302 Box::new(t_point()),
303 Box::new(types::Row::Cons(
304 "header-content".to_string(),
305 Box::new(t_block_boxes()),
306 Box::new(types::Row::Cons(
307 "footer-origin".to_string(),
308 Box::new(t_point()),
309 Box::new(types::Row::Cons(
310 "footer-content".to_string(),
311 Box::new(t_block_boxes()),
312 Box::new(types::Row::Empty),
313 )),
314 )),
315 )),
316 ))
317}
318/// `'a option` (vminst.ml's `tOPT`) — the built-in `option` variant
319/// (`builtin_variants_with_version`'s `option_decl`) applied to `ty`.
320pub(crate) fn t_option(ty: MonoType) -> MonoType {
321 MonoType::Variant("option".to_string(), vec![ty])
322}
323/// `script` (vminst.ml's `tSCR`) — a built-in **variant** (same shape as
324/// `t_color()` above): `HanIdeographic | Kana | Latin | OtherScript`
325/// (upstream's real surface constructor set, `primitives.cppo.ml:192-196`),
326/// registered by `builtin_variants_with_version`. `script-guard` (pervasives.satyh's
327/// `\SATySFi`/`\LaTeX`/`\TeX`) is its first consumer.
328fn t_script() -> MonoType {
329 MonoType::Variant("script".to_string(), Vec::new())
330}
331/// `language` (vminst.ml's `tLANG`; `charBasis.ml`'s `language_system =
332/// Japanese | English | NoLanguageSystem`) — a nullary built-in variant,
333/// same shape as [`t_script`]. `set-language`'s 2nd argument
334/// (`stdja.satyh`'s `set-language Kana Japanese`).
335fn t_language() -> MonoType {
336 MonoType::Variant("language".to_string(), Vec::new())
337}
338/// `text-info` (vminst.ml's `tTCTX`; v0.0.6 `TextInfoType`) — the text-mode
339/// context: only the three pure prims below produce/consume it.
340fn t_text_info() -> MonoType {
341 MonoType::Base(BaseType::TextInfo)
342}
343/// `paddings` (vminst.ml's `tPADS = tPROD [tLN;tLN;tLN;tLN]`) — a plain
344/// 4-tuple `(paddingL, paddingR, paddingT, paddingB)`, matching the runtime
345/// shape `primitives.rs`'s `as_paddings` reads (mirrors `evalUtil.ml`'s
346/// `get_paddings` field order).
347fn t_paddings() -> MonoType {
348 product(vec![t_length(), t_length(), t_length(), t_length()])
349}
350/// `cell` (`primitives.cppo.ml:214-217`) — a built-in **variant** (same
351/// shape as `t_color()` above), registered by `builtin_variants_with_version`.
352fn t_cell() -> MonoType {
353 MonoType::Variant("cell".to_string(), Vec::new())
354}
355/// `dash` (`graphicD.ml`'s `type dash = length * length * length`) —
356/// `dashed-stroke`'s 2nd argument, `(d1, d2, d0)` = on-length, off-length,
357/// phase.
358fn t_dash() -> MonoType {
359 product(vec![t_length(), t_length(), t_length()])
360}
361/// The result type of a graphics-producing callback (`inline-graphics`'s
362/// `tIGR`, `inline-graphics-outer`'s `tIGRO`, `tabular`'s `tRULESF`,
363/// `t_deco`'s own result): `list graphics` (`tL tGR`) under `V0_0`, one
364/// `graphics` collection (`tGR`) under `V0_1` — the hidden alias-redefinition
365/// retype surfaces across every carrier primitive that returns this shape. Runtime counterpart: `coerce_graphics_result`
366/// (`primitives.rs`), keyed on the same
367/// `RustyfiVersion::graphics_is_collection()` capability so the env and
368/// type-env agree by construction.
369fn t_graphics_output(version: RustyfiVersion) -> MonoType {
370 if version.graphics_is_collection() {
371 t_graphics()
372 } else {
373 list(t_graphics())
374 }
375}
376/// `deco` (vminst.ml's `tDECO_raw` under `V0_0`; dev-0-1-0 redefines the
377/// same alias with a bare `tGR` result), invoked (once placed) with
378/// its own position and resolved width/height/depth. `inline-frame-outer`'s
379/// stand-in body (`primitives.rs`) never actually calls it (see that
380/// primitive's doc comment), but it is typed faithfully so callers still
381/// type-check exactly as they would upstream.
382pub(crate) fn t_deco(version: RustyfiVersion) -> MonoType {
383 arrows(
384 vec![t_point(), t_length(), t_length(), t_length()],
385 t_graphics_output(version),
386 )
387}
388/// `deco-set = deco * deco * deco * deco` (vminst.ml's `tDECOSET`) —
389/// `block-frame-breakable`'s third argument (the four edge/corner
390/// decoration closures a frame would fire at placement time). STAND-IN
391/// body (`primitives.rs`'s `prim_block_frame_breakable`) pops and drops it
392/// entirely (like `t_deco()`'s own callers above), but it is typed
393/// faithfully here so callers still type-check exactly as they would
394/// upstream.
395pub(crate) fn t_decoset(version: RustyfiVersion) -> MonoType {
396 product(vec![t_deco(version); 4])
397}
398/// `font` — the V0_1-only OPAQUE face handle (upstream `saphe-split`
399/// `primitives.cppo.ml:45`'s `tFONTKEY = (~! "font", BaseType(FontType))`).
400/// Value: [`Value::Font`](crate::value::Value::Font), a resolved
401/// `rustyfi_backend::FontKey`, exactly upstream's `BCFontKey of FontKey.t`.
402///
403/// There is deliberately no `V0_0` counterpart: upstream 0.0.6 registers no
404/// `font` base type and declares no `type font` in its bundled library, so
405/// under `V0_0` the name falls through to the nominal `Variant("font", [])`
406/// — see [`BaseType::Font`] for the citations.
407pub(crate) fn t_font_key() -> MonoType {
408 MonoType::Base(BaseType::Font)
409}
410
411/// `font-with-ratio`, i.e. what `set-font`'s second argument actually is,
412/// **version-forked at the head component**:
413///
414/// - `V0_0` — `string * float * float`, upstream `v0.0.6
415/// primitives.cppo.ml:69`'s `tFONT = tPROD [tS; tFL; tFL]`. The head is a
416/// font ABBREV naming a row of `dist/hash/fonts.satysfi-hash`.
417/// - `V0_1` — `font * float * float`, upstream `saphe-split
418/// primitives.cppo.ml:74`'s `tFONTWR = tPROD [tFONTKEY; tFL; tFL]`. The
419/// head is the opaque [`t_font_key`] handle; the 0.0.6 name is GONE from
420/// the surface, and no primitive converts between the two.
421///
422/// The trailing `(size_ratio, rising_ratio)` pair is identical in both.
423fn t_font_with_ratio(version: RustyfiVersion) -> MonoType {
424 let head = match version {
425 RustyfiVersion::V0_1 => t_font_key(),
426 _ => t_string(),
427 };
428 product(vec![head, t_float(), t_float()])
429}
430
431/// `dom -> cod` (vminst.ml's `@->`) — a function taking no labeled optional
432/// arguments (`Row::Empty`), so every 0.0.6 primitive/inference site
433/// building a `Func` produces the empty-row shape by construction.
434pub fn arrow(dom: MonoType, cod: MonoType) -> MonoType {
435 MonoType::Func(
436 Box::new(crate::types::Row::Empty),
437 Box::new(dom),
438 Box::new(cod),
439 )
440}
441
442/// Right-folds [`arrow`] over `doms`, ending in `cod` — for chaining
443/// several arguments the way vminst.ml chains `@->`, e.g.
444/// `arrows(vec![t_bool(), t_bool(), t_context(), t_inline_boxes()], t_block_boxes())`
445/// for `bool -> bool -> context -> inline-boxes -> block-boxes`.
446fn arrows(doms: Vec<MonoType>, cod: MonoType) -> MonoType {
447 doms.into_iter().rev().fold(cod, |acc, dom| arrow(dom, acc))
448}
449
450/// `tL` — list type.
451pub fn list(t: MonoType) -> MonoType {
452 MonoType::List(Box::new(t))
453}
454
455/// `tR` — mutable reference type.
456pub fn reff(t: MonoType) -> MonoType {
457 MonoType::Ref(Box::new(t))
458}
459
460/// `tPROD` — tuple type.
461pub fn product(ts: Vec<MonoType>) -> MonoType {
462 MonoType::Product(ts)
463}
464
465/// `[...] inline-cmd` (vminst.ml's `tICMD ty` = `HorzCommandType([Mandatory
466/// ty])`: an inline command taking exactly one mandatory argument of type
467/// `ty`).
468fn inline_cmd(args: Vec<CmdArgType>) -> MonoType {
469 MonoType::InlineCmd(args)
470}
471
472/// `mandatory` command-argument entry (0.0.6 positional model; also the
473/// V0_1 shape for a slot with no `?(…)` bundle at all — `opt_labels` empty
474/// either way).
475pub(crate) fn mandatory(ty: MonoType) -> CmdArgType {
476 CmdArgType {
477 optional: false,
478 opt_labels: Vec::new(),
479 ty,
480 }
481}
482
483/// `optional` (`?`) command-argument entry (0.0.6 positional model only —
484/// `opt_labels` stays empty; V0_1 never sets `optional: true`, see
485/// [`labeled`]).
486pub(crate) fn optional(ty: MonoType) -> CmdArgType {
487 CmdArgType {
488 optional: true,
489 opt_labels: Vec::new(),
490 ty,
491 }
492}
493
494/// A SATySFi 0.1 labeled command-argument entry (upstream
495/// `CommandArgType(LabelMap.t, typ)`, `types.cppo.ml:214-215`):
496/// `ty` is the slot's mandatory argument, `opt_labels` its CLOSED `?(l:τ,…)`
497/// bundle (kept sorted by label by the caller — `command_scheme`'s harvest,
498/// `lower_type_atom`'s sig lowering). `optional` is always `false`: V0_1 has
499/// no whole-slot `ty?` positional-optional model at all.
500pub(crate) fn labeled(opt_labels: Vec<(String, MonoType)>, ty: MonoType) -> CmdArgType {
501 CmdArgType {
502 optional: false,
503 opt_labels,
504 ty,
505 }
506}
507
508/// A type scheme with no quantified variables (vminst.ml's `~%` wraps a
509/// closed monomorphic body the same way; primitives that actually need
510/// polymorphism, like `::`/`!`, use [`poly1`] instead).
511fn poly0(ty: MonoType) -> PolyType {
512 PolyType::mono(ty)
513}
514
515/// A type scheme quantified over exactly one fresh type variable, e.g.
516/// `poly1(|a| arrow(reff(a.clone()), a))` for `!`'s `'a ref -> 'a`
517/// (vminst.ml's `~@` marks such a per-scheme fresh variable; `~%` then
518/// closes the whole thing into a scheme, matching `ptyderef`/`ptycons` in
519/// `primitives.cppo.ml:546-547`).
520fn poly1<F: FnOnce(MonoType) -> MonoType>(f: F) -> PolyType {
521 let v = types::new_ty_var(0);
522 let body = f(MonoType::Var(v.clone()));
523 PolyType::from_vars(vec![v], Vec::new(), body)
524}
525
526// ============================================================================
527// The primitive type table.
528// ============================================================================
529
530/// Look up the type scheme of a v0.0.6 primitive registered in
531/// `primitives.rs`'s `prims!` table (or the separately-defined
532/// `inline-fil` constant), by its *source* name (sigil included, e.g.
533/// `"\\emph"`, `"+'"`, `"::"`). Back-compat wrapper — see
534/// `primitive_type_with_version`'s doc comment.
535pub fn primitive_type(name: &str) -> Option<PolyType> {
536 primitive_type_with_version(name, RustyfiVersion::V0_0)
537}
538
539/// Look up the type scheme of a primitive registered in `primitives.rs`'s
540/// `prims!` table, for a given target `version`. Mirrors
541/// `primitives::base_env`/`base_env_with_version`'s split (the
542/// `lex`/`lex_with_version` idiom).
543pub fn primitive_type_with_version(name: &str, version: RustyfiVersion) -> Option<PolyType> {
544 Some(match name {
545 // ==== removed in 0.1 — guard these OUT of the type table under V0_1
546 // before falling through to their ordinary (0.0.6-only-meaningful)
547 // arms further below. Runtime availability is the `prims!` table's
548 // `v006` tag on the same six names; this guard keeps the two
549 // mechanisms in agreement. ====
550 "get-axis-height"
551 | "math-pull-in-scripts"
552 | "math-color"
553 | "math-char-class"
554 | "math-variant-char"
555 | "text-in-math"
556 if version.math_is_split() =>
557 {
558 return None
559 }
560
561 // ==== added in 0.1 — unbound under V0_0
562 // (falls through this guard to the catch-all `_ => return None`
563 // below, since none of these names have a v0.0.6 arm at all). ====
564 //
565 // dev-0-1-0 vminst.ml:790-793 — REAL, see `primitives.rs`'s `prim_read_math`.
566 "read-math" if version.math_is_split() => {
567 poly0(arrows(vec![t_context(), t_math_text()], t_math_boxes()))
568 }
569 // vminst.ml:858 — STAND-IN (out-of-scope text backend, same scoping
570 // note as `primitives.rs`'s `prim_convert_string_for_math`);
571 // registered so 0.1 packages typecheck.
572 "stringify-math" if version.math_is_split() => {
573 poly0(arrows(vec![t_text_info(), t_math_text()], t_string()))
574 }
575 // vminst.ml:59 — REAL: inserts into `Context::math_class_map`.
576 "set-math-char" if version.math_is_split() => poly0(arrows(
577 vec![t_int(), t_int(), t_math_class(), t_context()],
578 t_context(),
579 )),
580 // vminst.ml:445 — REAL: sets `Context::math_char_class`.
581 "set-math-char-class" if version.math_is_split() => {
582 poly0(arrows(vec![t_math_char_class(), t_context()], t_context()))
583 }
584 // vminst.ml:459 — REAL: inverse of `as_math_char_class`.
585 "get-math-char-class" if version.math_is_split() => {
586 poly0(arrow(t_context(), t_math_char_class()))
587 }
588 // vminst.ml:432 — REAL data, stand-in render (`MathElement::EmbeddedBoxes`).
589 "embed-inline-to-math" if version.math_is_split() => poly0(arrows(
590 vec![t_math_class(), t_inline_boxes()],
591 t_math_boxes(),
592 )),
593 // vminst.ml:1305 — REAL: the axis-height ratio `MathC` already scales by.
594 "get-math-axis-height-ratio" if version.math_is_split() => {
595 poly0(arrow(t_context(), t_float()))
596 }
597 // `%math-attach-scripts` — hidden (unlexable name, `%` starts a
598 // comment), the synthesized script-attacher `val math` commands
599 // without `with sub sup` lower to.
600 "%math-attach-scripts" if version.math_is_split() => poly0(arrows(
601 vec![
602 t_context(),
603 t_math_boxes(),
604 t_option(t_math_text()),
605 t_option(t_math_text()),
606 ],
607 t_math_boxes(),
608 )),
609
610 // ==== hyphenation/unidata loader
611 // + setter stand-ins, and the `here` lex-time-constant stand-in —
612 // all V0_1-only (genuinely absent from 0.0.6 upstream, so these
613 // fall through to the catch-all `_ => return None` under V0_0).
614 // Types are FAITHFUL to upstream (`vminst.ml`/`primitives.cppo.ml`);
615 // bodies (`primitives.rs`) are ACCEPT-AND-RETURN stand-ins, not
616 // hard errors like `stringify-math` above, because std-ja
617 // *evaluates* `load-unicode-char-database`/`load-hyphenation-
618 // dictionary` at module load time. ====
619 //
620 // `vminst.ml`'s `LoadHyphenationDictionary`.
621 "load-hyphenation-dictionary" if version == RustyfiVersion::V0_1 => {
622 poly0(arrow(t_string(), t_hyphenation()))
623 }
624 // `vminst.ml`'s `LoadUnicodeCharDatabase` — args are
625 // Scripts.txt/EastAsianWidth.txt/LineBreak.txt paths.
626 "load-unicode-char-database" if version == RustyfiVersion::V0_1 => poly0(arrows(
627 vec![t_string(), t_string(), t_string()],
628 t_unicode_char_database(),
629 )),
630 // STAND-IN no-op (see `primitives.rs`'s
631 // `prim_set_hyphenation_dictionary`); closes a scout-identified gap.
632 "set-hyphenation-dictionary" if version == RustyfiVersion::V0_1 => {
633 poly0(arrows(vec![t_hyphenation(), t_context()], t_context()))
634 }
635 // STAND-IN no-op (see `primitives.rs`'s
636 // `prim_set_unicode_char_database`); closes a scout-identified gap.
637 "set-unicode-char-database" if version == RustyfiVersion::V0_1 => poly0(arrows(
638 vec![t_unicode_char_database(), t_context()],
639 t_context(),
640 )),
641 // upstream is a lex-time constant (the source file's directory);
642 // the port models it as a V0_1-only nullary constant bound to
643 // `Value::Str(String::new())` (`primitives.rs`'s `base_env_with_version`).
644 "here" if version == RustyfiVersion::V0_1 => poly0(t_string()),
645
646 // ---- this port's own natives (no vminst.ml entry — local signatures) ----
647 //
648 // vminst.ml:834, `HorzLex`.
649 "read-inline" => poly0(arrow(t_context(), arrow(t_inline_text(), t_inline_boxes()))),
650 // vminst.ml:857, `VertLex`.
651 "read-block" => poly0(arrow(t_context(), arrow(t_block_text(), t_block_boxes()))),
652 // vminst.ml:1003, `BackendLineBreaking`.
653 "line-break" => poly0(arrows(
654 vec![t_bool(), t_bool(), t_context(), t_inline_boxes()],
655 t_block_boxes(),
656 )),
657 // vminst.ml:1024, `BackendPageBreaking` — the real 4-arg primitive.
658 "page-break" => poly0(arrows(
659 vec![
660 t_page_or_geometry(version),
661 arrow(t_pbinfo(), t_page_content_scheme()),
662 arrow(t_pbinfo(), t_page_parts()),
663 t_block_boxes(),
664 ],
665 t_document(),
666 )),
667 // vminst.ml:1065 `BackendPageBreakingMultiColumn` — FAITHFUL, see
668 // `primitives.rs`'s `prim_page_break_multicolumn` / `page_break_core`.
669 "page-break-multicolumn" => poly0(arrows(
670 vec![
671 t_page_or_geometry(version),
672 list(t_length()),
673 arrow(t_unit(), t_block_boxes()),
674 arrow(t_unit(), t_block_boxes()),
675 arrow(t_pbinfo(), t_page_content_scheme()),
676 arrow(t_pbinfo(), t_page_parts()),
677 t_block_boxes(),
678 ],
679 t_document(),
680 )),
681 // vminst.ml:1041 `BackendPageBreakingTwoColumn`.
682 "page-break-two-column" => poly0(arrows(
683 vec![
684 t_page_or_geometry(version),
685 t_length(),
686 arrow(t_unit(), t_block_boxes()),
687 arrow(t_pbinfo(), t_page_content_scheme()),
688 arrow(t_pbinfo(), t_page_parts()),
689 t_block_boxes(),
690 ],
691 t_document(),
692 )),
693 // ---- int arithmetic ----
694 // vminst.ml:2537 `Plus`.
695 "+" => poly0(arrows(vec![t_int(), t_int()], t_int())),
696 // vminst.ml:2553 `Minus`.
697 "-" => poly0(arrows(vec![t_int(), t_int()], t_int())),
698 // vminst.ml:2487 `Times`.
699 "*" => poly0(arrows(vec![t_int(), t_int()], t_int())),
700 // vminst.ml:2503 `Divides`.
701 "/" => poly0(arrows(vec![t_int(), t_int()], t_int())),
702 // vminst.ml:2520 `Mod`.
703 "mod" => poly0(arrows(vec![t_int(), t_int()], t_int())),
704
705 // ---- int comparisons ----
706 // vminst.ml:2569 `EqualTo`.
707 "==" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
708 // LOCAL (primitives.cppo.ml's `general_table` derives this as
709 // `LogicalNot (EqualTo ..)`, not its own vminst.ml instruction).
710 "<>" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
711 // vminst.ml:2601 `LessThan`.
712 "<" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
713 // vminst.ml:2585 `GreaterThan`.
714 ">" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
715 // LOCAL (derives as `LogicalNot (GreaterThan ..)`).
716 "<=" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
717 // LOCAL (derives as `LogicalNot (LessThan ..)`).
718 ">=" => poly0(arrows(vec![t_int(), t_int()], t_bool())),
719
720 // ---- bool ----
721 // vminst.ml:2617 `LogicalAnd`.
722 "&&" => poly0(arrows(vec![t_bool(), t_bool()], t_bool())),
723 // vminst.ml:2633 `LogicalOr`.
724 "||" => poly0(arrows(vec![t_bool(), t_bool()], t_bool())),
725 // vminst.ml:2649 `LogicalNot`.
726 "not" => poly0(arrow(t_bool(), t_bool())),
727
728 // ---- float ----
729 // vminst.ml:2664 `FloatPlus`.
730 "+." => poly0(arrows(vec![t_float(), t_float()], t_float())),
731 // vminst.ml:2680 `FloatMinus`.
732 "-." => poly0(arrows(vec![t_float(), t_float()], t_float())),
733 // vminst.ml:2696 `FloatTimes`.
734 "*." => poly0(arrows(vec![t_float(), t_float()], t_float())),
735 // vminst.ml:2712 `FloatDivides`.
736 "/." => poly0(arrows(vec![t_float(), t_float()], t_float())),
737 // vminst.ml:2333 `PrimitiveFloat`.
738 "float" => poly0(arrow(t_int(), t_float())),
739 // vminst.ml:2348 `PrimitiveRound` — despite the name this truncates
740 // toward zero (see primitives.rs's `prim_round`).
741 "round" => poly0(arrow(t_float(), t_int())),
742
743 // ---- length ----
744 // vminst.ml:2894 `LengthPlus`.
745 "+'" => poly0(arrows(vec![t_length(), t_length()], t_length())),
746 // vminst.ml:2910 `LengthMinus`.
747 "-'" => poly0(arrows(vec![t_length(), t_length()], t_length())),
748 // vminst.ml:2926 `LengthTimes`.
749 "*'" => poly0(arrows(vec![t_length(), t_float()], t_length())),
750 // vminst.ml:2942 `LengthDivides`.
751 "/'" => poly0(arrows(vec![t_length(), t_length()], t_float())),
752 // vminst.ml:2958 `LengthLessThan`.
753 "<'" => poly0(arrows(vec![t_length(), t_length()], t_bool())),
754 // vminst.ml:2974 `LengthGreaterThan`.
755 ">'" => poly0(arrows(vec![t_length(), t_length()], t_bool())),
756
757 // ---- string ----
758 // vminst.ml:22 `Concat`.
759 "^" => poly0(arrows(vec![t_string(), t_string()], t_string())),
760 // vminst.ml:2303 `PrimitiveArabic`.
761 "arabic" => poly0(arrow(t_int(), t_string())),
762 // vminst.ml:2085 `PrimitiveSame`.
763 "string-same" => poly0(arrows(vec![t_string(), t_string()], t_bool())),
764 // vminst.ml:2143 `PrimitiveStringLength`.
765 "string-length" => poly0(arrow(t_string(), t_int())),
766 // vminst.ml:2101 `PrimitiveStringSub`.
767 "string-sub" => poly0(arrows(vec![t_string(), t_int(), t_int()], t_string())),
768 // vminst.ml:2212 `PrimitiveStringExplode`.
769 "string-explode" => poly0(arrow(t_string(), list(t_int()))),
770 // vminst.ml `PrimitiveRegExpOfString`/`PrimitiveStringMatch`. The
771 // port models `regexp` as its underlying pattern `string` (see
772 // `primitives.rs`); `satysfi-base`'s `char.satyg` only ever builds
773 // character-class patterns (`[0-9]`, `[A-Za-z]`, …).
774 "regexp-of-string" => poly0(arrow(t_string(), t_string())),
775 "string-match" => poly0(arrows(vec![t_string(), t_string()], t_bool())),
776 // vminstdef.yaml:1961 `PrimitiveStringScan`:
777 // `~% (tRE @-> tS @-> tOPT (tPROD [tS; tS]))` — the matched prefix and
778 // the remainder. `satysfi-code-printer`'s lexer is built on it.
779 "string-scan" => poly0(arrows(
780 vec![t_string(), t_string()],
781 t_option(product(vec![t_string(), t_string()])),
782 )),
783 // vminst.ml `PrimitiveSplitOnRegExp` — split points paired with the
784 // segment between them (`satysfi-base`/figbox split a path on `\.`).
785 "split-on-regexp" => poly0(arrows(
786 vec![t_string(), t_string()],
787 list(product(vec![t_int(), t_string()])),
788 )),
789
790 // ---- list cons ----
791 // primitives.cppo.ml:547's `ptycons`. Not its own vminst.ml
792 // instruction upstream (`::` desugars to `ListCons` at parse time
793 // there); here it's a first-class primitive (see the `prims!`
794 // table's comment on `"::"` in primitives.rs).
795 "::" => poly1(|a| arrow(a.clone(), arrow(list(a.clone()), list(a)))),
796
797 // ---- mutable-cell dereference ----
798 // primitives.cppo.ml:546's `ptyderef`.
799 "!" => poly1(|a| arrow(reff(a.clone()), a)),
800
801 // ---- text embedding ----
802 // vminst.ml:1706 `PrimitiveEmbed`.
803 "embed-string" => poly0(arrow(t_string(), t_inline_text())),
804
805 // ---- context ops ----
806 // vminst.ml:1434 `PrimitiveSetFontSize`.
807 "set-font-size" => poly0(arrow(t_length(), arrow(t_context(), t_context()))),
808 // vminst.ml:1449 `PrimitiveGetFontSize`.
809 "get-font-size" => poly0(arrow(t_context(), t_length())),
810 // vminst.ml:1633 `PrimitiveSetLeading` (see primitives.rs's `prims!`
811 // table comment on why this, and not `set-min-gap-of-lines`, is the
812 // baseline-distance setter).
813 "set-leading" => poly0(arrow(t_length(), arrow(t_context(), t_context()))),
814 // vminst.ml:1396 `PrimitiveSetParagraphMargin`.
815 "set-paragraph-margin" => poly0(arrows(
816 vec![t_length(), t_length(), t_context()],
817 t_context(),
818 )),
819 // vminst.ml:1648 `PrimitiveGetTextWidth`.
820 "get-text-width" => poly0(arrow(t_context(), t_length())),
821 // vminst.ml:1247 `PrimitiveGetInitialContext` — the second argument
822 // is a `[math] inline-cmd` (`MonoType::InlineCmd` with one mandatory
823 // `math` argument), NOT `MathCmd` (`MathCommandType`, the different
824 // v0.0.6 type used for math-mode commands like `\sqrt`). Call sites
825 // pass `(command \math)` — or a local stub command — to build the
826 // first-class command reference this needs; the runtime
827 // side is FAITHFUL, see `primitives.rs`'s
828 // `prim_get_initial_context`.
829 "get-initial-context" => poly0(arrow(
830 t_length(),
831 arrow(inline_cmd(vec![mandatory(t_math_text())]), t_context()),
832 )),
833
834 // ---- context ops, continued (a LOCAL, non-upstream primitive; see primitives.rs's `prims!` table
835 // comment on `"set-font-key"` for why it exists) --------------------
836 "set-font-key" => poly0(arrow(t_int(), arrow(t_context(), t_context()))),
837
838 // ---- box combinators ----
839 // vminst.ml:803 `HorzConcat`.
840 "++" => poly0(arrows(
841 vec![t_inline_boxes(), t_inline_boxes()],
842 t_inline_boxes(),
843 )),
844 // vminst.ml:818 `VertConcat`.
845 "+++" => poly0(arrows(
846 vec![t_block_boxes(), t_block_boxes()],
847 t_block_boxes(),
848 )),
849 // No vminst.ml entry — see `base_env`'s comment on `inline-nil`/
850 // `block-nil` in primitives.rs (the empty-boxes value that v0.0.6
851 // gets for free from `{}`/`<>` literal syntax).
852 "inline-nil" => poly0(t_inline_boxes()),
853 "block-nil" => poly0(t_block_boxes()),
854 // vminst.ml:1757 `BackendFixedEmpty`.
855 "inline-skip" => poly0(arrow(t_length(), t_inline_boxes())),
856 // vminst.ml:1771 `BackendOuterEmpty`.
857 "inline-glue" => poly0(arrows(
858 vec![t_length(), t_length(), t_length()],
859 t_inline_boxes(),
860 )),
861 // vminst.ml:1171 `BackendVertSkip`.
862 "block-skip" => poly0(arrow(t_length(), t_block_boxes())),
863
864 // ---- the reflow
865 // marker-box constructors — no vminst.ml entry (NEW, not a port);
866 // see `primitives.rs`'s `prim_list_mark`/`prim_inline_mark` doc
867 // comments for the `int` tag encoding. Both produce an INERT marker
868 // box, stripped with zero contribution before PDF/faithful-HTML
869 // placement — read only by the reflow HTML walker. ----
870 "list-mark" => poly0(arrow(t_int(), t_block_boxes())),
871 "inline-mark" => poly0(arrow(t_int(), t_inline_boxes())),
872
873 // ---- images (raster images, mirroring v0.0.6 vminstdef.yaml:540/:554) ----
874 "load-image" => poly0(arrow(t_string(), t_image())),
875 "use-image-by-width" => poly0(arrows(vec![t_image(), t_length()], t_inline_boxes())),
876 // v0.0.6 vminstdef.yaml:525 — path + 1-based page number.
877 "load-pdf-image" => poly0(arrows(vec![t_string(), t_int()], t_image())),
878
879 // ---- inline-fil ----
880 // Not a primitive *function* at all (`base_env` binds it directly
881 // to a constant `Value::InlineBoxes`, primitives.rs), so there is
882 // no vminst.ml `~type_:` to cite; its type is simply that of the
883 // value it names.
884 "inline-fil" => poly0(t_inline_boxes()),
885 // primitives.cppo.ml:567 — same shape as `inline-fil` above (a bare
886 // constant, STAND-IN body; see `primitives.rs`'s `base_env` comment).
887 "omit-skip-after" => poly0(t_inline_boxes()),
888 // primitives.cppo.ml:569 — same shape as `inline-fil`/`omit-skip-
889 // after` above: a bare constant (`base_env` binds it to
890 // `Value::BlockBoxes(vec![VertBox::ClearPage])`), FAITHFUL —
891 // `mitou-report.satyh`'s `document` unblocker.
892 "clear-page" => poly0(t_block_boxes()),
893
894 // ---- the ~18 pure primitives ----------------------------
895 // (`|>` is excluded here on purpose — it is elaborated directly to
896 // ordinary `Apply`, never a `scope`/env-bound name, so it has no
897 // primitive type scheme at all; see `elaborate.rs`'s `climb`.)
898
899 // vminst.ml:2729/2744/2759/2774/2789/2804 `FloatSine`/`FloatArcSine`/
900 // `FloatCosine`/`FloatArcCosine`/`FloatTangent`/`FloatArcTangent`.
901 "sin" => poly0(arrow(t_float(), t_float())),
902 "asin" => poly0(arrow(t_float(), t_float())),
903 "cos" => poly0(arrow(t_float(), t_float())),
904 "acos" => poly0(arrow(t_float(), t_float())),
905 "tan" => poly0(arrow(t_float(), t_float())),
906 "atan" => poly0(arrow(t_float(), t_float())),
907 // vminst.ml:2819 `FloatArcTangent2`, params `(flt1, flt2)` in that
908 // order, so `flt1.atan2(flt2)`.
909 "atan2" => poly0(arrows(vec![t_float(), t_float()], t_float())),
910 // vminst.ml:2835 `FloatLogarithm`: natural log, not log10.
911 "log" => poly0(arrow(t_float(), t_float())),
912 // vminst.ml:2850 `FloatExponential`.
913 "exp" => poly0(arrow(t_float(), t_float())),
914 // vminst.ml:2865/2880 `PrimitiveCeil`/`PrimitiveFloor` — result is
915 // `float`, not `int` (contrast `round`, above).
916 "ceil" => poly0(arrow(t_float(), t_float())),
917 "floor" => poly0(arrow(t_float(), t_float())),
918 // vminst.ml:2319 `PrimitiveShowFloat`.
919 "show-float" => poly0(arrow(t_float(), t_string())),
920
921 // vminst.ml:2159 `PrimitiveStringByteLength`.
922 "string-byte-length" => poly0(arrow(t_string(), t_int())),
923 // vminst.ml:2123 `PrimitiveStringSubBytes`.
924 "string-sub-bytes" => poly0(arrows(vec![t_string(), t_int(), t_int()], t_string())),
925 // vminst.ml:2196 `PrimitiveStringUnexplode`.
926 "string-unexplode" => poly0(arrow(list(t_int()), t_string())),
927
928 // vminst.ml:2056 `PrimitiveDisplayMessage`.
929 "display-message" => poly0(arrow(t_string(), t_unit())),
930 // vminst.ml:3133 `AbortWithMessage` — a fresh-per-instantiation type
931 // variable (same pattern as `!`/`::`'s `poly1` above). ZERO-EDIT
932 // row: dev-0-1-0's vminst.ml entry differs only in notation (`let
933 // bid = …` vs `forall "a"`), not in the type — both generations are
934 // the identical `∀a. string -> a`, so this arm serves both versions
935 // unchanged.
936 "abort-with-message" => poly1(|a| arrow(t_string(), a)),
937
938 // ==== graphics primitives — paths, fill/stroke, and the `inline-graphics` on-page sink.
939 // Argument order transcribed from `tools/gencode/vminst.ml`:
940 // `start-path` :713, `line-to` :727, `terminate-path` :759,
941 // `close-with-line` :773, `fill` :2398, `stroke` :2381,
942 // `inline-graphics` :1872. ====================================
943 //
944 "start-path" => poly0(arrow(t_point(), t_prepath())),
945 "line-to" => poly0(arrows(vec![t_point(), t_prepath()], t_prepath())),
946 // finishes an OPEN subpath.
947 "terminate-path" => poly0(arrow(t_prepath(), t_path())),
948 // closes with a straight segment back to the subpath's start.
949 "close-with-line" => poly0(arrow(t_prepath(), t_path())),
950 // even-odd filled region.
951 "fill" => poly0(arrows(vec![t_color(), t_path()], t_graphics())),
952 "stroke" => poly0(arrows(vec![t_length(), t_color(), t_path()], t_graphics())),
953 // a box of size (w, h, d) carrying the callback's graphics, the
954 // minimal on-page sink for `graphics` values (`primitives.rs`'s body
955 // notes an eager-callback-at-origin caveat this signature doesn't
956 // capture). The callback's RESULT forks per version via
957 // `t_graphics_output`; row stays untagged (`Both`) — see
958 // `coerce_graphics_result`'s doc comment for why.
959 "inline-graphics" => poly0(arrows(
960 vec![
961 t_length(),
962 t_length(),
963 t_length(),
964 arrow(t_point(), t_graphics_output(version)),
965 ],
966 t_inline_boxes(),
967 )),
968
969 // v0.0.6 vminst.ml:539 (`tRULESF` at primitives.cppo.ml:141);
970 // dev-0-1-0 inlines the same shape with a bare `tGR` result,
971 // vminst.ml:487-489 — the ruled-grid primitive; same
972 // per-version callback-result fork as `inline-graphics`.
973 "tabular" => poly0(arrows(
974 vec![
975 list(list(t_cell())),
976 arrows(
977 vec![list(t_length()), list(t_length())],
978 t_graphics_output(version),
979 ),
980 ],
981 t_inline_boxes(),
982 )),
983
984 // vminst.ml:1891 `BackendInlineGraphicsOuter` — the callback's args
985 // are (the resolved width, then the placed point). Same
986 // per-version callback-result fork.
987 "inline-graphics-outer" => poly0(arrows(
988 vec![
989 t_length(),
990 t_length(),
991 arrows(vec![t_length(), t_point()], t_graphics_output(version)),
992 ],
993 t_inline_boxes(),
994 )),
995
996 // ==== gr.satyh prims — signatures from `tools/gencode/vminst.ml`:
997 // `bezier-to` :742, `close-with-bezier` :787, `shift-path` :663,
998 // `linear-transform-path` :678, `shift-graphics` :2451,
999 // `linear-transform-graphics` :2432, `get-graphics-bbox` :2466,
1000 // `dashed-stroke` :2414, `draw-text` :2363. ====================
1001 //
1002 "bezier-to" => poly0(arrows(
1003 vec![t_point(), t_point(), t_point(), t_prepath()],
1004 t_prepath(),
1005 )),
1006 "close-with-bezier" => poly0(arrows(vec![t_point(), t_point(), t_prepath()], t_path())),
1007 "shift-path" => poly0(arrows(vec![t_point(), t_path()], t_path())),
1008 "linear-transform-path" => poly0(arrows(
1009 vec![t_float(), t_float(), t_float(), t_float(), t_path()],
1010 t_path(),
1011 )),
1012 "shift-graphics" => poly0(arrows(vec![t_point(), t_graphics()], t_graphics())),
1013 // `primitives.rs`'s body notes an eager-vs-upstream's-lazy-`cm`
1014 // stroke-width caveat this signature doesn't capture.
1015 "linear-transform-graphics" => poly0(arrows(
1016 vec![t_float(), t_float(), t_float(), t_float(), t_graphics()],
1017 t_graphics(),
1018 )),
1019 // A version fork: v0.0.6 `graphics -> point * point` (vminst.ml:2466); v0.1
1020 // `graphics -> option (point * point)` (dev-0-1-0 vminst.ml:2301) —
1021 // `graphics` is a collection under 0.1, so an empty one legitimately
1022 // has no bbox.
1023 "get-graphics-bbox" => {
1024 let bbox_ty = product(vec![t_point(), t_point()]);
1025 if version.graphics_is_collection() {
1026 poly0(arrow(t_graphics(), t_option(bbox_ty)))
1027 } else {
1028 poly0(arrow(t_graphics(), bbox_ty))
1029 }
1030 }
1031 // dev-0-1-0 vminst.ml:3119 — v0.1-only, the same mirror-guard
1032 // idiom as the 0.1-only math rows above.
1033 "unite-graphics" if version.graphics_is_collection() => {
1034 poly0(arrow(list(t_graphics()), t_graphics()))
1035 }
1036 // dev-0-1-0 vminst.ml:3105 — v0.1-only.
1037 "clip-graphics-by-path" if version.graphics_is_collection() => {
1038 poly0(arrows(vec![t_path(), t_graphics()], t_graphics()))
1039 }
1040 // vminst.ml:696 `PathGetBoundingBox`.
1041 "get-path-bbox" => poly0(arrow(t_path(), product(vec![t_point(), t_point()]))),
1042 // like `stroke` (width first), with the dash pattern inserted next.
1043 "dashed-stroke" => poly0(arrows(
1044 vec![t_length(), t_dash(), t_color(), t_path()],
1045 t_graphics(),
1046 )),
1047 // FAITHFUL (`primitives.rs`'s `prim_draw_text`).
1048 "draw-text" => poly0(arrows(vec![t_point(), t_inline_boxes()], t_graphics())),
1049
1050 // ==== pervasives.satyh unblockers ====
1051 //
1052 // vminst.ml:2020 `PrimitiveGetNaturalMetrics`.
1053 "get-natural-metrics" => poly0(arrow(
1054 t_inline_boxes(),
1055 product(vec![t_length(), t_length(), t_length()]),
1056 )),
1057 // vminst.ml:1787 `BackendOuterFrame`. STAND-IN body — see
1058 // primitives.rs's `prim_inline_frame_outer` doc comment.
1059 "inline-frame-outer" => poly0(arrows(
1060 vec![t_paddings(), t_deco(version), t_inline_boxes()],
1061 t_inline_boxes(),
1062 )),
1063 // vminst.ml:1807 `BackendInnerFrame` — same signature as
1064 // `inline-frame-outer` above.
1065 "inline-frame-inner" => poly0(arrows(
1066 vec![t_paddings(), t_deco(version), t_inline_boxes()],
1067 t_inline_boxes(),
1068 )),
1069 // vminst.ml:1661 `PrimitiveSetManualRising`.
1070 "set-manual-rising" => poly0(arrow(t_length(), arrow(t_context(), t_context()))),
1071 // vminst.ml:1908 `BackendScriptGuard`. STAND-IN body (identity) —
1072 // see primitives.rs's `prim_script_guard` doc comment.
1073 "script-guard" => poly0(arrows(vec![t_script(), t_inline_boxes()], t_inline_boxes())),
1074 // vminst.ml:1969 `BackendDiscretionary`, params `(pb, hblst0,
1075 // hblst1, hblst2)`.
1076 "discretionary" => poly0(arrows(
1077 vec![
1078 t_int(),
1079 t_inline_boxes(),
1080 t_inline_boxes(),
1081 t_inline_boxes(),
1082 ],
1083 t_inline_boxes(),
1084 )),
1085
1086 // ==== Tier-2 decoration/graphics packages ====
1087 //
1088 // vminst.ml:1739 `PrimitiveGetAxisHeight`. STAND-IN body — see
1089 // `primitives.rs`'s `prim_get_axis_height` doc comment.
1090 "get-axis-height" => poly0(arrow(t_context(), t_length())),
1091
1092 // ==== hooks / annotations / cross-references ====
1093 //
1094 // vminstdef.yaml:576.
1095 "hook-page-break" => poly0(arrow(
1096 arrows(vec![t_pbinfo(), t_point()], t_unit()),
1097 t_inline_boxes(),
1098 )),
1099 // vminst.ml:632 `BackendHookPageBreakBlock` — the block-level analog
1100 // of `hook-page-break` above, FAITHFUL: see `primitives.rs`'s
1101 // `prim_hook_page_break_block`. `stdjareport.satyh`'s `document`
1102 // unblocker.
1103 "hook-page-break-block" => poly0(arrow(
1104 arrows(vec![t_pbinfo(), t_point()], t_unit()),
1105 t_block_boxes(),
1106 )),
1107 // vminstdef.yaml:1793.
1108 "register-cross-reference" => poly0(arrows(vec![t_string(), t_string()], t_unit())),
1109 // vminstdef.yaml:1808.
1110 "get-cross-reference" => poly0(arrow(t_string(), t_option(t_string()))),
1111 // vminst.ml:3043 `BackendProbeCrossReference` — `get-cross-reference`
1112 // without the recorded miss. FAITHFUL.
1113 "probe-cross-reference" => poly0(arrow(t_string(), t_option(t_string()))),
1114
1115 // ==== annot.satyh's
1116 // prim surface. STAND-IN bodies — see primitives.rs's
1117 // `prim_get_leftmost_script`/`prim_inline_frame_breakable`/
1118 // `prim_register_destination` doc comments. ====
1119 //
1120 // vminstdef.yaml:1754/1767.
1121 "get-leftmost-script" => poly0(arrow(t_inline_boxes(), t_option(t_script()))),
1122 "get-rightmost-script" => poly0(arrow(t_inline_boxes(), t_option(t_script()))),
1123 // vminstdef.yaml:1672.
1124 "inline-frame-breakable" => poly0(arrows(
1125 vec![t_paddings(), t_decoset(version), t_inline_boxes()],
1126 t_inline_boxes(),
1127 )),
1128 // vminstdef.yaml:2738.
1129 "register-destination" => poly0(arrows(vec![t_string(), t_point()], t_unit())),
1130 // vminstdef.yaml:2753/2773.
1131 "register-link-to-uri" => poly0(arrows(
1132 vec![
1133 t_string(),
1134 t_point(),
1135 t_length(),
1136 t_length(),
1137 t_length(),
1138 t_option(product(vec![t_length(), t_color()])),
1139 ],
1140 t_unit(),
1141 )),
1142 "register-link-to-location" => poly0(arrows(
1143 vec![
1144 t_string(),
1145 t_point(),
1146 t_length(),
1147 t_length(),
1148 t_length(),
1149 t_option(product(vec![t_length(), t_color()])),
1150 ],
1151 t_unit(),
1152 )),
1153
1154 // ==== the faithful `Value::Math` primitive layer `math.satyh` is
1155 // built out of. Signatures transcribed from
1156 // `tools/gencode/vminst.ml` (cited per entry). ====
1157 //
1158 // vminst.ml:388 `BackendMathChar`; v0.1 (dev-0-1-0 vminst.ml:358) —
1159 // ctx ACCEPTED, not stored on the atom.
1160 "math-char" => {
1161 if version.math_is_split() {
1162 poly0(arrows(
1163 vec![t_context(), t_math_class(), t_string()],
1164 t_math_boxes(),
1165 ))
1166 } else {
1167 poly0(arrows(vec![t_math_class(), t_string()], t_math()))
1168 }
1169 }
1170 // vminst.ml:405 `BackendMathBigChar` — same shape, large-operator
1171 // size class (layout does not yet upscale it; it renders the same
1172 // size as `math-char`). v0.1 (vminst.ml:374): same fork as `math-char`.
1173 "math-big-char" => {
1174 if version.math_is_split() {
1175 poly0(arrows(
1176 vec![t_context(), t_math_class(), t_string()],
1177 t_math_boxes(),
1178 ))
1179 } else {
1180 poly0(arrows(vec![t_math_class(), t_string()], t_math()))
1181 }
1182 }
1183 // vminst.ml:422 `BackendMathCharWithKern`; v0.1 (vminst.ml:390):
1184 // same ctx-prepended fork as `math-char`.
1185 "math-char-with-kern" => {
1186 if version.math_is_split() {
1187 poly0(arrows(
1188 vec![
1189 t_context(),
1190 t_math_class(),
1191 t_string(),
1192 t_math_kern_func(),
1193 t_math_kern_func(),
1194 ],
1195 t_math_boxes(),
1196 ))
1197 } else {
1198 poly0(arrows(
1199 vec![
1200 t_math_class(),
1201 t_string(),
1202 t_math_kern_func(),
1203 t_math_kern_func(),
1204 ],
1205 t_math(),
1206 ))
1207 }
1208 }
1209 // vminst.ml:445 `BackendMathBigCharWithKern` — same shape. v0.1
1210 // (vminst.ml:411): same fork as `math-char-with-kern`.
1211 "math-big-char-with-kern" => {
1212 if version.math_is_split() {
1213 poly0(arrows(
1214 vec![
1215 t_context(),
1216 t_math_class(),
1217 t_string(),
1218 t_math_kern_func(),
1219 t_math_kern_func(),
1220 ],
1221 t_math_boxes(),
1222 ))
1223 } else {
1224 poly0(arrows(
1225 vec![
1226 t_math_class(),
1227 t_string(),
1228 t_math_kern_func(),
1229 t_math_kern_func(),
1230 ],
1231 t_math(),
1232 ))
1233 }
1234 }
1235 // vminst.ml:193 `BackendMathConcat`. v0.1 (vminst.ml:181): same
1236 // shape, `mb` instead of `math`.
1237 "math-concat" => {
1238 if version.math_is_split() {
1239 poly0(arrows(vec![t_math_boxes(), t_math_boxes()], t_math_boxes()))
1240 } else {
1241 poly0(arrows(vec![t_math(), t_math()], t_math()))
1242 }
1243 }
1244 // vminst.ml:209 `BackendMathGroup`. v0.1 (vminst.ml:194): same
1245 // shape, `mb` instead of `math`.
1246 "math-group" => {
1247 if version.math_is_split() {
1248 poly0(arrows(
1249 vec![t_math_class(), t_math_class(), t_math_boxes()],
1250 t_math_boxes(),
1251 ))
1252 } else {
1253 poly0(arrows(
1254 vec![t_math_class(), t_math_class(), t_math()],
1255 t_math(),
1256 ))
1257 }
1258 }
1259 // vminst.ml:226 `BackendMathSuperscript`. v0.1 (vminst.ml:208): the
1260 // script argument is a context-taking callback, evaluated under
1261 // `enter_script`.
1262 "math-sup" => {
1263 if version.math_is_split() {
1264 poly0(arrows(
1265 vec![t_context(), t_math_boxes(), t_math_script_fn()],
1266 t_math_boxes(),
1267 ))
1268 } else {
1269 poly0(arrows(vec![t_math(), t_math()], t_math()))
1270 }
1271 }
1272 // vminst.ml:242 `BackendMathSubscript`. v0.1 (vminst.ml:228): same
1273 // shape as `math-sup`.
1274 "math-sub" => {
1275 if version.math_is_split() {
1276 poly0(arrows(
1277 vec![t_context(), t_math_boxes(), t_math_script_fn()],
1278 t_math_boxes(),
1279 ))
1280 } else {
1281 poly0(arrows(vec![t_math(), t_math()], t_math()))
1282 }
1283 }
1284 // vminst.ml:258 `BackendMathFraction`. v0.1 (vminst.ml:248):
1285 // ctx prepended, `mb` instead of `math`.
1286 "math-frac" => {
1287 if version.math_is_split() {
1288 poly0(arrows(
1289 vec![t_context(), t_math_boxes(), t_math_boxes()],
1290 t_math_boxes(),
1291 ))
1292 } else {
1293 poly0(arrows(vec![t_math(), t_math()], t_math()))
1294 }
1295 }
1296 // vminst.ml:274 `BackendMathRadical`. v0.1 (vminst.ml:262):
1297 // ctx prepended, `mb` instead of `math`.
1298 "math-radical" => {
1299 if version.math_is_split() {
1300 poly0(arrows(
1301 vec![t_context(), t_option(t_math_boxes()), t_math_boxes()],
1302 t_math_boxes(),
1303 ))
1304 } else {
1305 poly0(arrows(vec![t_option(t_math()), t_math()], t_math()))
1306 }
1307 }
1308 // vminst.ml:352 `BackendMathLowerLimit`. v0.1
1309 // (vminst.ml:338): same script-callback shape as `math-sup`.
1310 "math-lower" => {
1311 if version.math_is_split() {
1312 poly0(arrows(
1313 vec![t_context(), t_math_boxes(), t_math_script_fn()],
1314 t_math_boxes(),
1315 ))
1316 } else {
1317 poly0(arrows(vec![t_math(), t_math()], t_math()))
1318 }
1319 }
1320 // vminst.ml:336 `BackendMathUpperLimit`. v0.1
1321 // (vminst.ml:318): same script-callback shape as `math-sup`.
1322 "math-upper" => {
1323 if version.math_is_split() {
1324 poly0(arrows(
1325 vec![t_context(), t_math_boxes(), t_math_script_fn()],
1326 t_math_boxes(),
1327 ))
1328 } else {
1329 poly0(arrows(vec![t_math(), t_math()], t_math()))
1330 }
1331 }
1332 // vminst.ml:368 `BackendMathPullInScripts`.
1333 "math-pull-in-scripts" => poly0(arrows(
1334 vec![
1335 t_math_class(),
1336 t_math_class(),
1337 arrows(vec![t_option(t_math()), t_option(t_math())], t_math()),
1338 ],
1339 t_math(),
1340 )),
1341 // vminst.ml:488 `BackendMathColor`.
1342 "math-color" => poly0(arrows(vec![t_color(), t_math()], t_math())),
1343 // vminst.ml:504 `BackendMathCharClass`.
1344 "math-char-class" => poly0(arrows(vec![t_math_char_class(), t_math()], t_math())),
1345 // vminst.ml:111 `BackendMathVariantCharDirect`.
1346 "math-variant-char" => poly0(arrows(
1347 vec![t_math_class(), t_math_variant_style()],
1348 t_math(),
1349 )),
1350 // No bundled `.satyh` consumer, but v0.0.6-shaped (this arm
1351 // plus `get-left-math-class`/`get-right-math-class` below, the
1352 // boundary-class introspection pair). v0.1 (vminst.ml:36): the body
1353 // applies the selector once per each of the 9 `MathCharClass`
1354 // values and inserts into `math_variant_char_map` (eager
1355 // materialization of upstream's stored selector).
1356 "set-math-variant-char" => {
1357 if version.math_is_split() {
1358 poly0(arrows(
1359 vec![t_int(), arrow(t_math_char_class(), t_int()), t_context()],
1360 t_context(),
1361 ))
1362 } else {
1363 poly0(arrows(
1364 vec![t_math_char_class(), t_int(), t_int(), t_context()],
1365 t_context(),
1366 ))
1367 }
1368 }
1369 // v0.1 (vminst.ml:128) — ctx dropped.
1370 "get-left-math-class" => {
1371 if version.math_is_split() {
1372 poly0(arrow(t_math_boxes(), t_option(t_math_class())))
1373 } else {
1374 poly0(arrows(
1375 vec![t_context(), t_math()],
1376 t_option(t_math_class()),
1377 ))
1378 }
1379 }
1380 // v0.1 (vminst.ml:146): same fork as `get-left-math-class`.
1381 "get-right-math-class" => {
1382 if version.math_is_split() {
1383 poly0(arrow(t_math_boxes(), t_option(t_math_class())))
1384 } else {
1385 poly0(arrows(
1386 vec![t_context(), t_math()],
1387 t_option(t_math_class()),
1388 ))
1389 }
1390 }
1391 // vminst.ml:294 `BackendMathParen`. v0.1 (vminst.ml:279):
1392 // ctx prepended.
1393 "math-paren" => {
1394 if version.math_is_split() {
1395 poly0(arrows(
1396 vec![
1397 t_context(),
1398 t_paren(version),
1399 t_paren(version),
1400 t_math_boxes(),
1401 ],
1402 t_math_boxes(),
1403 ))
1404 } else {
1405 poly0(arrows(
1406 vec![t_paren(version), t_paren(version), t_math()],
1407 t_math(),
1408 ))
1409 }
1410 }
1411 // vminst.ml:314 `BackendMathParenWithMiddle`. v0.1
1412 // (vminst.ml:297): ctx prepended.
1413 "math-paren-with-middle" => {
1414 if version.math_is_split() {
1415 poly0(arrows(
1416 vec![
1417 t_context(),
1418 t_paren(version),
1419 t_paren(version),
1420 t_paren(version),
1421 list(t_math_boxes()),
1422 ],
1423 t_math_boxes(),
1424 ))
1425 } else {
1426 poly0(arrows(
1427 vec![
1428 t_paren(version),
1429 t_paren(version),
1430 t_paren(version),
1431 list(t_math()),
1432 ],
1433 t_math(),
1434 ))
1435 }
1436 }
1437 // vminst.ml:468 `BackendMathText` (named `text-in-math`).
1438 "text-in-math" => poly0(arrows(
1439 vec![t_math_class(), arrow(t_context(), t_inline_boxes())],
1440 t_math(),
1441 )),
1442 // vminst.ml:61 `PrimitiveConvertStringForMath`. STAND-IN
1443 // body (see `primitives.rs`'s `prim_convert_string_for_math`).
1444 "convert-string-for-math" => poly0(arrows(
1445 vec![t_context(), t_math_char_class(), t_string()],
1446 t_string(),
1447 )),
1448 // vminst.ml:520 `BackendEmbeddedMath` (named `embed-math`) — the
1449 // bridge to the page; `\math` (math.satyh:439) wraps this. v0.1
1450 // (vminst.ml:472): `as_math_boxes` then the SAME
1451 // `layout_math_value`, the whole MATH-engine reuse in one primitive.
1452 "embed-math" => {
1453 if version.math_is_split() {
1454 poly0(arrows(vec![t_context(), t_math_boxes()], t_inline_boxes()))
1455 } else {
1456 poly0(arrows(vec![t_context(), t_math()], t_inline_boxes()))
1457 }
1458 }
1459 // vminst.ml:77 `PrimitiveSetMathCommand` — installs the default
1460 // command a bare `${…}`-in-text dispatches to. FAITHFUL, see
1461 // `primitives.rs`'s `prim_set_math_command`.
1462 "set-math-command" => poly0(arrow(
1463 inline_cmd(vec![mandatory(t_math())]),
1464 arrow(t_context(), t_context()),
1465 )),
1466 // `PrimitiveSetMathFont`, version-forked in its FIRST argument:
1467 // 0.0.6 `vminstdef.yaml:1364` takes the math font's ABBREV (a plain
1468 // string); saphe-split `tools/gencode/vminst.ml:1462` takes the
1469 // opaque [`t_font_key`] handle (its body writes
1470 // `ctx.math_font_key = Some(mathkey)`).
1471 "set-math-font" => {
1472 let dom = match version {
1473 RustyfiVersion::V0_1 => t_font_key(),
1474 _ => t_string(),
1475 };
1476 poly0(arrow(dom, arrow(t_context(), t_context())))
1477 }
1478 // LOCAL, non-upstream, V0_1-only — the port's stand-in for
1479 // upstream's internal `LoadSingleFont{path}` node, which has no
1480 // surface name upstream. Its argument is the port's font-store key
1481 // standing in for upstream's font-file path — see `primitives.rs`'s
1482 // `prim_load_single_font` for why. Same LOCAL-primitive precedent as
1483 // `set-font-key` (`primitives.rs`'s `prims!` table).
1484 "load-single-font" if version == RustyfiVersion::V0_1 => {
1485 poly0(arrow(t_string(), t_font_key()))
1486 }
1487 // vminst.ml:173 `BackendSpaceBetweenMaths`. STAND-IN body,
1488 // used by `math.satyh`'s `+align`. v0.1 (vminst.ml:164): shared
1489 // body, only the extractor forks.
1490 "space-between-maths" => {
1491 if version.math_is_split() {
1492 poly0(arrows(
1493 vec![t_context(), t_math_boxes(), t_math_boxes()],
1494 t_option(t_inline_boxes()),
1495 ))
1496 } else {
1497 poly0(arrows(
1498 vec![t_context(), t_math(), t_math()],
1499 t_option(t_inline_boxes()),
1500 ))
1501 }
1502 }
1503 // vminst.ml:1677 `PrimitiveRaiseInline` (name inferred from usage,
1504 // not independently confirmed against a `~name:` line). STAND-IN
1505 // body — see `primitives.rs`'s `prim_raise_inline` doc comment (no
1506 // per-box vertical-offset wrapper in the line model yet outside
1507 // `PureHorzBox::Math`).
1508 "raise-inline" => poly0(arrows(vec![t_length(), t_inline_boxes()], t_inline_boxes())),
1509 // vminst.ml:973 `PrimitiveEmbeddedVertBreakable` (named
1510 // `embed-block-breakable`). STAND-IN body — no nested
1511 // page-breakable block-in-inline box yet (see
1512 // `primitives.rs`'s `prim_embed_block_breakable`).
1513 "embed-block-breakable" => {
1514 poly0(arrows(vec![t_context(), t_block_boxes()], t_inline_boxes()))
1515 }
1516 // `gr.satyh`-adjacent path combinator; `math.satyh`'s `\norm`
1517 // unions two vertical bars into one path. FAITHFUL: a real path
1518 // union (concatenation of subpaths — see `primitives.rs`'s
1519 // `prim_unite_path`).
1520 "unite-path" => poly0(arrows(vec![t_path(), t_path()], t_path())),
1521 // vminst.ml:1291 `PrimitiveSetMinGapOfLines` — a *different*
1522 // context field than `set-leading` (see that primitive's own
1523 // comment); `math.satyh`'s `+math-list` calls this. STAND-IN body:
1524 // no separate `min_gap_of_lines` field on `Context` yet, so this is
1525 // a same-shape passthrough (see `primitives.rs`'s
1526 // `prim_set_min_gap_of_lines`).
1527 "set-min-gap-of-lines" => poly0(arrow(t_length(), arrow(t_context(), t_context()))),
1528
1529 // ==== (rows 1-10): the
1530 // context-setter + box-combinator prims `code.satyh`/`itemize.satyh`
1531 // need. Signatures transcribed from `tools/gencode/vminst.ml` (cited
1532 // per entry). ====
1533 //
1534 // vminst.ml:1603 `PrimitiveSetTextColor`. FAITHFUL
1535 // (`primitives.rs`'s `prim_set_text_color`).
1536 "set-text-color" => poly0(arrow(t_color(), arrow(t_context(), t_context()))),
1537 // vminst.ml:1618 `PrimitiveGetTextColor`. FAITHFUL — `itemize.satyh`
1538 // feeds this straight into `fill`, see `primitives.rs`'s
1539 // `prim_get_text_color`/`make_color_value`.
1540 "get-text-color" => poly0(arrow(t_context(), t_color())),
1541 // vminst.ml:1692 `PrimitiveSetHyphenPenalty`. FAITHFUL store;
1542 // consumed by `flush_word`'s hyphenation injection when a
1543 // dictionary is installed.
1544 "set-hyphen-penalty" => poly0(arrow(t_int(), arrow(t_context(), t_context()))),
1545 // vminstdef.yaml:1163-1177 `PrimitiveSetHyphenMin`, params
1546 // `(left_hyphen_min, right_hyphen_min)`.
1547 "set-hyphen-min" => poly0(arrows(vec![t_int(), t_int(), t_context()], t_context())),
1548 // vminst.ml:1309 `PrimitiveSetSpaceRatio`, params `(natural, shrink,
1549 // stretch)`. FAITHFUL: read by `text_to_boxes`'s interword glue.
1550 "set-space-ratio" => poly0(arrows(
1551 vec![t_float(), t_float(), t_float(), t_context()],
1552 t_context(),
1553 )),
1554 // vminst.ml's `PrimitiveSetSpaceRatioBetweenScripts`, params
1555 // `(natural, shrink, stretch, then the two adjacent scripts)`. Used
1556 // by slydifi's arctic theme. STAND-IN — see `primitives.rs`'s
1557 // `prim_set_space_ratio_between_scripts` for why the observable
1558 // output still matches upstream.
1559 "set-space-ratio-between-scripts" => poly0(arrows(
1560 vec![
1561 t_float(),
1562 t_float(),
1563 t_float(),
1564 t_script(),
1565 t_script(),
1566 t_context(),
1567 ],
1568 t_context(),
1569 )),
1570 // vminst.ml:2269 `PrimitiveSplitIntoLines`. FAITHFUL — pure string
1571 // op, see `primitives.rs`'s `prim_split_into_lines`.
1572 "split-into-lines" => poly0(arrow(t_string(), list(product(vec![t_int(), t_string()])))),
1573 // vminst.ml:1090 `PrimitiveBlockFrameBreakable`. STAND-IN:
1574 // reduced-width + left-indent inner block, `deco-set` dropped —
1575 // see `primitives.rs`'s `prim_block_frame_breakable`.
1576 "block-frame-breakable" => poly0(arrows(
1577 vec![
1578 t_context(),
1579 t_paddings(),
1580 t_decoset(version),
1581 arrow(t_context(), t_block_boxes()),
1582 ],
1583 t_block_boxes(),
1584 )),
1585 // vminst.ml:1145 `PrimitiveEmbeddedVertTop` (named `embed-block-top`).
1586 // STAND-IN: top-aligned `PureHorzBox::EmbeddedBlock` — see
1587 // `primitives.rs`'s `prim_embed_block_top`.
1588 "embed-block-top" => poly0(arrows(
1589 vec![t_context(), t_length(), arrow(t_context(), t_block_boxes())],
1590 t_inline_boxes(),
1591 )),
1592 // vminst.ml:1185 `PrimitiveEmbeddedVertBottom` (named
1593 // `embed-block-bottom`). Same STAND-IN shape as `embed-block-top`
1594 // above — see `primitives.rs`'s `prim_embed_block_bottom`.
1595 "embed-block-bottom" => poly0(arrows(
1596 vec![t_context(), t_length(), arrow(t_context(), t_block_boxes())],
1597 t_inline_boxes(),
1598 )),
1599 // vminst.ml:1229 `PrimitiveLineStackBottom` (named
1600 // `line-stack-bottom`). FAITHFUL — see `primitives.rs`'s
1601 // `prim_line_stack_bottom`.
1602 "line-stack-bottom" => poly0(arrow(list(t_inline_boxes()), t_inline_boxes())),
1603 // vminstdef.yaml:1109 `BackendLineStackTop` — same shape as
1604 // `line-stack-bottom`, differing only in which stacked line's
1605 // baseline the result carries. FAITHFUL — see `prim_line_stack_top`.
1606 "line-stack-top" => poly0(arrow(list(t_inline_boxes()), t_inline_boxes())),
1607 // vminst.ml:1130 `PrimitiveAddFootnote`. FAITHFUL — see
1608 // primitives.rs's prim_add_footnote (footnote float accumulator).
1609 "add-footnote" => poly0(arrow(t_block_boxes(), t_inline_boxes())),
1610 // `PrimitiveSetFont`, version-forked in its SECOND argument only:
1611 // 0.0.6 `vminstdef.yaml:1335` (`tFONT = string * float * float`);
1612 // saphe-split `tools/gencode/vminst.ml:1433`
1613 // (`tFONTWR = font * float * float`). See [`t_font_with_ratio`].
1614 "set-font" => poly0(arrows(
1615 vec![t_script(), t_font_with_ratio(version), t_context()],
1616 t_context(),
1617 )),
1618 // 0.0.6 `vminstdef.yaml:1350` — the reader for the slot `set-font`
1619 // writes, so it forks at the SAME head via the same
1620 // [`t_font_with_ratio`]. FAITHFUL — see `primitives.rs`'s
1621 // `prim_get_font_v006`.
1622 "get-font" => poly0(arrows(
1623 vec![t_script(), t_context()],
1624 t_font_with_ratio(version),
1625 )),
1626 // `stdja:116`; orphan #4 — not in any vminst.ml table this port has
1627 // transcribed, so no upstream line is cited. STAND-IN: accepted and
1628 // dropped, like `set-math-command`/`set-math-font` above — see
1629 // `primitives.rs`'s `prim_set_code_text_command`.
1630 "set-code-text-command" => poly0(arrow(
1631 inline_cmd(vec![mandatory(t_string())]),
1632 arrow(t_context(), t_context()),
1633 )),
1634 // vminst.ml:2040 `PrimitiveGetNaturalLength` — `get-natural-width`'s
1635 // block sibling. FAITHFUL: block height+depth summed to one length
1636 // via `measure_block` (rustyfi-backend) — see `primitives.rs`'s
1637 // `prim_get_natural_length`.
1638 "get-natural-length" => poly0(arrow(t_block_boxes(), t_length())),
1639
1640 // ==== the remaining stdja.satyh primitives, not grouped above.
1641 // `set-dominant-wide-script`/
1642 // `set-dominant-narrow-script`/`set-language` (rows 15/17/18) are
1643 // FAITHFUL stores with real getter round-trips just
1644 // below; `set-every-word-break` is a STAND-IN (accepted, dropped) —
1645 // see its `primitives.rs` doc comment. ====
1646 //
1647 // vminst.ml:1511 `PrimitiveSetDominantWideScript`.
1648 "set-dominant-wide-script" => poly0(arrow(t_script(), arrow(t_context(), t_context()))),
1649 // vminst.ml:1539 `PrimitiveSetDominantNarrowScript`: same shape.
1650 "set-dominant-narrow-script" => poly0(arrow(t_script(), arrow(t_context(), t_context()))),
1651 // vminst.ml:1568 `PrimitiveSetLangSys`.
1652 "set-language" => poly0(arrows(
1653 vec![t_script(), t_language(), t_context()],
1654 t_context(),
1655 )),
1656 // vminst.ml:1526/1555 `PrimitiveGetDominantWideScript`/
1657 // `...NarrowScript`. FAITHFUL.
1658 "get-dominant-wide-script" => poly0(arrow(t_context(), t_script())),
1659 "get-dominant-narrow-script" => poly0(arrow(t_context(), t_script())),
1660 // vminst.ml:1587 `PrimitiveGetLangSys`.
1661 "get-language" => poly0(arrows(vec![t_script(), t_context()], t_language())),
1662 // vminst.ml:3007 `PrimitiveSetEveryWordBreak`.
1663 "set-every-word-break" => poly0(arrows(
1664 vec![t_inline_boxes(), t_inline_boxes(), t_context()],
1665 t_context(),
1666 )),
1667 // vminstdef.yaml:2794 `BackendRegisterOutline` — a list of (depth,
1668 // title, label, is-frozen) PDF-outline entries. FAITHFUL — see
1669 // `primitives.rs`'s `prim_register_outline`.
1670 "register-outline" => poly0(arrow(
1671 list(product(vec![t_int(), t_string(), t_string(), t_bool()])),
1672 t_unit(),
1673 )),
1674 // vminstdef.yaml:1565 `PrimitiveExtract`. FAITHFUL (mirrors
1675 // `horzBox.ml`'s `extract_string`); see `primitives.rs`'s
1676 // `extract_string_pure_one`.
1677 "extract-string" => poly0(arrow(t_inline_boxes(), t_string())),
1678
1679 // ==== (text-mode-context sliver): the three PURE text-info prims. The text/html backends
1680 // (`stringify-inline`/`stringify-block`, `.satyh-text` loading) are
1681 // deliberately out of scope for this PDF port — see
1682 // `primitives.rs`'s section comment. ====
1683 //
1684 // `get-initial-text-info` — a version fork (the
1685 // `t_page_or_geometry`-style version branch, inlined since it's
1686 // one row): v0.0.6 (vminst.ml:953 `TextGetInitialTextModeContext`)
1687 // takes unit; v0.1 (dev-0-1-0 vminst.ml:906) threads the text-mode
1688 // default math command (`inline [math-text]`) + a math-scripts
1689 // stringifier into `tctxsub`. Both bodies are the same STAND-IN
1690 // (`primitives.rs`'s `prim_get_initial_text_info_v01`).
1691 "get-initial-text-info" => {
1692 if version == RustyfiVersion::V0_1 {
1693 poly0(arrows(
1694 vec![
1695 inline_cmd(vec![mandatory(t_math_text())]),
1696 arrows(
1697 vec![t_string(), t_option(t_string()), t_option(t_string())],
1698 t_string(),
1699 ),
1700 ],
1701 t_text_info(),
1702 ))
1703 } else {
1704 poly0(arrow(t_unit(), t_text_info()))
1705 }
1706 }
1707 // vminst.ml:921 `TextDeepenIndent`.
1708 "deepen-indent" => poly0(arrows(vec![t_int(), t_text_info()], t_text_info())),
1709 // vminst.ml:935 `TextBreak`.
1710 "break" => poly0(arrow(t_text_info(), t_string())),
1711
1712 // ==== 10 new v0.1-only rows, unbound under V0_0 (the same
1713 // mirror-guard idiom as the 0.1-only math rows). ====
1714 //
1715 // Bitwise ops (dev-0-1-0 vminst.ml :2495/:2477/:2527/:2541/:2513/
1716 // :2556) — `<<`/`>>` lex as ordinary opsymbol-run identifiers under
1717 // BOTH versions (`primitives.rs`'s `prims!` table comment), so only
1718 // the type table's V0_1 guard decides whether they resolve.
1719 "<<" | ">>" | "band" | "bor" | "bxor" if version == RustyfiVersion::V0_1 => {
1720 poly0(arrows(vec![t_int(), t_int()], t_int()))
1721 }
1722 "bnot" if version == RustyfiVersion::V0_1 => poly0(arrow(t_int(), t_int())),
1723 // Unicode string ops (dev-0-1-0 vminst.ml :2050/:2066/:2082) — REAL,
1724 // `primitives.rs`'s `prim_normalize_string_to_nfc`/`_nfd`/
1725 // `prim_split_grapheme_cluster`.
1726 "normalize-string-to-nfc" | "normalize-string-to-nfd"
1727 if version == RustyfiVersion::V0_1 =>
1728 {
1729 poly0(arrow(t_string(), t_string()))
1730 }
1731 "split-grapheme-cluster" if version == RustyfiVersion::V0_1 => {
1732 poly0(arrow(t_string(), list(t_string())))
1733 }
1734 // dev-0-1-0 vminst.ml:3073 — REAL, `primitives.rs`'s `prim_read_file`.
1735 //
1736 // Bound under BOTH generations, unlike its neighbours here. It was
1737 // added on the 0.0.6 DEV line, not in 0.1: `satysfi-code-printer`
1738 // 1.1.1 calls it from `+file-printer` while its own opam pins
1739 // `satysfi { >= "0.0.6-53-g2867e4d9" & < "0.1" }`, which is direct
1740 // evidence that a 0.0.6-generation compiler has it. Gating it to
1741 // `V0_1` made the whole package fail to load under 0.0.6 — an
1742 // `unbound variable 'read-file'` from a module body, so not even
1743 // reachable-code-dependent.
1744 "read-file" => poly0(arrow(t_string(), list(t_string()))),
1745 // dev-0-1-0 vminst.ml:2978 — REAL, `primitives.rs`'s
1746 // `prim_register_document_information`.
1747 "register-document-information" if version == RustyfiVersion::V0_1 => {
1748 poly0(arrow(t_doc_info_dictionary(), t_unit()))
1749 }
1750
1751 // ---- language-completeness sweep: 0.1 float comparisons
1752 // (`primitives.rs`'s `prims!` table comment on ">."/"<."/">=."/
1753 // "<=." for the upstream citation + the confirmation these are
1754 // genuinely absent from 0.0.6) — unbound under V0_0.
1755 ">." | "<." | ">=." | "<=." if version == RustyfiVersion::V0_1 => {
1756 poly0(arrows(vec![t_float(), t_float()], t_bool()))
1757 }
1758
1759 _ => return None,
1760 })
1761}
1762
1763// ============================================================================
1764// Variant type declarations (primitives.cppo.ml's `~% ( ... )` registration
1765// block, lines ~150-170).
1766// ============================================================================
1767
1768/// A user (or built-in) variant type declaration.
1769///
1770/// `param_vars` names the declaration's type parameters as concrete
1771/// placeholder [`TyVarRef`]s that appear (via `MonoType::Var`) inside
1772/// `ctors`' payload types; they are never meant to be unified against
1773/// anything directly. `VariantDecl::instantiate_ctor` is the only
1774/// sanctioned way to use a declaration: given concrete argument types for
1775/// the variant's `params` type parameters, it substitutes them for the
1776/// placeholders (matched by pointer identity, the same mechanism
1777/// `types::instantiate` uses for quantified variables — see
1778/// `types::substitute`) throughout the chosen constructor's payload type,
1779/// and returns both that payload type and the resulting
1780/// `MonoType::Variant(name, args)`.
1781///
1782/// This mirrors v0.0.6's `Typeenv.Raw.register_type`/`add_constructor`
1783/// (`primitives.cppo.ml:154-217`), which pairs a `TypeID.t` with a
1784/// `Typeenv.Data(arity)` and each constructor with a `Poly(...)` scheme
1785/// quantified over the same `bid`/`typaram` placeholders declared once for
1786/// the whole type — the same shape, just spelled with this port's
1787/// `TyVarRef`/`substitute` machinery instead of v0.0.6's `BoundID`/
1788/// `PolyBound`.
1789#[derive(Clone, Debug)]
1790pub struct VariantDecl {
1791 pub name: String,
1792 pub params: usize,
1793 pub ctors: Vec<(String, Option<MonoType>)>,
1794 pub param_vars: Vec<TyVarRef>,
1795}
1796
1797impl VariantDecl {
1798 /// Instantiate `ctor` at a fresh application `Name(args[0], args[1],
1799 /// ...)`. Returns `None` if `ctor` isn't one of this declaration's
1800 /// constructors or `args.len() != self.params`. On success, returns
1801 /// `(payload_type, result_type)`, where `payload_type` is `None` for a
1802 /// nullary constructor (like `None`).
1803 pub(crate) fn instantiate_ctor(
1804 &self,
1805 ctor: &str,
1806 args: &[MonoType],
1807 ) -> Option<(Option<MonoType>, MonoType)> {
1808 if args.len() != self.params {
1809 return None;
1810 }
1811 let (_, payload_tpl) = self.ctors.iter().find(|(n, _)| n == ctor)?;
1812 let mut var_map: HashMap<usize, MonoType> = HashMap::new();
1813 for (pv, arg) in self.param_vars.iter().zip(args.iter()) {
1814 var_map.insert(types::ptr_key(pv), arg.clone());
1815 }
1816 let row_map = HashMap::new();
1817 let payload = payload_tpl
1818 .as_ref()
1819 .map(|t| types::substitute(t, &var_map, &row_map));
1820 Some((payload, MonoType::Variant(self.name.clone(), args.to_vec())))
1821 }
1822}
1823
1824/// The variant declarations `crate::eval`'s pattern matching and (later)
1825/// the inferencer need before any user `.saty` source runs, transcribed
1826/// from `primitives.cppo.ml:154-159`:
1827///
1828/// ```ocaml
1829/// |> Typeenv.Raw.register_type "option" tyid_option (Typeenv.Data(1))
1830/// |> Typeenv.Raw.add_constructor "None" ([bid], Poly(tU)) tyid_option
1831/// |> Typeenv.Raw.add_constructor "Some" ([bid], Poly(typaram)) tyid_option
1832/// |> Typeenv.Raw.register_type "itemize" tyid_itemize (Typeenv.Data(0))
1833/// |> Typeenv.Raw.add_constructor "Item" ([], Poly(tPROD [tIT; tL (tITMZ ())])) tyid_itemize
1834/// ```
1835///
1836/// v0.0.6 gives *every* constructor a payload type, using `tU` (unit)
1837/// for `None`'s "no real payload" case; this port's `Ast::Ctor`/
1838/// `Pattern::Ctor` (ast.rs) instead represent a nullary constructor as
1839/// `None` (the Rust `Option`, not the SATySFi one!) directly, so `None`'s
1840/// declared payload here is `Option::None`, not `Some(unit)`.
1841///
1842/// Takes the target `version` explicitly; mirrors the `base_env`/
1843/// `primitive_type` split above.
1844pub fn builtin_variants_with_version(version: RustyfiVersion) -> Vec<VariantDecl> {
1845 let option_param = types::new_ty_var(0);
1846 let option_decl = VariantDecl {
1847 name: "option".to_string(),
1848 params: 1,
1849 ctors: vec![
1850 ("None".to_string(), None),
1851 (
1852 "Some".to_string(),
1853 Some(MonoType::Var(option_param.clone())),
1854 ),
1855 ],
1856 param_vars: vec![option_param],
1857 };
1858
1859 let itemize_decl = VariantDecl {
1860 name: "itemize".to_string(),
1861 params: 0,
1862 ctors: vec![(
1863 "Item".to_string(),
1864 Some(product(vec![
1865 t_inline_text(),
1866 list(MonoType::Variant("itemize".to_string(), Vec::new())),
1867 ])),
1868 )],
1869 param_vars: Vec::new(),
1870 };
1871
1872 // `color` — nullary variant, `primitives.cppo.ml:187-190`.
1873 // Unblocks `color.satyh`'s `Color.rgb`/`Color.gray`/`Color.cmyk`
1874 // constructor wrappers; `fill`/`stroke` also consume it.
1875 let color_decl = VariantDecl {
1876 name: "color".to_string(),
1877 params: 0,
1878 ctors: vec![
1879 ("Gray".to_string(), Some(t_float())),
1880 (
1881 "RGB".to_string(),
1882 Some(product(vec![t_float(), t_float(), t_float()])),
1883 ),
1884 (
1885 "CMYK".to_string(),
1886 Some(product(vec![t_float(), t_float(), t_float(), t_float()])),
1887 ),
1888 ],
1889 param_vars: Vec::new(),
1890 };
1891
1892 // `script` (pervasives.satyh's `\SATySFi`/`\LaTeX`/`\TeX`, via
1893 // `script-guard`) — nullary variant, upstream's real surface constructor
1894 // set (`primitives.cppo.ml:192-196`). `script-guard`'s stand-in body
1895 // (primitives.rs) never inspects which constructor it got; the full set
1896 // is registered anyway so the TYPE is faithful even though the behavior
1897 // isn't yet.
1898 let script_decl = VariantDecl {
1899 name: "script".to_string(),
1900 params: 0,
1901 ctors: vec![
1902 ("HanIdeographic".to_string(), None),
1903 ("Kana".to_string(), None),
1904 ("Latin".to_string(), None),
1905 ("OtherScript".to_string(), None),
1906 ],
1907 param_vars: Vec::new(),
1908 };
1909
1910 // `language` (`charBasis.ml`'s `language_system`) — nullary variant,
1911 // `set-language`'s 2nd argument (`stdja.satyh`'s `set-language Kana
1912 // Japanese`), stored per script in `Context::langsys_scheme` and read
1913 // back by `get-language` (primitives.rs).
1914 let language_decl = VariantDecl {
1915 name: "language".to_string(),
1916 params: 0,
1917 ctors: vec![
1918 ("Japanese".to_string(), None),
1919 ("English".to_string(), None),
1920 ("NoLanguageSystem".to_string(), None),
1921 ],
1922 param_vars: Vec::new(),
1923 };
1924
1925 // `page` — nullary variant, the exact constructor set at
1926 // `primitives.cppo.ml:204-212`: 8 nullary paper-size constants plus
1927 // `UserDefinedPaper`. `page-break`'s first argument; `as_page`
1928 // (`primitives.rs`) maps each ctor to a backend `PaperSize`.
1929 //
1930 // GONE in v0.1 upstream (no replacement ADT — paper sizes are a
1931 // plain `length * length` tuple there, see `t_page_or_geometry`), so
1932 // this declaration is gated on
1933 // `has_page_adt()` below rather than being unconditionally registered.
1934 let page_decl = VariantDecl {
1935 name: "page".to_string(),
1936 params: 0,
1937 ctors: vec![
1938 ("A0Paper".to_string(), None),
1939 ("A1Paper".to_string(), None),
1940 ("A2Paper".to_string(), None),
1941 ("A3Paper".to_string(), None),
1942 ("A4Paper".to_string(), None),
1943 ("A5Paper".to_string(), None),
1944 ("USLetter".to_string(), None),
1945 ("USLegal".to_string(), None),
1946 (
1947 "UserDefinedPaper".to_string(),
1948 Some(product(vec![t_length(), t_length()])),
1949 ),
1950 ],
1951 param_vars: Vec::new(),
1952 };
1953
1954 // `cell` — nullary variant, transcribed from `primitives.cppo.ml:214-217`.
1955 // `EmptyCell`'s payload is `None` (this port's nullary-constructor
1956 // spelling, see this fn's doc comment), matching upstream's `Poly(tU)`
1957 // "no real payload" case.
1958 let cell_decl = VariantDecl {
1959 name: "cell".to_string(),
1960 params: 0,
1961 ctors: vec![
1962 (
1963 "NormalCell".to_string(),
1964 Some(product(vec![t_paddings(), t_inline_boxes()])),
1965 ),
1966 ("EmptyCell".to_string(), None),
1967 (
1968 "MultiCell".to_string(),
1969 Some(product(vec![
1970 t_int(),
1971 t_int(),
1972 t_paddings(),
1973 t_inline_boxes(),
1974 ])),
1975 ),
1976 ],
1977 param_vars: Vec::new(),
1978 };
1979
1980 // `math-class` — nullary variant, transcribed from
1981 // `primitives.cppo.ml:162-170`. **Distinct** from `math-char-class`
1982 // below (the styling variant) — do not conflate.
1983 let math_class_decl = VariantDecl {
1984 name: "math-class".to_string(),
1985 params: 0,
1986 ctors: vec![
1987 ("MathOrd".to_string(), None),
1988 ("MathBin".to_string(), None),
1989 ("MathRel".to_string(), None),
1990 ("MathOp".to_string(), None),
1991 ("MathPunct".to_string(), None),
1992 ("MathOpen".to_string(), None),
1993 ("MathClose".to_string(), None),
1994 ("MathPrefix".to_string(), None),
1995 ("MathInner".to_string(), None),
1996 ],
1997 param_vars: Vec::new(),
1998 };
1999
2000 // `math-char-class` — nullary variant. Constructor set is
2001 // version-dependent: `v0.0.6` upstream has
2002 // exactly these 9 (`v0.0.6:src/backend/horzBox.ml:147-158`'s exact set,
2003 // literally "TEMPORARY; should add more"); dev-0-1-0 widens
2004 // `math_char_class` 9 → 14 (`b836d512:src/backend/horzBox.ml:98-113`),
2005 // adding `MathSansSerif`/`MathBoldSansSerif`/`MathItalicSansSerif`/
2006 // `MathBoldItalicSansSerif`/`MathTypewriter`. Needed for `math.satyh`'s
2007 // `\mathrm`/`\mathbf`/`\mathcal`/`\mathfrak`/`\mathbb`/`\bm`/`\mathsf`/
2008 // `\mathtt` to type-check (each applies `math-char-class` to one of
2009 // these). Gated on `math_is_split()` so the frozen 0.0.6 surface never
2010 // learns the 5 new names (unknown-constructor error preserved,
2011 // `typecheck.rs:2257/2347`).
2012 let mut math_char_class_ctors = vec![
2013 ("MathItalic".to_string(), None),
2014 ("MathBoldItalic".to_string(), None),
2015 ("MathRoman".to_string(), None),
2016 ("MathBoldRoman".to_string(), None),
2017 ("MathScript".to_string(), None),
2018 ("MathBoldScript".to_string(), None),
2019 ("MathFraktur".to_string(), None),
2020 ("MathBoldFraktur".to_string(), None),
2021 ("MathDoubleStruck".to_string(), None),
2022 ];
2023 if version.math_is_split() {
2024 math_char_class_ctors.extend([
2025 ("MathSansSerif".to_string(), None),
2026 ("MathBoldSansSerif".to_string(), None),
2027 ("MathItalicSansSerif".to_string(), None),
2028 ("MathBoldItalicSansSerif".to_string(), None),
2029 ("MathTypewriter".to_string(), None),
2030 ]);
2031 }
2032 let math_char_class_decl = VariantDecl {
2033 name: "math-char-class".to_string(),
2034 params: 0,
2035 ctors: math_char_class_ctors,
2036 param_vars: Vec::new(),
2037 };
2038
2039 let mut decls = vec![
2040 option_decl,
2041 itemize_decl,
2042 color_decl,
2043 script_decl,
2044 language_decl,
2045 cell_decl,
2046 math_class_decl,
2047 math_char_class_decl,
2048 ];
2049 if version.has_page_adt() {
2050 decls.push(page_decl);
2051 }
2052 decls
2053}