pub enum Sexp {
Nil,
Atom(Atom),
List(Vec<Sexp>),
Quote(Box<Sexp>),
Quasiquote(Box<Sexp>),
Unquote(Box<Sexp>),
UnquoteSplice(Box<Sexp>),
}Variants§
Nil
Atom(Atom)
List(Vec<Sexp>)
Quote(Box<Sexp>)
'x — literal; does not participate in macro substitution.
Quasiquote(Box<Sexp>)
`x — quasi-quotation; substitution happens inside.
Unquote(Box<Sexp>)
,x — substitute the binding named x. Only valid inside a quasi-quote.
UnquoteSplice(Box<Sexp>)
,@x — splice the list x into the containing list.
Implementations§
Source§impl Sexp
impl Sexp
Sourcepub const LIST_OPEN: char = '('
pub const LIST_OPEN: char = '('
Canonical ( char that opens a Self::List rendering AND
(paired with Self::LIST_CLOSE) the empty Self::Nil
rendering (). Outer-structural peer of Atom::STR_DELIMITER
on the atomic-payload delimiter axis: where STR_DELIMITER is
the ONE " byte the reader’s tokenizer’s FOUR
Token::Str-round-trip sites bind to on the closed-set Atom
algebra, LIST_OPEN is the ONE ( byte the reader’s tokenizer’s
Token::LParen outer-dispatch arm AND the bare-atom terminator
disjunct AND [fmt::Display for Sexp]’s list-opening emission
AND Self::Nil’s two-char () rendering all bind to on the
closed-set outer Sexp algebra.
Pre-lift the same '(' byte lived inline at FOUR sites: two
outer-match arms in crate::reader::tokenize (the
Token::LParen construction arm AND the bare-atom terminator’s
|| ch == '(' disjunct), and two Display arms in [fmt::Display for Sexp] (the Self::List(_) opener AND the Self::Nil
two-char () rendering’s left char). Post-lift the (typed
structural role, canonical byte) pairing binds at ONE constant
on the Sexp algebra that every consumer routes through; a
refactor that swaps the byte (e.g. a Racket-style port to [
for square-bracket list literals, an S-expression-DSL port to
{ for brace-list syntax) touches ONE constant rather than
four inline bytes that would silently drift out of round-trip
agreement if one was updated without the others.
Load-bearing paired-delimiter contract:
Sexp::LIST_OPEN MUST pair section-for-retraction with
Self::LIST_CLOSE at every round-trip site — the reader’s
Token::LParen (from LIST_OPEN) MUST be closed by a
Token::RParen (from LIST_CLOSE) for a well-formed list,
and the Display impl’s Self::List(_) arm MUST emit
LIST_OPEN at the opener AND LIST_CLOSE at the closer for
the reader-then-Display round trip
parse(display(list)) == list to hold. Guards the paired
disjointness across the closed-set outer Sexp algebra so a
future refactor that renames one constant without updating the
other fails at rustc / test time rather than as a silent list-
rendering asymmetry.
Cross-axis disjointness with the sibling delimiters (pinned
structurally at
sexp_list_delimiters_distinct_from_every_other_algebra_marker):
LIST_OPEN’s byte MUST differ from Atom::STR_DELIMITER
('"'), Atom::KEYWORD_MARKER (":"), the two
Atom::bool_literal spellings ("#t" / "#f") AND every
QuoteForm::lead_char projection ('\'', '`', ',')
on the substrate’s outer-marker axes. Otherwise the reader’s
Token::LParen outer-dispatch arm would ambiguously route
through a sibling algebra’s arm.
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(Self::List outer structure, canonical ( opener) pairing
now binds at ONE constant on the closed-set outer Sexp
algebra regardless of which of the four consumer surfaces
reaches in. THEORY.md §VI.1 — generation over composition;
four byte-identical inline '(' char literals across two
substrate files collapse onto ONE named constant, matching
the substrate’s three-times rule. THEORY.md §V.1 — knowable
platform; the canonical list-opener byte becomes a
TYPE-level constant on the outer substrate algebra rather
than four inline bytes at four consumer surfaces across two
substrate files (crate::reader and crate::ast).
Sourcepub const LIST_CLOSE: char = ')'
pub const LIST_CLOSE: char = ')'
Canonical ) char that closes a Self::List rendering AND
(paired with Self::LIST_OPEN) the empty Self::Nil
rendering (). See Self::LIST_OPEN for the substrate-wide
paired-delimiter contract, cross-axis disjointness, and theory
anchors — this constant is its section-for-retraction sibling
on the closer axis. Four consumer sites (the reader’s
Token::RParen outer-dispatch arm, the bare-atom terminator’s
|| ch == ')' disjunct, [fmt::Display for Sexp]’s
Self::List(_) closer, Self::Nil’s two-char ()
rendering’s right char) all bind here.
Sourcepub const LIST_DELIMITERS: [char; 2]
pub const LIST_DELIMITERS: [char; 2]
Canonical paired list-delimiter closed-set ALL array — composes
Self::LIST_OPEN followed by Self::LIST_CLOSE in canonical
declaration order, forced-arity [char; 2] so a hypothetical
third list-delimiter row would extend this array + one algebra
constant in lockstep. Peer to Atom::SELF_ESCAPE_TABLE
([char; 2] on the Str-payload self-escape sub-vocabulary axis
of the closed-set Atom algebra): where SELF_ESCAPE_TABLE
closes the two pattern-EQUALS-value inner-tokenizer arms of
Atom::decode_str_escape as ONE typed forced-arity array,
LIST_DELIMITERS closes the two outer-structural list-delimiter
arms of the reader’s outer-dispatch as the analogous typed
forced-arity array one axis over on the closed-set outer
Sexp algebra.
Pre-lift the two-element [Self::LIST_OPEN, Self::LIST_CLOSE]
composition lived inline at TWO sites: the two || ch == Self::LIST_{OPEN,CLOSE} disjuncts inside
Self::is_bare_atom_boundary’s reader-boundary projection
(spanning TWO of the SIX categories the projection carries), AND
the [Sexp::LIST_OPEN, Sexp::LIST_CLOSE].iter().collect() array
literal at the Nil Display composition-pin test that binds the
two-char () rendering to the two typed constants. Post-lift
the sub-vocabulary sweep binds at ONE typed forced-arity array
on the closed-set outer Sexp algebra rather than at two
inline algebra-constant enumerations per consumer. Adding a
hypothetical Racket-compat square-bracket list mode
([Self::LIST_OPEN, Self::LIST_CLOSE, Self::VEC_OPEN, Self::VEC_CLOSE]) would extend LIST_DELIMITERS ONCE +
Self::is_bare_atom_boundary’s sub-vocabulary sweep ONCE + two
new algebra constants (opener + closer) in lockstep; rustc’s
forced-arity check on [char; N] binds the extension through
the array declaration site.
Structural invariant carried at the SHAPE level: [char; 2]
pairs section-for-retraction one-to-one with
Atom::SELF_ESCAPE_TABLE’s [char; 2] — the two arrays
sit on distinct closed-set algebras (outer-structural
list-delimiter vocabulary on Sexp; inner-Str-payload
self-escape vocabulary on Atom) but share the same
forced-arity shape at their respective sub-vocabulary
axes. A consumer that reaches for one of the two arrays
encodes its vocabulary’s paired-role identity in the SHAPE
it iterates rather than in a per-site convention.
Composition law (round-trip): LIST_DELIMITERS[0] == Self::LIST_OPEN AND LIST_DELIMITERS[1] == Self::LIST_CLOSE
AND LIST_DELIMITERS.len() == 2. The forced-arity + canonical
declaration order together pin every downstream index-sweep
consumer to the (opener, closer) pairing at rustc time; a
reorder without reordering the underlying algebra constants
fails at the composition pin below.
Cross-axis disjointness pinned structurally at
[sexp_list_delimiters_distinct_from_every_other_algebra_marker]:
neither element aliases any sibling outer-marker char on the
substrate’s other closed-set algebras — the Str-payload
delimiter (Atom::STR_DELIMITER), the Keyword-marker prefix
(Atom::KEYWORD_MARKER_LEAD), the Comment-lead byte
(Self::COMMENT_LEAD), every quote-family lead char
(QuoteForm::lead_char), and every Bool-literal spelling’s
first char.
Theory anchor: THEORY.md §III — the typescape; the paired
(opener, closer) list-delimiter sub-vocabulary becomes a typed
forced-arity ALL array on the closed-set outer Sexp
algebra rather than as two inline algebra-constant
enumerations at every consumer that iterates the paired
delimiter axis. THEORY.md §V.1 — knowable platform; the
paired-delimiter sub-vocabulary now binds as load-bearing
typed data at the algebra level rather than as two per-site
disjuncts. THEORY.md §VI.1 — generation over composition; the
paired-delimiter (opener + closer) composition regenerates
identically through this ONE typed forced-arity array rather
than through two inline algebra-constant enumerations per
consumer.
Sourcepub const COMMENT_LEAD: char = ';'
pub const COMMENT_LEAD: char = ';'
Canonical ; char that begins a line-comment run in the reader’s
tokenizer AND (as a bare-atom terminator disjunct) breaks a
Token::Atom accumulator when the byte is encountered mid-lexeme.
Outer-structural peer of Self::LIST_OPEN / Self::LIST_CLOSE
on the reader-discard axis: where LIST_OPEN / LIST_CLOSE are
the paired-delimiter constants that shape a Sexp::List payload
on the closed-set outer Sexp algebra, COMMENT_LEAD is the
ONE ; byte the reader’s tokenizer’s TWO comment-boundary sites
bind to on the same outer algebra — the outer-dispatch arm that
begins a line-comment run (consuming through the trailing \n
which is itself absorbed by the whitespace disjunct in the outer
match) AND the bare-atom terminator disjunct that ends a
Token::Atom accumulator when it encounters this byte mid-lexeme
so a bare foo;bar source tokenizes as Token::Atom("foo") @ 0
followed by a discarded line-comment run rather than as ONE
Token::Atom("foo;bar") payload.
Pre-lift the same ';' byte lived inline at TWO sites in
crate::reader::tokenize: the outer-match ';' => { … }
line-comment arm AND the bare-atom terminator’s || ch == ';'
disjunct. Post-lift the (reader-discard role, canonical byte)
pairing binds at ONE constant on the Sexp algebra that both
consumer sites route through; a refactor that swaps the byte
(e.g. a Scheme R7RS-style port to #; datum-comment syntax, an
Emacs-style port to #! shebang-comment syntax) touches ONE
constant rather than two inline bytes that would silently drift
out of tokenizer agreement if one was updated without the other.
Reader-discard contract: Sexp::COMMENT_LEAD MUST NOT surface
as an atomic payload in any parsed Sexp — the outer-dispatch
arm consumes the byte AND every char up to (but not past) the
trailing \n, emitting NO token. The bare-atom terminator
disjunct breaks the Token::Atom accumulator EXACTLY on this
byte so the subsequent line-comment run reaches the outer arm
with its byte-offset intact. Both sites bind to ONE constant so
a regression that drifts ONE of the two disjuncts (e.g.
re-inlines ';' at the outer arm while migrating the terminator
to a different byte) fails at rustc / test time rather than as
a silent tokenizer misclassification.
Cross-axis disjointness with the sibling markers (pinned
structurally at
sexp_comment_lead_distinct_from_every_other_algebra_marker):
COMMENT_LEAD‘s byte MUST differ from Self::LIST_OPEN
('('), Self::LIST_CLOSE (')'), Atom::STR_DELIMITER
('"'), Atom::KEYWORD_MARKER’s lead byte (':'), the two
Atom::bool_literal spellings’ lead byte ('#') AND every
QuoteForm::lead_char projection ('\'', '`', ',')
on the substrate’s outer-marker axes. Otherwise a bare ;foo
lexeme would ambiguously route through the line-comment arm AND
a sibling algebra’s arm at the reader’s outer dispatch.
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(reader-discard role, canonical ; byte) pairing binds at ONE
constant on the closed-set outer Sexp algebra regardless of
which of the two consumer sites reaches in. THEORY.md §VI.1 —
generation over composition; two byte-identical inline ';'
char literals across ONE substrate file collapse onto ONE named
constant, matching the substrate’s three-times rule
(\geq 2 PRIME-DIRECTIVE trigger). THEORY.md §V.1 — knowable
platform; the canonical comment-lead byte becomes a TYPE-level
constant on the outer substrate algebra rather than two inline
bytes at two consumer surfaces in crate::reader.
Sourcepub const COMMENT_TERM: char = '\n'
pub const COMMENT_TERM: char = '\n'
Canonical \n char that terminates a line-comment run in the
reader’s tokenizer — the section-for-retraction sibling of
Self::COMMENT_LEAD on the reader-discard axis. Paired
opener/terminator peer of Self::LIST_OPEN /
Self::LIST_CLOSE on the outer-structural axis: where
Self::LIST_OPEN / Self::LIST_CLOSE are the two typed
constants that shape a Sexp::List payload, Self::COMMENT_LEAD
/ Self::COMMENT_TERM are the two typed constants that shape
the reader’s line-comment discard run. Both pairs live on the
closed-set outer Sexp algebra so the reader-discard axis
carries the same opener/closer discipline the outer-structural
axis has carried since the initial Self::LIST_OPEN /
Self::LIST_CLOSE lift.
Pre-lift the same '\n' byte lived inline at ONE site in
crate::reader::tokenize — the line-comment discard loop’s
terminator check if ch == '\n' { break; }. Post-lift the
(reader-discard terminator role, canonical byte) pairing binds
at ONE constant on the Sexp algebra that the consumer site
routes through; a refactor that ports the reader to a different
line-break convention (e.g. Scheme R7RS #; datum-comment
terminated at the next well-formed datum, an Emacs-style port
with \r\n CRLF sequences, a Common-Lisp-style #|…|# block
comment closed by |#) touches ONE constant (or extends the
algebra by ONE peer method) rather than an inline byte.
Reader-discard contract: Sexp::COMMENT_TERM MUST NOT surface
as an atomic payload in any parsed Sexp — the reader’s
Self::COMMENT_LEAD outer-dispatch arm consumes every byte
(INCLUDING this terminator) up to and including the FIRST
occurrence of Self::COMMENT_TERM, emitting NO token. The
(lead, term) pair carries the SAME reader-discard invariant as
(LIST_OPEN, LIST_CLOSE) does on the outer-structural axis: both
bytes are structural markers that never appear in a token
payload.
Cross-axis disjointness with the sibling closed-set markers
(pinned structurally at
sexp_comment_term_distinct_from_every_non_whitespace_algebra_marker):
COMMENT_TERM‘s byte MUST differ from every NON-whitespace
outer-marker char — Self::LIST_OPEN, Self::LIST_CLOSE,
Self::COMMENT_LEAD, Atom::STR_DELIMITER,
Atom::STR_ESCAPE_LEAD, Atom::KEYWORD_MARKER’s lead byte,
the two Atom::bool_literal spellings’ lead byte, AND every
QuoteForm::lead_char projection. The terminator IS a
whitespace char (it satisfies char::is_whitespace) so the
disjointness test explicitly excludes the whitespace-family
axis: the terminator’s role IS to be whitespace-family, so the
disjointness contract binds only against the non-whitespace
outer-marker axes.
Interaction with the escape-decode codomain axis: the terminator
byte '\n' COINCIDES with the C0 control byte
Atom::decode_str_escape('n') produces — the two roles are
distinct algebraic axes (reader-discard structural marker on
the outer Sexp algebra vs. Str-escape shorthand codomain
value on the inner Atom algebra) so the collision at the
byte level is by design, NOT a disjointness violation. A \n
byte APPEARING inside a Token::Str payload’s decoded output
(via \n shorthand) is orthogonal to a \n byte APPEARING as
the line-comment terminator at the reader’s outer-dispatch
discard loop.
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(reader-discard terminator role, canonical \n byte) pairing
binds at ONE constant on the closed-set outer Sexp algebra
regardless of which consumer reaches in. THEORY.md §II.1
invariant 5 — composition preserves proofs; the paired
(COMMENT_LEAD, COMMENT_TERM) shape now lives at the algebra
alongside (LIST_OPEN, LIST_CLOSE), so the reader-discard axis
carries the SAME opener/closer discipline as the outer-
structural axis. THEORY.md §VI.1 — generation over composition;
the reader’s inline '\n' char literal at the line-comment
discard loop’s terminator check collapses onto ONE named
constant on the substrate algebra. THEORY.md §V.1 — knowable
platform; the canonical line-comment terminator byte becomes
a TYPE-level constant on the outer substrate algebra rather
than an inline '\n' at one consumer surface in
crate::reader::tokenize.
Sourcepub const COMMENT_DELIMITERS: [char; 2]
pub const COMMENT_DELIMITERS: [char; 2]
Canonical paired line-comment delimiter closed-set ALL array —
composes Self::COMMENT_LEAD followed by Self::COMMENT_TERM
in canonical (opener, terminator) declaration order, forced-arity
[char; 2] so a hypothetical alternative comment convention (a
Scheme R7RS #; datum-comment lead paired with a next-datum
terminator, an Emacs-style CRLF paired terminator, a Common-Lisp
#|…|# block comment closed by |#) would extend this array +
one algebra constant per new row in lockstep — rustc’s forced-
arity check on [char; N] binds the extension through the array
declaration site.
Cross-axis peer to Self::LIST_DELIMITERS ([char; 2] on the
outer-structural paired-delimiter axis of the SAME closed-set
outer Sexp algebra) — both close a paired-role byte
sub-vocabulary at the SAME shape ([char; 2], (opener, closer_or_terminator) canonical declaration order) but on
DISTINCT roles: Self::LIST_DELIMITERS closes the two typed
constants that shape a Self::List payload
(Self::LIST_OPEN / Self::LIST_CLOSE, BOTH of which
classify as bare-atom boundaries and both non-whitespace);
Self::COMMENT_DELIMITERS closes the two typed constants that
shape the reader’s line-comment discard run
(Self::COMMENT_LEAD / Self::COMMENT_TERM, where the LEAD
row is a bare-atom boundary AND non-whitespace, and the TERM row
is a whitespace-family char absorbed by the reader’s
ch.is_whitespace() outer-dispatch arm rather than a distinct
bare-atom boundary). The two arrays partition the SIX-category
outer-dispatch arm-set into a per-axis paired shape (2 rows for
list delimiters + 2 rows for comment delimiters + 1 row for
Atom::STR_DELIMITER + 3-of-4 rows for QuoteForm::LEADS +
the residual whitespace family), each axis carrying its own
forced-arity ALL array.
Also sibling-shape to Atom::SELF_ESCAPE_TABLE ([char; 2] on
the inner-Str-payload self-escape sub-vocabulary axis of the
closed-set Atom algebra), Atom::BOOL_LITERALS
([&'static str; 2] on the Scheme-bool spelling axis), and
crate::error::UnquoteForm::MARKERS / crate::error::UnquoteForm::IAC_FORGE_TAGS
([&'static str; 2] on the template-substitution subset algebra
— the 2-of-4 subset carving of QuoteForm) — every closed-set
outer projection on the substrate that carries a paired-role
two-row axis now pins its canonical bytes at ONE pub const per
role plus a forced-arity ALL array for family-wide consumers.
Pre-lift the two-element [Self::COMMENT_LEAD, Self::COMMENT_TERM]
composition had NO typed source of truth on the substrate — the
two constants each existed independently on the algebra
(Self::COMMENT_LEAD shipped in the initial reader-discard
lift; Self::COMMENT_TERM shipped in the follow-on paired-
terminator lift bb1bd5e) and consumers that wanted the
(opener, terminator) shape had to reach across the algebra
through TWO pub const sites. Post-lift the paired-role
sub-vocabulary binds at ONE forced-arity ALL array on the closed-
set outer Sexp algebra alongside the peer
Self::LIST_DELIMITERS array on the outer-structural axis; a
consumer that walks EITHER axis of the outer-structural /
reader-discard cross-product reads the paired-role identity off
the shared [char; 2] shape.
Structural invariant carried at the SHAPE level: [char; 2]
pairs section-for-retraction one-to-one with
Self::LIST_DELIMITERS’s [char; 2] — the two arrays sit at
distinct roles on the SAME closed-set outer Sexp algebra
(outer-structural payload-delimiter role for LIST_DELIMITERS;
reader-discard opener/terminator role for COMMENT_DELIMITERS)
but share the same forced-arity shape at their respective axes.
A consumer that reaches for one of the two arrays encodes its
axis’s paired-role identity in the SHAPE it iterates rather than
in a per-site convention.
Composition law (round-trip): COMMENT_DELIMITERS[0] == Self::COMMENT_LEAD AND COMMENT_DELIMITERS[1] == Self::COMMENT_TERM AND COMMENT_DELIMITERS.len() == 2. The
forced-arity + canonical declaration order together pin every
downstream index-sweep consumer to the (opener, terminator)
pairing at rustc time; a reorder without reordering the
underlying algebra constants fails at the composition pin below.
Path-uniformity contract pinned per-row: COMMENT_DELIMITERS[0]
(the LEAD row) MUST classify as a bare-atom boundary via
Self::is_bare_atom_boundary (the reader’s outer-dispatch’s
dedicated line-comment arm is one of the SIX categories that
projection covers), AND COMMENT_DELIMITERS[1] (the TERM row)
MUST classify as a whitespace-family char via
char::is_whitespace (the reader’s ch.is_whitespace() arm
absorbs the terminator so the discard loop’s post-loop hand-off
to the outer-dispatch fires the whitespace arm rather than a
distinct comment-terminator arm). The per-row asymmetry is
LOAD-BEARING and structurally distinct from
Self::LIST_DELIMITERS’s BOTH-rows-are-bare-atom-boundaries
contract (both ( and ) are non-whitespace outer-dispatch
arms). Pinned by
sexp_comment_delimiters_lead_row_is_bare_atom_boundary +
sexp_comment_delimiters_term_row_is_whitespace_family_char.
Cross-axis disjointness pinned structurally at
sexp_comment_delimiters_disjoint_from_list_delimiters: no row
of COMMENT_DELIMITERS aliases any row of
Self::LIST_DELIMITERS — the reader-discard sub-vocabulary
and the outer-structural list-delimiter sub-vocabulary partition
their respective bytes disjointly on the SAME closed-set outer
Sexp algebra. Cross-algebra disjointness pinned at
sexp_comment_delimiters_disjoint_from_str_delimiter: no row
aliases Atom::STR_DELIMITER — the reader-discard arm and the
Str-payload arm partition their bytes across the two closed-set
algebras disjointly.
Future consumers that compose against Self::COMMENT_DELIMITERS:
a hypothetical tatara_lisp_comment_delimiter_total{delimiter=";"|"\n"}
Sekiban metric surface at Prometheus recording time — the
label-set generator sweeps this array verbatim rather than
re-typing the two paired bytes inline at each recorder, and
rustc-binds the metric-label set to the closed set through the
forced-arity ALL array; an LSP / structural-editor that
highlights line-comment runs — the (opener, terminator) pair the
editor spans over IS this array’s two rows; a hypothetical
Sexp::BLOCK_COMMENT_DELIMITERS peer array for a future
#|…|# block-comment mode would follow the same shape
mechanically, extending the reader-discard axis by ONE peer
array without touching this one’s shape.
Theory anchor: THEORY.md §III — the typescape; the paired
(opener, terminator) reader-discard sub-vocabulary of the
reader’s outer-dispatch arm-set now binds at ONE typed [char; 2] array on the closed-set outer Sexp algebra rather than
as two independent algebra constants (Self::COMMENT_LEAD,
Self::COMMENT_TERM) accessed independently at every consumer
that wants the paired-role shape. The shared [char; 2] shape
with Self::LIST_DELIMITERS encodes the paired-role identity
relation across the two axes of the SAME closed-set algebra at
the type system level. THEORY.md §V.1 — knowable platform; the
paired-discard-delimiter sub-vocabulary becomes load-bearing
typed data on the closed-set outer Sexp algebra. THEORY.md
§VI.1 — generation over composition; the paired-delimiter
(opener + terminator) composition regenerates identically
through this ONE typed forced-arity array rather than through
two independent algebra constants at every consumer. THEORY.md
§II.1 invariant 5 — composition preserves proofs; the two-axis
(outer-structural, reader-discard) cross-product on the closed-
set outer Sexp algebra now carries the SAME opener/closer
discipline on BOTH axes through two forced-arity ALL arrays
with byte-identical shape.
Sourcepub const NON_WHITESPACE_BARE_ATOM_TERMINATORS: [char; 7]
pub const NON_WHITESPACE_BARE_ATOM_TERMINATORS: [char; 7]
Closed-set forced-arity ALL array over the SEVEN non-whitespace
category-leading chars the reader’s outer-dispatch cascade
specialises on — the reader-level boundary sub-vocabulary that
paired with char::is_whitespace() closes the six-clause
Self::is_bare_atom_boundary disjunction. Composes through
seven typed pub const primitives spanning THREE type namespaces
on the SAME reader-outer-dispatch axis of the substrate:
Self::LIST_OPEN('(') — the outer-structural list-opening delimiter on the outerSexpalgebra;Self::LIST_CLOSE(')') — the outer-structural list-closing delimiter on the outerSexpalgebra;QuoteForm::QUOTE_LEAD('\'') — theQuoteForm::Quotereader-punctuation lead byte on the quote-family sub-algebra;QuoteForm::QUASIQUOTE_LEAD('`') — theQuoteForm::Quasiquotereader-punctuation lead byte;QuoteForm::UNQUOTE_LEAD(',') — the sharedQuoteForm::Unquote/QuoteForm::UnquoteSplicereader- punctuation lead byte (disambiguated at the second-char peek viaQuoteForm::promote_via_next_char);Atom::STR_DELIMITER('"') — the Str-payload opening / closing delimiter on the closed-setAtomalgebra;Self::COMMENT_LEAD(';') — the line-comment opener on the outerSexpalgebra (paired withSelf::COMMENT_TERMinside the discard loop — but the TERM is a run-boundary marker inside a comment run, NOT a reader-outer-dispatch category-leading char, so it is intentionally omitted from this ALL array).
Cross-axis peer to Self::LIST_DELIMITERS ([char; 2] on the
outer-structural payload-delimiter axis) and Self::COMMENT_DELIMITERS
([char; 2] on the reader-discard opener/terminator axis) at
ONE algebra level up: those two arrays close their respective
paired-role sub-vocabularies (opener + closer, opener + terminator)
on the reader-INNER axis of the outer Sexp algebra; this
array closes the reader-OUTER-dispatch category-leading char
sub-vocabulary across the SAME closed-set outer Sexp algebra
PLUS its two sibling sub-algebras (QuoteForm, Atom) at
ONE family-wide [char; 7] primitive. Sibling-shape peer of the
intra-algebra sub-vocabulary array QuoteForm::LEADS
([char; 3] — the three DISTINCT quote-family reader-lead
bytes) which this array embeds as its middle three positions:
where QuoteForm::LEADS closes the quote-family sub-carving’s
reader-lead sub-vocabulary at ONE forced-arity array on ONE
closed-set algebra, this array closes the FULL reader-outer-
dispatch non-whitespace category-leading char sub-vocabulary at
ONE forced-arity array on the outer Sexp algebra by
composing through the three-arm quote-family sub-carving + the
two-arm structural-delimiter sub-carving + the two-arm atomic-
carve delimiter + comment-lead singletons.
Pre-lift the seven category-leading chars had NO family-wide
array on the outer Sexp algebra — Self::is_bare_atom_boundary
carried them as three sub-expressions (Self::LIST_DELIMITERS.contains(&ch)
on the structural-delimiter axis, QuoteForm::from_lead_char(ch).is_some()
on the quote-family axis, ch == Atom::STR_DELIMITER || ch == Self::COMMENT_LEAD on the singleton axes) unified through boolean
disjunction. Post-lift the WHOLE non-whitespace terminator sub-
vocabulary binds at ONE pub const [char; 7] array on the outer
Sexp algebra so Self::is_bare_atom_boundary collapses to
ch.is_whitespace() || Self::NON_WHITESPACE_BARE_ATOM_TERMINATORS.contains(&ch)
— TWO clauses (one whitespace-partial predicate + one
contains-check on the family-wide ARRAY) rather than five
sub-clauses spanning three type namespaces. Consumers keyed on
the whole family (a tatara-check predicate (check-reader- outer-dispatch-terminator-partition-injective …) that verifies
the seven-arm partition structurally, a future REPL / LSP
tokenizer-boundary hint that scans the source for the reader-
outer-dispatch category-leading chars upfront, a future
completion generator that suggests the seven bytes at every
bare-atom-lexeme insertion site) read through
Self::NON_WHITESPACE_BARE_ATOM_TERMINATORS without re-deriving
the three-part sub-expression composition inline.
Composition law (forward): for every ch: char,
Self::NON_WHITESPACE_BARE_ATOM_TERMINATORS.contains(&ch) == (Self::LIST_DELIMITERS.contains(&ch) || QuoteForm::from_lead_char(ch).is_some() || ch == Atom::STR_DELIMITER || ch == Self::COMMENT_LEAD) — pinned
by sexp_non_whitespace_bare_atom_terminators_agree_with_pre_lift_sub_expression_disjunction.
Boundary-predicate composition law:
Self::is_bare_atom_boundary(ch) == (ch.is_whitespace() || Self::NON_WHITESPACE_BARE_ATOM_TERMINATORS.contains(&ch)) —
pinned by sexp_is_bare_atom_boundary_agrees_with_terminators_array_on_non_whitespace_partition.
Adding a hypothetical seventh reader-outer-dispatch category
(e.g. #|…|# block-comment lead byte, #\ char-literal prefix,
#[ vector-literal prefix — each pinning a new lead byte on
Self or a fresh sub-algebra) extends this array AND
Self::is_bare_atom_boundary’s indirect coverage in
LOCKSTEP — rustc’s forced-arity check on [char; 7] fails
compilation if the algebra grows without the array (or the
array without the algebra).
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(reader-outer-dispatch category, canonical char) family-wide
pairing binds at ONE typed [char; 7] array on the outer
Sexp algebra regardless of which of the three sub-algebras
the individual chars name their per-role pub const primitive
on. THEORY.md §III — the typescape; the seven canonical
reader-outer-dispatch category-leading bytes bind at ONE typed
[char; 7] array on the outer Sexp algebra rather than at
three-part sub-expression composition inline at
Self::is_bare_atom_boundary. THEORY.md §V.1 — knowable
platform; the family’s cardinality becomes a TYPE-level constant
on the substrate algebra rather than a per-consumer hand-rolled
enumeration of the seven chars. THEORY.md §VI.1 — generation over
composition; the family-wide contract sweeps (routing through
typed sub-algebra pub const primitives, pairwise distinctness,
agreement with the pre-lift sub-expression disjunction) emerge
from the composition of EIGHT substrate primitives (this
pub const [char; 7] array + the seven sub-algebra pub const
primitives) rather than as inline disjunctions at each call site.
Sourcepub fn is_bare_atom_boundary(ch: char) -> bool
pub fn is_bare_atom_boundary(ch: char) -> bool
Reader-level boundary predicate — returns true iff ch is one
of the SIX outer-dispatch category-leading chars the reader’s
tokenizer specialises on: whitespace, Self::LIST_OPEN,
Self::LIST_CLOSE, any QuoteForm::lead_char (via the
closed-set QuoteForm::from_lead_char decode),
Atom::STR_DELIMITER, AND Self::COMMENT_LEAD. The ONE
typed projection on the outer Sexp algebra that names the
disjunction of “the char would start a NEW reader-level token
(or a discarded run) rather than feed the current bare-atom
accumulator.”
Structural dual of the reader’s outer-dispatch cascade in
crate::reader::tokenize: the outer-dispatch has FIVE specific
arms (ws if ws.is_whitespace(), Self::COMMENT_LEAD,
Self::LIST_OPEN, Self::LIST_CLOSE, Atom::STR_DELIMITER)
plus ONE pre-match QuoteForm::from_lead_char(c).is_some()
gate — SIX categories in total. The default _ => { … bare-atom accumulator … } arm fires EXACTLY when every specific
arm rejects. This method is the typed projection of that
implicit disjunction: Sexp::is_bare_atom_boundary(ch) == true
iff ch would trigger one of the SIX specific arms, and
false iff ch would fall through to the bare-atom
accumulator’s default arm. The two consumer sites in
crate::reader::tokenize — the outer-dispatch’s implicit “no
specific arm fires” residual predicate AND the bare-atom
accumulator’s terminator disjunct — now share ONE typed source
of truth on the closed-set outer Sexp algebra.
Pre-lift the SIX-clause boolean chain
(ch.is_whitespace() || ch == Sexp::LIST_OPEN || ch == Sexp::LIST_CLOSE || QuoteForm::from_lead_char(ch).is_some() || ch == Atom::STR_DELIMITER || ch == Sexp::COMMENT_LEAD) lived
inline at the bare-atom accumulator’s terminator gate in
crate::reader::tokenize, spanning THREE type namespaces
(Sexp, Atom, QuoteForm) at ONE consumer site.
Post-lift the WHOLE disjunction binds at ONE typed projection
on the outer Sexp algebra so a refactor that adds a
SEVENTH outer-dispatch category (e.g. #|…|# block-comment
lead byte, #\ char-literal prefix, #[ vector-literal
prefix) extends the algebra ONCE (via a new arm on THIS method
AND a matching outer-dispatch arm in the reader) rather than
mutating an inline six-clause boolean chain that would silently
drift out of tokenizer agreement if one clause was added
without the other. Sibling-shape peer of the outer-dispatch’s
closed-set per-category projections
(QuoteForm::from_lead_char on the quote-family axis;
Atom::decode_str_escape on the Str-escape axis): where those
two methods each lift ONE outer-dispatch category’s decode onto
its typed algebra, THIS method lifts the DISJUNCTION of ALL SIX
outer-dispatch categories onto the outer Sexp algebra as
a bool predicate.
Composition law (forward): for every char ch and every
substrate-marker enumeration
Self::{LIST_OPEN, LIST_CLOSE, COMMENT_LEAD},
Atom::STR_DELIMITER, QuoteForm::from_lead_char(ch).is_some(),
is_bare_atom_boundary returns true; for every char that
isn’t whitespace AND isn’t listed on any marker axis, returns
false. Reader-level composition law:
read(format!("foo{ch}")) tokenizes as [Token::Atom("foo"), …trailing token(s) from ch] when
Self::is_bare_atom_boundary(ch) is true, and as
[Token::Atom(format!("foo{ch}"))] (ONE token) when it is
false.
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(reader-level boundary role, canonical char) pairing binds at
ONE typed projection on the outer Sexp algebra regardless
of which of the SIX outer-dispatch category-leading chars is
under test. THEORY.md §VI.1 — generation over composition; a
SIX-clause inline boolean disjunction spanning THREE type
namespaces collapses onto ONE named method — the substrate’s
three-times rule saturated at the outer-dispatch’s disjunction.
THEORY.md §V.1 — knowable platform; the canonical reader-level
boundary predicate becomes a TYPE-level method on the outer
substrate algebra rather than an inline six-clause boolean
chain at ONE consumer site inside crate::reader::tokenize.
Sourcepub fn symbol(s: impl Into<String>) -> Sexp
pub fn symbol(s: impl Into<String>) -> Sexp
Canonical Self::Atom-Atom::Symbol outer constructor —
composes Atom::symbol (the typed-construct method on the
closed-set Atom algebra) under the Self::Atom outer
wrapper. The first of six Self::Atom(Atom::X(_)) outer
constructors all routing through the typed Atom construct
family at the inner algebra so the .into() coercion + tuple-
variant constructor pair lives at ONE site per kind on the
Atom algebra rather than at this outer constructor’s body.
Sibling-shape lift to the [Atom::as_X] /
[Self::as_X] composition through Self::as_atom on the
projection axis: where projections route OUTER Self::as_X
through self.as_atom().and_then(Atom::as_X), constructions
route OUTER Self::X through Self::Atom(Atom::X(payload)).
Composition law (forward): Sexp::symbol(s) == Sexp::Atom(Atom::symbol(s)) for every s: impl Into<String>.
Round-trip law (with the soft-projection sibling): for every
s: &str, Sexp::symbol(s).as_symbol() == Some(s) — the inner
algebra’s section-for-retraction surfaces through the outer
algebra without re-derivation. Same posture across the six
sibling pairs.
Sourcepub fn keyword(s: impl Into<String>) -> Sexp
pub fn keyword(s: impl Into<String>) -> Sexp
Canonical Self::Atom-Atom::Keyword outer constructor —
composes Atom::keyword under Self::Atom. See
Self::symbol for the outer-algebra docstring.
Sourcepub fn string(s: impl Into<String>) -> Sexp
pub fn string(s: impl Into<String>) -> Sexp
Canonical Self::Atom-Atom::Str outer constructor —
composes Atom::string under Self::Atom.
Sourcepub fn int(n: i64) -> Sexp
pub fn int(n: i64) -> Sexp
Canonical Self::Atom-Atom::Int outer constructor —
composes Atom::int under Self::Atom.
Sourcepub fn float(n: f64) -> Sexp
pub fn float(n: f64) -> Sexp
Canonical Self::Atom-Atom::Float outer constructor —
composes Atom::float under Self::Atom.
Sourcepub fn boolean(b: bool) -> Sexp
pub fn boolean(b: bool) -> Sexp
Canonical Self::Atom-Atom::Bool outer constructor —
composes Atom::boolean under Self::Atom.
Sourcepub fn quote(inner: Sexp) -> Sexp
pub fn quote(inner: Sexp) -> Sexp
Canonical Self::Quote outer constructor — composes
QuoteForm::wrap on the QuoteForm::Quote marker so the
Box::new(inner) allocation + tuple-variant pair lives at ONE
site on the closed-set QuoteForm algebra rather than at
this outer-constructor body. The first of four Self::Quote*
outer constructors all routing through the typed
QuoteForm::wrap family at the inner algebra — the
quote-family-axis section peer of the six Self::Atom(Atom::X(_))
outer constructors (Self::symbol, Self::keyword,
Self::string, Self::int, Self::float,
Self::boolean) all routing through the typed Atom
construct family on the atomic-payload axis. Sibling-shape lift
to the Self::as_quote_form soft-projection sibling on the
projection axis: where the projection soft-decomposes a
quote-family wrapper into Option<(QuoteForm, &Sexp)> (surfacing
the typed marker alongside the borrowed inner body), each of
these four typed constructors embeds a fresh inner body under
the typed marker into the matching tuple-variant wrapper.
Composition law (forward): Sexp::quote(inner) == QuoteForm::Quote.wrap(inner) == Sexp::Quote(Box::new(inner))
for every inner: Sexp. Round-trip law (section-for-retraction
with the soft-projection sibling): Sexp::quote(inner) .as_quote_form() == Some((QuoteForm::Quote, &inner)) for every
inner: Sexp — the inner algebra’s typed constructor pairs
section-for-retraction with the outer algebra’s soft
projection, and the marker + inner body cross-projection
preserves identity. Same posture across the four sibling
pairs (Sexp::quote / Sexp::quasiquote / Sexp::unquote /
Sexp::unquote_splice).
Pre-lift the Self::Quote(Box::new(inner)) welded triple
(Self::Quote, Box::new, inner) appeared inline at every
consumer that builds a quote-family wrapper — well past the ≥2
PRIME-DIRECTIVE trigger once the structural shape is named. The
welded triple already lives at ONE site on the closed-set
QuoteForm::wrap algebra for the marker-driven consumer path;
this outer constructor binds the per-variant Sexp::X(Box::new( inner)) welded triple to ONE typed-algebra method per marker on
the outer Sexp algebra, so consumers that know the marker at
compile time bind to the typed method directly rather than
re-deriving the Self::X(Box::new(_)) pair inline. A future
allocation-policy change (e.g. arena-allocated wrappers for
span-aware Sexp) lands as ONE edit at QuoteForm::wrap
(the single site the allocation composition lives) and
propagates through these four typed constructors byte-for-byte.
Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
(QuoteForm variant, Sexp tuple-variant constructor) pairing
binds at ONE typed-algebra method per marker on the outer
Sexp algebra regardless of which consumer reaches in.
THEORY.md §VI.1 — generation over composition; the welded
Self::X(Box::new(_)) triple at every quote-family construct
site regenerates through QuoteForm::X.wrap(_) composition over
the typed algebra rather than per-site re-derivation. THEORY.md
§V.1 — knowable platform; the typed-construct family becomes a
TYPE projection on the substrate’s outer Sexp algebra sitting
next to the typed-project family Self::as_quote_form rather
than as bare tuple-variant constructor + per-site Box::new
discipline. A future fifth homoiconic prefix syntax (e.g. syntax
quotation #'x for hygienic macros) extends QuoteForm::ALL +
QuoteForm::wrap’s arm + this construct family in lockstep,
rustc-enforced through the closed-set exhaustiveness.
Frontier inspiration: Racket’s (quote x) /
(quasiquote x) / (unquote x) / (unquote-splicing x) typed
syntactic-form construct face paired one-for-one with the
Self::as_quote_form closed-set soft-projection sibling on
the outer syntax algebra — the typed-construct + typed-project
algebra dual is closed at one method per direction per marker
on Racket’s surface, and the Self::quote /
Self::quasiquote / Self::unquote / Self::unquote_splice
family is the Rust-typed peer on the closed-set outer Sexp
algebra with QuoteForm::wrap standing in for Racket’s typed
dispatch face. MLIR’s mlir::OpBuilder::create<QuoteOp>(loc, inner) typed-IR wrapper construction paired with
mlir::dyn_cast<QuoteOp>(op) on the projection face — the typed
factory + typed downcast pair the IR algebra closes over on
every wrapper op; Self::quote / Self::as_quote_form is
the Rust-typed peer on the outer Sexp algebra with the
closed-set QuoteForm standing in for MLIR’s OperationName
taxonomy over the wrapper-op family.
Sourcepub fn quasiquote(inner: Sexp) -> Sexp
pub fn quasiquote(inner: Sexp) -> Sexp
Canonical Self::Quasiquote outer constructor — composes
QuoteForm::wrap on the QuoteForm::Quasiquote marker.
See Self::quote for the outer-algebra docstring.
Sourcepub fn unquote(inner: Sexp) -> Sexp
pub fn unquote(inner: Sexp) -> Sexp
Canonical Self::Unquote outer constructor — composes
QuoteForm::wrap on the QuoteForm::Unquote marker.
See Self::quote for the outer-algebra docstring.
Sourcepub fn unquote_splice(inner: Sexp) -> Sexp
pub fn unquote_splice(inner: Sexp) -> Sexp
Canonical Self::UnquoteSplice outer constructor — composes
QuoteForm::wrap on the QuoteForm::UnquoteSplice marker.
See Self::quote for the outer-algebra docstring.
Sourcepub fn quote_form(marker: QuoteForm, inner: Sexp) -> Sexp
pub fn quote_form(marker: QuoteForm, inner: Sexp) -> Sexp
Canonical marker-driven quote-family outer constructor — routes
through QuoteForm::wrap on the caller-supplied QuoteForm
marker at ONE site on the closed-set Sexp algebra. The outer-
algebra section-for-retraction sibling of the existing
Self::as_quote_form soft-projection (Option<(QuoteForm, &Sexp)>): where the projection soft-decomposes a quote-family
wrapper into its typed QuoteForm marker + borrowed inner body
on the (marker, borrowed-inner) product, this constructor embeds
a typed QuoteForm marker + owned inner body pair into the
matching tuple-variant wrapper on the (marker, owned-inner)
product. Marker-driven parent of the four per-variant siblings
Self::quote / Self::quasiquote / Self::unquote /
Self::unquote_splice — each of the four is Self::quote_form( QuoteForm::X, inner) restricted to a compile-time-known marker;
this constructor is the marker-abstracted parent every consumer
that binds the marker as a runtime value routes through.
Sibling posture across the outer-algebra construct-family layer:
where Self::call and Self::named_call
close the (construct, project) dual on the call-form + named-
call-form typed decompositions of the residual-axis List arm,
and Self::list closes it on the residual-axis
List arm itself, this constructor closes it on the quote-family-
axis wrapper decomposition — the outer Sexp algebra now
carries a (marker, project) construct-family dual pair for every
axis of the SexpShape closed set at ONE typed method per
corner, with Sexp::quote_form(qf, inner) as the marker-driven
quote-family construct entry and Self::as_quote_form as its
marker-recovering projection sibling.
Composition law (forward): Sexp::quote_form(marker, inner) == marker.wrap(inner) for every marker: QuoteForm and every
inner: Sexp. The body routes through the SAME closed-set
QuoteForm::wrap method the four per-variant siblings
(Self::quote / Self::quasiquote / Self::unquote /
Self::unquote_splice) already reach for, so the (marker,
Sexp tuple-variant constructor) pairing binds at ONE closed-
set match on the substrate algebra — a regression that drifts
one consumer’s marker→wrapper mapping from the others (e.g. a
copy-edit that pairs QuoteForm::Quote with the
Sexp::Quasiquote tuple variant, or that drops a
QuoteForm::UnquoteSplice value through the
Sexp::Unquote tuple variant) cannot reach the substrate’s
runtime.
Round-trip law (section-for-retraction with the outer-algebra
soft-projection): for every marker: QuoteForm and every
inner: Sexp, Sexp::quote_form(marker, inner.clone()) .as_quote_form() == Some((marker, &inner)) — the outer
algebra’s marker-driven quote-family constructor pairs section-
for-retraction with the outer algebra’s soft quote-family
projection, and the (marker, inner body) cross-projection
preserves identity for every QuoteForm variant.
Marker-recovering projection composition: Sexp::quote_form( marker, inner).as_quote_form_marker() == Some(marker) for every
input — the marker-only projection sibling
(Self::as_quote_form_marker) recovers the constructor’s
marker byte-for-byte. Outer-shape composition law:
Sexp::quote_form(marker, inner).shape() == marker.sexp_shape()
— the outer-shape identity binds through the typed-shape lattice
at ONE arm per QuoteForm variant, symmetric with the atomic
construct family’s Sexp::X_atom(payload).shape() == AtomKind::X.sexp_shape() composition and the residual construct
family’s Sexp::list(items).shape() == SexpShape::List
composition.
Per-variant restriction laws (structural identity between the marker-driven parent + the four per-variant siblings):
Sexp::quote_form(QuoteForm::Quote, inner) == Sexp::quote(inner)Sexp::quote_form(QuoteForm::Quasiquote, inner) == Sexp::quasiquote(inner)Sexp::quote_form(QuoteForm::Unquote, inner) == Sexp::unquote(inner)Sexp::quote_form(QuoteForm::UnquoteSplice, inner) == Sexp::unquote_splice(inner)
The four per-variant constructors ARE the marker-driven parent
specialized on a compile-time-known marker; the marker-abstracted
parent binds every consumer that routes a runtime QuoteForm
value through a quote-family construct to ONE typed method on
the outer Sexp algebra rather than a four-arm inline
match qf { QuoteForm::X => Sexp::x(inner), … } dispatch.
Pre-lift consumers with a runtime QuoteForm marker routed
through marker.wrap(inner) directly (the reader’s
read_quoted production consumer at reader.rs, the domain
module’s quote-family round-trip test site) — well past the ≥2
PRIME-DIRECTIVE trigger once the marker-driven pattern is
named. Post-lift consumers bind to ONE typed-algebra method on
the outer Sexp algebra sitting next to the typed-project
family (Self::as_quote_form / Self::as_quote_form_marker)
rather than reaching into the closed-set QuoteForm::wrap
method directly. A future allocation-policy change (e.g. arena-
allocated wrappers for span-aware Sexp) lands as ONE edit at
the single QuoteForm::wrap composition site and propagates
through this constructor byte-for-byte.
Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
(typed QuoteForm marker, owned inner body, QuoteForm::wrap
composition) triple binds at ONE typed-algebra method on the
outer Sexp algebra, closing the marker-driven quote-family
(construct, project) algebra dual pair with
Self::as_quote_form on the projection side. THEORY.md §II.1
invariant 2 — free middle; every consumer that has a runtime
QuoteForm marker + an owned inner body and wants to build a
quote-family wrapper routes through the SAME typed method, so a
regression that drifts one consumer’s marker→wrapper mapping
cannot reach the substrate’s runtime. THEORY.md §V.1 — knowable
platform; the marker-driven quote-family typed-construct becomes
a TYPE projection on the substrate’s outer Sexp algebra
sitting next to the typed-project family
Self::as_quote_form / Self::as_quote_form_marker rather
than the closed-set QuoteForm::wrap method threaded as a
method call on a bound-marker value. THEORY.md §VI.1 —
generation over composition; the marker-driven quote-family
pair emerges from ONE typed-algebra composition through
QuoteForm::wrap rather than from per-consumer marker→wrapper
dispatch literals; a future fifth homoiconic prefix syntax
(e.g. #'x for hygienic macros) extends QuoteForm::ALL +
QuoteForm::wrap’s match arm + Self::as_quote_form’s
match arm in lockstep — rustc-enforced through the closed-set
exhaustiveness — with THIS constructor inheriting the extension
through the QuoteForm::wrap composition site without a per-
site edit.
Frontier inspiration: Racket’s (datum->syntax stx (list #'qf inner)) marker-abstracted quote-family construct paired one-
for-one with syntax-e on the projection face — the typed-
construct + typed-project algebra dual is closed on Racket’s
syntax algebra at one method per direction, and
Sexp::quote_form / Sexp::as_quote_form is the Rust-typed peer
on the closed-set outer Sexp algebra with QuoteForm
standing in for Racket’s syntactic-form taxonomy over the four
homoiconic prefix wrappers. MLIR’s typed-IR
mlir::OpBuilder::create(loc, OperationName, operands) marker-
driven op construction paired with mlir::Operation::getName()
on the projection face — the typed factory + typed downcast pair
the IR algebra closes over on every op kind at one method per
direction; Sexp::quote_form / Self::as_quote_form_marker
is the Rust-typed peer on the outer Sexp algebra with the
closed-set QuoteForm standing in for MLIR’s OperationName
taxonomy over the four homoiconic prefix-wrapper op kinds.
Sourcepub fn unquote_form(marker: UnquoteForm, inner: Sexp) -> Sexp
pub fn unquote_form(marker: UnquoteForm, inner: Sexp) -> Sexp
Canonical marker-driven template-substitution outer constructor —
routes through UnquoteForm::wrap on the caller-supplied
UnquoteForm marker at ONE site on the closed-set Sexp
algebra. Subset-algebra peer of the marker-driven quote-family
parent Self::quote_form: where Self::quote_form embeds a
caller-supplied QuoteForm marker + owned inner body on the
4-of-12 quote-family carving through the closed-set
QuoteForm::wrap composition site, THIS constructor embeds a
caller-supplied UnquoteForm marker + owned inner body on the
2-of-12 template-substitution subset carving through the
UnquoteForm::wrap composition site (which itself composes
UnquoteForm::to_quote_form then QuoteForm::wrap, so the
welded Sexp::X(Box::new(_)) triple ultimately still binds at
the ONE canonical QuoteForm::wrap site the four per-variant
siblings Self::quote / Self::quasiquote / Self::unquote
/ Self::unquote_splice and the marker-driven parent
Self::quote_form all route through). Closes the (construct,
project) algebra dual on the (UnquoteForm, Sexp) product
against the pre-existing projection sibling Self::as_unquote
(soft-decomposition into Option<(UnquoteForm, &Sexp)>) and its
marker-only peer Self::as_unquote_form (soft-decomposition
into Option<UnquoteForm>) — post-lift the outer Sexp
algebra carries a marker-driven (construct, project) dual pair
Sexp::unquote_form / Sexp::as_unquote at ONE typed method per
direction on the template-substitution subset alongside the
marker-only projection sibling Self::as_unquote_form,
symmetric with the pair Sexp::quote_form / Sexp::as_quote_form
/ Sexp::as_quote_form_marker the superset carries.
Sibling posture across the outer-algebra construct-family layer:
where Self::call and Self::named_call
close the (construct, project) dual on the call-form + named-call-
form typed decompositions of the residual-axis List arm,
Self::list closes it on the residual-axis List
arm itself, and Self::quote_form closes it
on the quote-family-axis marker-driven decomposition (the parent
4-of-12 quote-family carving), THIS constructor closes it on the
template-substitution-subset marker-driven decomposition (the
2-of-4 subset of the quote-family carving, equivalently the
2-of-12 substitution carving of the outer SexpShape closed
set) — the outer Sexp algebra now carries a marker-driven
construct-family dual pair for every closed-set carving on the
quote-family axis at ONE typed method per corner.
Composition law (forward): Sexp::unquote_form(marker, inner) == marker.wrap(inner) for every marker: UnquoteForm and every
inner: Sexp. The body routes through the SAME
UnquoteForm::wrap method the subset-algebra consumer path
already reaches for, so the (subset marker, Sexp tuple-variant
wrapper) pairing binds at ONE closed-set composition site on the
substrate — a regression that drifts one consumer’s subset
marker → wrapper mapping from the others (e.g. a copy-edit that
pairs UnquoteForm::Unquote with the Sexp::UnquoteSplice
tuple variant, or that drops a UnquoteForm::Splice value
through the Sexp::Unquote tuple variant) cannot reach the
substrate’s runtime.
Round-trip law (section-for-retraction with the outer-algebra
soft-projection): for every marker: UnquoteForm and every
inner: Sexp, Sexp::unquote_form(marker, inner.clone()) .as_unquote() == Some((marker, &inner)) — the outer algebra’s
marker-driven template-substitution constructor pairs section-
for-retraction with the outer algebra’s soft template-substitution
projection, and the (subset marker, inner body) cross-projection
preserves identity for every UnquoteForm variant.
Marker-recovering projection composition: Sexp::unquote_form( marker, inner).as_unquote_form() == Some(marker) for every input
— the marker-only projection sibling Self::as_unquote_form
recovers the constructor’s subset marker byte-for-byte. Outer-
shape composition law: Sexp::unquote_form(marker, inner).shape() == marker.sexp_shape() — the outer-shape identity binds through
the typed-shape lattice at ONE arm per UnquoteForm variant,
symmetric with the quote-family construct family’s
Sexp::quote_form(marker, inner).shape() == marker.sexp_shape()
composition and the atomic construct family’s
Sexp::X_atom(payload).shape() == AtomKind::X.sexp_shape()
composition. Superset-routing composition law:
Sexp::unquote_form(marker, inner) == Sexp::quote_form( marker.to_quote_form(), inner) for every input — the subset-
algebra construct routes through the same closed-set
QuoteForm::wrap composition site the superset construct
routes through, threaded via the typed 2-of-4 subset → superset
projection UnquoteForm::to_quote_form. A regression that
drifts either direction of this composition fails at the
superset-routing pin.
Per-variant restriction laws (structural identity between the marker-driven parent + the two per-variant siblings):
Sexp::unquote_form(UnquoteForm::Unquote, inner) == Sexp::unquote(inner)Sexp::unquote_form(UnquoteForm::Splice, inner) == Sexp::unquote_splice(inner)
The two per-variant constructors ARE the marker-driven parent
specialized on a compile-time-known subset marker; the marker-
abstracted parent binds every consumer that routes a runtime
UnquoteForm value through a template-substitution construct
to ONE typed method on the outer Sexp algebra rather than a
two-arm inline match uf { UnquoteForm::Unquote => Sexp::unquote( inner), UnquoteForm::Splice => Sexp::unquote_splice(inner) }
dispatch.
Pre-lift consumers with a runtime UnquoteForm marker routed
through marker.wrap(inner) directly (reaching into the
UnquoteForm::wrap subset-algebra method) OR through the two-
step Sexp::quote_form(marker.to_quote_form(), inner)
composition (routing via the superset marker-driven parent).
Post-lift consumers bind to ONE typed-algebra method on the outer
Sexp algebra sitting next to the typed-project family
(Self::as_unquote / Self::as_unquote_form) rather than
reaching into the closed-set UnquoteForm::wrap method
directly or composing the superset parent with the subset →
superset projection. A future allocation-policy change (e.g.
arena-allocated wrappers for span-aware Sexp) lands as ONE
edit at the single QuoteForm::wrap composition site and
propagates through this constructor byte-for-byte (via the
UnquoteForm::wrap composition).
Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
(typed UnquoteForm marker, owned inner body,
UnquoteForm::wrap composition) triple binds at ONE typed-
algebra method on the outer Sexp algebra, closing the marker-
driven template-substitution (construct, project) algebra dual
pair with Self::as_unquote on the projection side. THEORY.md
§II.1 invariant 2 — free middle; every consumer that has a
runtime UnquoteForm marker + an owned inner body and wants to
build a template-substitution wrapper routes through the SAME
typed method, so a regression that drifts one consumer’s subset
marker → wrapper mapping cannot reach the substrate’s runtime.
THEORY.md §V.1 — knowable platform; the marker-driven template-
substitution typed-construct becomes a TYPE projection on the
substrate’s outer Sexp algebra sitting next to the typed-
project family Self::as_unquote / Self::as_unquote_form
rather than the closed-set UnquoteForm::wrap method threaded
as a method call on a bound-marker value or the two-step subset
→ superset then Self::quote_form composition. THEORY.md
§VI.1 — generation over composition; the marker-driven template-
substitution pair emerges from ONE typed-algebra composition
through UnquoteForm::wrap rather than from per-consumer
subset-marker → wrapper dispatch literals; a future third
template-substitution marker (e.g. a ,~ reverse-unquote)
extends UnquoteForm::ALL + UnquoteForm::to_quote_form’s
dispatch table in lockstep — rustc-enforced through the closed-
set exhaustiveness — with THIS constructor inheriting the
extension through the UnquoteForm::wrap composition site
without a per-site edit.
Frontier inspiration: Racket’s (datum->syntax stx (list #'uf inner)) marker-abstracted template-substitution construct
restricted to the substitution-subset of syntactic-form kinds,
paired one-for-one with syntax-e on the projection face — the
typed-construct + typed-project algebra dual is closed on
Racket’s syntax algebra at one method per direction per subset,
and Sexp::unquote_form / Sexp::as_unquote is the Rust-typed
peer on the closed-set outer Sexp algebra with
UnquoteForm standing in for Racket’s substitution-subset
syntactic-form taxonomy. MLIR’s typed factory
mlir::OpBuilder::create<UnquoteFamilyOp>(loc, marker, operands)
paired with the projection sibling
mlir::dyn_cast<UnquoteFamilyOp>(op) — the typed factory + typed
downcast pair the IR algebra closes over on every op-family
subset at one method per direction; Sexp::unquote_form /
Self::as_unquote_form is the Rust-typed peer on the outer
Sexp algebra with the closed-set UnquoteForm standing in
for MLIR’s OperationName subset taxonomy over the template-
substitution op family.
pub fn is_list(&self) -> bool
pub fn as_list(&self) -> Option<&[Sexp]>
Sourcepub fn list<I>(items: I) -> Sexpwhere
I: IntoIterator<Item = Sexp>,
pub fn list<I>(items: I) -> Sexpwhere
I: IntoIterator<Item = Sexp>,
Canonical Self::List outer constructor — collects an
impl IntoIterator<Item = Sexp> into the tuple-variant payload
Vec<Sexp> at ONE site on the closed-set Sexp algebra. The
residual-axis section-for-retraction sibling of the existing
Self::as_list soft-projection ([Option<&[Sexp]>]): where
the projection soft-decomposes a Self::List arm into its
borrowed inner slice, this constructor embeds a fresh owned
item sequence into the matching tuple-variant wrapper. Sibling
of the atomic-payload construct family (Self::symbol,
Self::keyword, Self::string, Self::int,
Self::float, Self::boolean — all routing through the
typed Atom construct family on the 6-of-12 atomic-payload
carving) and the quote-family construct family (Self::quote,
Self::quasiquote, Self::unquote, Self::unquote_splice
— all routing through the typed QuoteForm::wrap family on
the 4-of-12 quote-family carving); closes the (construct,
project) algebra dual on the third and final structural carving
of the outer Sexp closed set — the 2-of-12 residual axis
covering Self::Nil and Self::List. Self::Nil is a
unit variant carrying no payload — the residual-axis
construct family closes at ONE constructor (this method) for
the sole payload-bearing residual arm.
Composition law (forward): Sexp::list(items) == Sexp::List(items.into_iter().collect::<Vec<Sexp>>()) for every
items: impl IntoIterator<Item = Sexp>. Round-trip law
(section-for-retraction with the soft-projection sibling): for
every items: Vec<Sexp>, Sexp::list(items.clone()).as_list() == Some(items.as_slice()) — the outer algebra’s typed
constructor pairs section-for-retraction with the outer
algebra’s soft projection, and the borrowed-slice cross-
projection preserves identity. Sibling posture across the
three axis-construct families on the outer Sexp algebra
(atomic + quote-family + residual).
Outer-shape composition law: Sexp::list(items).shape() == SexpShape::List for every items: impl IntoIterator<Item = Sexp> — the residual-arm outer-shape identity binds through
the typed-shape lattice at ONE arm, symmetric with the
quote-family construct family’s outer-shape composition
Sexp::X_variant(inner).shape() == QuoteForm::X.sexp_shape()
and the atomic construct family’s Sexp::X_atom(payload).shape() == AtomKind::X.sexp_shape(). Structural-carving-marker
composition law: Sexp::list(items).as_structural_kind() == Some(StructuralKind::List) for every items: impl IntoIterator<Item = Sexp> — the residual-axis carving marker
binds through the closed-set StructuralKind algebra at ONE
arm, symmetric with the atomic-axis’s Sexp::X_atom(payload) .as_atom_kind() == Some(AtomKind::X) marker composition.
Pre-lift the [Self::List(Vec<Sexp>)] welded pair
(Self::List tuple-variant constructor + Vec<Sexp>
payload) appeared inline at every consumer that builds a
list-shaped Sexp value — well past the ≥2 PRIME-DIRECTIVE
trigger once the structural shape is named. Post-lift the
welded pair binds at ONE typed-algebra method on the outer
Sexp algebra with an impl IntoIterator<Item = Sexp>
bound so consumers that have a Vec<Sexp>, a [Sexp; N]
array, an iter().cloned() sequence, a
.map(...).collect()-worthy chain, or a
once(head).chain(tail) composition can hand the sequence
directly to the algebra without a per-site .collect::<Vec< Sexp>>() coercion. A future allocation-policy change (e.g.
arena-allocated lists for span-aware Sexp) lands as ONE
edit at this method site and propagates through consumers
byte-for-byte.
Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
(list-shaped inner sequence, Self::List tuple-variant
constructor) pairing binds at ONE typed-algebra method on the
outer Sexp algebra, closing the outer-algebra construct
family across ALL THREE structural carvings of the SexpShape
closed set (atomic-payload + quote-family + residual). THEORY.md
§II.1 invariant 2 — free middle; every consumer that has an
owned or iterable sequence of Sexp and wants to build a
list-shaped wrapper routes through the SAME typed method, so a
regression that drifts one consumer’s construction from the
others cannot reach the substrate’s runtime. THEORY.md §V.1 —
knowable platform; the typed-construct family becomes a TYPE
projection on the substrate’s outer Sexp algebra sitting
next to the typed-project family Self::as_list rather than
bare tuple-variant constructor + per-site Vec<Sexp> discipline.
THEORY.md §VI.1 — generation over composition; the residual-
arm outer-shape + carving-marker pairings emerge from ONE
typed-algebra composition on the outer Sexp algebra rather
than from per-consumer per-variant literals.
Frontier inspiration: Racket’s (list x y z) typed list-
construct primitive paired one-for-one with (list? v) /
(car v) / (cdr v) predicate/projection siblings on the
same closed-set list shape — the typed-construct + typed-
project algebra dual is closed at one method per direction on
Racket’s surface, and Self::list / Self::as_list is
the Rust-typed peer on the closed-set outer Sexp algebra
with impl IntoIterator<Item = Sexp> standing in for Racket’s
variadic collect face. MLIR’s mlir::OpBuilder::create< ListOp>(loc, elements) typed-IR list-op construction paired
with mlir::dyn_cast<ListOp>(op) on the projection face —
the typed factory + typed downcast pair the IR algebra closes
over on every list-shaped op; Self::list / Self::as_list
is the Rust-typed peer on the outer Sexp algebra with
StructuralKind::List standing in for MLIR’s OperationName
taxonomy over the list-shaped op family.
Sourcepub fn as_structural_kind(&self) -> Option<StructuralKind>
pub fn as_structural_kind(&self) -> Option<StructuralKind>
Soft projection onto the closed-set StructuralKind residual
carving marker — the 2-of-12 carving of the SexpShape algebra
covering Self::Nil and Self::List (the outer shapes that
lie OUTSIDE both the atomic-payload carving
AtomKind and the
quote-family carving
QuoteForm). Returns
Some(StructuralKind::Nil) for Self::Nil,
Some(StructuralKind::List) for Self::List, None for
every other outer shape (every Self::Atom variant, every
quote-family wrapper: Self::Quote, Self::Quasiquote,
Self::Unquote, Self::UnquoteSplice).
Sibling soft-projection peer of Self::as_quote_form (the
soft-decomposition of the four homoiconic prefix wrappers into
(QuoteForm, &Sexp)) and Self::as_unquote (the
soft-decomposition of the two template-substitution wrappers
into (UnquoteForm, &Sexp)). Direct value-level peer of the
shape-level projection
SexpShape::as_structural_kind
— the pair (Sexp::as_structural_kind, SexpShape::as_structural_kind)
binds the (Sexp value, StructuralKind carving marker) pairing at
ONE typed method on each algebra, symmetric with the existing
(Sexp value → AtomKind via
Sexp::as_atom().map(Atom::kind)) atomic-axis composition and
the direct (Sexp value → QuoteForm) marker projection
Self::as_quote_form returns.
Composition law: s.as_structural_kind() == s.shape().as_structural_kind() for every s: &Sexp. Pre-lift
the residual-carving marker at the value level was reachable
only via the two-step composition
s.shape().as_structural_kind() (walking through the full
12-variant SexpShape closed set to arrive at the 2-of-12
carving marker); post-lift the composition lands at ONE typed
method on the value algebra — the Nil arm returns Some(Nil)
directly and the List arm returns Some(List) directly,
matching the residual-carving membership at the value level.
The composition law is pinned by
sexp_as_structural_kind_agrees_with_shape_as_structural_kind_for_every_variant
in this module, so a regression that drifts either projection
from the other surfaces immediately.
Sibling-shape lift to Self::is_list (the bare List-arm
predicate) and Self::is_kwargs_list (the narrower
kwargs-shaped List cohort predicate): where is_list returns
true iff the value inhabits the List arm of the residual
carving, as_structural_kind returns the typed carving marker
that binds BOTH residual arms (Nil and List) at ONE typed
projection — the operator answering “which residual arm?”
rather than the bare “is this the List arm?” predicate.
Theory anchor: THEORY.md §V.1 — knowable platform; the
(Sexp variant, StructuralKind carving marker) pairing becomes a
TYPE projection on the substrate Sexp algebra rather than a
two-step composition through the shape-level projection. A typo
or swap at the value-projection site is no longer a runtime
drift but a compile error against the typed projection.
THEORY.md §VI.1 — generation over composition; the
residual-carving marker projection now lives on the typed
Sexp algebra alongside Self::as_atom, Self::as_list,
Self::as_quote_form, Self::as_unquote, completing the
(Sexp value → closed-set carving marker) family at the residual
axis. THEORY.md §II.1 invariant 2 — free middle; every consumer
that needs the residual-carving marker at the value level (a
future tatara-check predicate keyed on the Nil/List cohort, a
future LSP structural-navigation filter that keys on the
residual carving, a future typed-rewriter walk over the
residual arm) binds to ONE typed method on the value algebra
rather than a two-step composition through the shape-level
projection.
Frontier inspiration: MLIR’s mlir::dyn_cast<StructuralOp>(val)
typed soft-downcast on the residual carving of a closed-set
value algebra — the (value, typed carving marker) pairing lives
at ONE typed projection on the outer value-algebra sibling. The
Rust-typed peer here uses the substrate’s outer Sexp algebra
with Sexp::as_structural_kind closing the residual-carving
cell of the value-level soft-projection surface, symmetric with
the atomic-axis composition through Self::as_atom and the
quote-family projection Self::as_quote_form.
Sourcepub fn is_kwargs_list(&self) -> bool
pub fn is_kwargs_list(&self) -> bool
Structural-shape predicate — true iff this is a Self::List
whose items form a non-empty, even-length (:k v :k v …) kwargs
sequence with every even-indexed item being an Atom::Keyword.
false for every other outer shape (Self::Nil, every
Self::Atom variant, every quote-family wrapper) and for every
Self::List that fails the kwargs convention (empty list, odd
length, or any even-indexed non-keyword).
The structural witness that Self::to_json will project this
value as serde_json::Value::Object rather than
serde_json::Value::Array at the Self::List arm — the
(Sexp variant + kwargs shape, JSON canonical-form) pairing
binds at ONE inherent method on the algebra rather than at a
free function consumers must reach into the domain module
path to invoke. Inverse round-trip law: every
Self::from_json projection of a serde_json::Value::Object
satisfies this predicate (the Self::List arm
Self::from_json builds for an Object is non-empty by the
Object’s non-empty-keys invariant when present, even-length by
the alternating :k v build, and keyword-headed at every even
index by the Self::keyword(camel_to_kebab(k)) build — except
for the structurally degenerate empty Object which projects to
Sexp::List(vec![]) and returns false here, matching
Self::to_json’s “empty-list ↛ kwargs” gate).
Composes through Self::as_list (the structural soft-projection
onto &[Sexp]) and Atom::as_keyword (the typed soft-projection
onto the keyword payload from the Atom algebra) — the predicate
is rebuilt from already-lifted algebra primitives rather than
inline-matching the Self::List arm. Sibling-shape predicate
peer of Self::is_list (the unconditional Self::List-arm
predicate), with this method narrowing the structural witness to
the kwargs-shaped sub-cohort. The two predicates partition the
list-typed cell of the algebra: every Self::List either
satisfies is_kwargs_list (projects as serde_json::Value::Object
through Self::to_json) or does not (projects as
serde_json::Value::Array).
Theory anchor: THEORY.md §VI.1 — generation over composition; the
kwargs-shape predicate, previously a pub(crate) free function in
domain.rs reached across the module boundary by Self::to_json,
is lifted ONE algebra level higher onto the inherent method on
the Sexp algebra — completing the structural-predicate family
alongside Self::is_list and the soft-projection family
(Self::as_atom, Self::as_list, Self::as_quote_form).
THEORY.md §II.1 invariant 2 — free middle; every consumer that
queries “would Self::to_json project this as Object?” (the
Self::to_json arm itself, future authoring-tool diagnostics, a
future LSP completion fallback, a future REPL pretty-printer that
chooses between (…) and {…} rendering, a future tatara-check
typed-pattern matcher) routes through ONE inherent algebra method
rather than reaching into the domain module path for a free
function. THEORY.md §V.1 — knowable platform; the JSON-format
witness becomes a TYPE projection on the substrate Sexp algebra
next to its sibling Sexp::is_list / Sexp::as_list pair rather
than living in a domain.rs pub(crate) helper consumers must
import via module path.
Frontier inspiration: MLIR’s mlir::Operation::hasTrait<T>() —
typed-IR operations carry their structural traits as inherent
methods on the operation algebra rather than as free functions
in a sibling module; Sexp::is_kwargs_list is the
unstructured-Rust peer on the Sexp algebra for the
“would-this-project-as-Object” structural trait. Racket’s
(keyword-apply-procedure? stx) — the syntax-class predicate
that gates a kwargs-style application form’s printer / expander
path on the syntax algebra; Sexp::is_kwargs_list is the
substrate’s peer at the Sexp layer, with the as_list(). is_some_and(…) composition standing in for Racket’s
syntax-parse pattern matcher.
Sourcepub fn as_atom(&self) -> Option<&Atom>
pub fn as_atom(&self) -> Option<&Atom>
Soft projection onto the inner Atom payload — Some(&Atom)
iff this is a Self::Atom variant, None for every other
outer shape (Nil, List, Quote, Quasiquote, Unquote,
UnquoteSplice). The structural-lift face of the per-atomic-
payload soft-projection family — composes with the typed
Atom::as_symbol / Atom::as_keyword / Atom::as_string
/ Atom::as_int / Atom::as_float / Atom::as_bool
projections to give the six Sexp::as_X consumers ONE typed
boundary instead of six inline Self::Atom(Atom::X(s)) => Some(s)
arms.
Sibling soft-projection peer of Self::as_quote_form (the
soft-decomposition of the four homoiconic prefix wrappers into
(QuoteForm, &Sexp)) and Self::as_list (the soft-decomposition
of the structural list constructor into &[Sexp]). Together the
three projections (as_atom, as_list, as_quote_form) and
their nullary peer (Self::Nil via matches!(self, Self::Nil))
cover every outer-shape arm of the Sexp algebra: Nil + Atom +
List + 4 quote-family arms = 7 outer shapes, with the typed-
projection set partitioning them by structural axis.
Composition law binding Sexp::as_X to the typed Atom algebra:
for every Sexp s,
s.as_symbol() (and each as_keyword / as_string / as_int /
as_bool sibling) == s.as_atom().and_then(Atom::as_<variant>).
The Sexp::as_float consumer specializes through the widening
inline composition s.as_atom().and_then(|a| a.as_float() .or_else(|| a.as_int().map(|n| n as f64))) so the algebra-level
Atom::as_float stays strict and the typed-identity
distinction Int(1) vs Float(1.0) is preserved at the algebra
layer (see Atom::as_int’s docstring for the discipline).
Theory anchor: THEORY.md §VI.1 — generation over composition;
the six inline Self::Atom(Atom::X(s)) => Some(_) arms across
the Sexp::as_X family is past the three-times rule. THEORY.md
§II.1 invariant 2 — free middle; SIX consumers (as_symbol,
as_keyword, as_string, as_int, as_float, as_bool) now
route through ONE typed structural lift (this method) AND ONE
per-variant projection family on the closed-set Atom algebra
rather than six byte-identical outer-arm matches each.
THEORY.md §V.1 — knowable platform; the (Sexp variant, inner
payload kind) pairing becomes a TYPE projection on the substrate
algebra rather than six inline arms scattered across the six
Sexp::as_X consumers.
Sourcepub fn as_atom_kind(&self) -> Option<AtomKind>
pub fn as_atom_kind(&self) -> Option<AtomKind>
Soft projection onto the closed-set AtomKind atomic-payload
carving marker — the 6-of-12 carving of the SexpShape algebra
covering Self::Atom’s six per-payload variants (Atom::Symbol,
Atom::Keyword, Atom::Str, Atom::Int, Atom::Float,
Atom::Bool). Returns Some(a.kind()) iff this is a
Self::Atom variant, None for every other outer shape
(Self::Nil, Self::List, every quote-family wrapper:
Self::Quote, Self::Quasiquote, Self::Unquote,
Self::UnquoteSplice).
Direct value-level peer of the shape-level projection
SexpShape::as_atom_kind
— the pair (Sexp::as_atom_kind, SexpShape::as_atom_kind) binds
the (Sexp value, AtomKind carving marker) pairing at ONE typed
method on each algebra, closing the atomic-axis cell of the
(Sexp value → carving marker) matrix. Sibling soft-projection
peer of Self::as_structural_kind (the 2-of-12 residual
carving returning Option<StructuralKind>) and
Self::as_quote_form (the 4-of-12 quote-family carving
returning Option<(QuoteForm, &Sexp)>) — post-lift ALL THREE
carvings that partition the twelve outer shapes of the
SexpShape algebra have a marker-only value-level projection
on Sexp: as_atom_kind (atomic axis), as_quote_form
(quote-family axis, marker + inner), as_structural_kind
(residual axis). The Sexp::as_atom projection stays available
for consumers that need the inner Atom payload for further
per-variant typed projection (Atom::as_symbol et al.); this
projection is the shortcut for consumers that only need the
carving-marker identity.
Composition laws (dual bindings): s.as_atom_kind() == s.as_atom().map(Atom::kind) == s.shape().as_atom_kind() for
every s: &Sexp. Pre-lift the atomic carving marker at the
value level was reachable only via one of these two-step
compositions — either through the Atom algebra
(as_atom().map(Atom::kind)) or through the shape algebra
(shape().as_atom_kind()). Post-lift the projection lands at
ONE typed method on the value algebra, and both compositions
are pinned as agreement laws (see
sexp_as_atom_kind_agrees_with_as_atom_map_kind_for_every_variant
and
sexp_as_atom_kind_agrees_with_shape_as_atom_kind_for_every_variant
in this module). A regression that drifts any of the three
projections from the others surfaces immediately.
Symmetric with Self::as_structural_kind’s shape (returns
just the marker, no inner-payload borrow) — where
Self::as_quote_form and Self::as_unquote surface both
the marker AND the wrapped inner &Sexp (because the four
quote-family arms and the two substitution arms structurally
carry a boxed inner value), as_atom_kind and
as_structural_kind return marker-only projections (the atomic
arm’s inner payload is heterogeneous across the six variants —
String / i64 / f64 / bool — and the residual arms
carry no or list-heterogeneous payload). Consumers that need
the payload compose through Self::as_atom +
Atom::as_symbol et al. (atomic axis) or Self::as_list
(residual axis); this projection is the payload-agnostic
carving-marker cell.
Theory anchor: THEORY.md §V.1 — knowable platform; the (Sexp
variant, AtomKind carving marker) pairing becomes a TYPE
projection on the substrate Sexp algebra rather than a
two-step composition through either the Atom algebra or the
shape algebra. A typo or swap at the value-projection site is
no longer a runtime drift but a compile error against the
typed projection. THEORY.md §VI.1 — generation over composition;
the atomic-carving marker projection now lives on the typed
Sexp algebra alongside Self::as_atom, Self::as_list,
Self::as_quote_form, Self::as_unquote,
Self::as_structural_kind, completing the (Sexp value →
closed-set carving marker) family across ALL THREE axes
(atomic + quote-family + structural-residual). THEORY.md §II.1
invariant 2 — free middle; every consumer that needs the
atomic-carving marker at the value level (a future
tatara-check predicate keyed on the atomic cohort, a future
LSP structural-navigation filter that keys on the atomic
carving, a future typed-rewriter walk over the atomic arm)
binds to ONE typed method on the value algebra rather than a
two-step composition.
Sibling posture across the value-level marker family — the
three projections (as_atom_kind, as_quote_form,
as_structural_kind) form a partition of the seven outer-shape
variants of the Sexp algebra: for every s: &Sexp, EXACTLY
ONE returns Some(_) (pinned by the joint sweep
sexp_as_atom_kind_partitions_outer_shapes_jointly_with_as_quote_form_and_as_structural_kind
in this module, sibling to the pre-existing partition sweep
keyed on as_atom rather than as_atom_kind). The value-level
partition-total invariant across the three carvings is the
value-level peer of the shape-level partition-total invariant
(sexp_shape_partition_is_total_across_atom_quote_structural_carvings
in error.rs); each axis has BOTH invariants pinned.
Frontier inspiration: MLIR’s mlir::dyn_cast<AtomOp>(val) typed
soft-downcast onto the atomic carving of a closed-set value
algebra — the (value, typed carving marker) pairing lives at
ONE typed projection on the outer value-algebra sibling. The
Rust-typed peer here uses the substrate’s outer Sexp algebra
with Sexp::as_atom_kind closing the atomic-carving cell of
the value-level soft-projection surface, symmetric with the
residual-carving projection Self::as_structural_kind and
the quote-family projection Self::as_quote_form. Racket’s
(atom? stx) predicate paired with (syntax->datum stx) on
the atomic branch — the substrate’s as_atom_kind surfaces the
typed witness (AtomKind) alongside the predicate verdict in
ONE Option<AtomKind> projection.
Sourcepub fn shape(&self) -> SexpShape
pub fn shape(&self) -> SexpShape
Project this Sexp to its closed-set SexpShape outer-shape
marker — Nil → SexpShape::Nil, Atom(a) → a.kind().sexp_shape(),
List(_) → SexpShape::List, and each quote-family wrapper routes
through as_quote_form().map(|(qf, _)| qf.sexp_shape()). The
outer-shape peer on the Sexp algebra of Atom::kind (the
atomic-payload axis) and QuoteForm::sexp_shape (the
quote-family axis) — completes the substrate’s Sexp-shape
projection family by lifting the free-function dispatcher
crate::domain::sexp_shape onto the typed Sexp algebra
alongside its Atom / QuoteForm peers.
Composition law: s.shape() == crate::domain::sexp_shape(s) for
every s: &Sexp. The free function continues to exist as a thin
delegate (its callers in domain.rs’s diagnostic-builder paths,
compile.rs’s TypeMismatch.got builder, and downstream tests
route through s.shape() after this lift), so the (Sexp variant,
SexpShape variant) pairing now binds at ONE inherent method on
the algebra rather than at a free function domain consumers
must reach into the module path to invoke.
Sibling-shape lift to the typed-EXIT projection trio on Atom
([fmt::Display for Atom], Atom::to_json,
Atom::to_iac_forge_sexpr (removed)) and the typed-ENTRY classifier
(Atom::from_lexeme): where the atomic-payload algebra carries
its own per-variant projection family at the atomic-payload
level, the Sexp algebra carries this single outer-shape
projection that composes through Self::as_atom +
Atom::kind (atomic axis) and Self::as_quote_form (quote-
family axis) — every other arm (Nil, List) projects to its
own SexpShape variant directly.
Theory anchor: THEORY.md §V.1 — knowable platform; the
(Sexp variant, SexpShape variant) pairing becomes an inherent
algebra projection rather than a free function in domain.rs,
so the projection sits next to the rest of the typed Sexp
algebra (Self::as_atom, Self::as_list,
Self::as_quote_form, Self::head_symbol,
Self::as_call) the substrate carries. THEORY.md §II.1
invariant 2 — free middle; every consumer that needs the
outer shape (diagnostic builders at
crate::domain::sexp_witness / crate::domain::missing_head_err,
crate::compile’s TypeMismatch.got projection, future LSP /
REPL / tatara-check typed-pattern matchers) now reaches a
method on the value rather than a free function imported from
domain. THEORY.md §VI.1 — generation over composition; the
inline dispatch lifted to crate::domain::sexp_shape is now
lifted ONE algebra level higher — from the free function to
the inherent method — so a future Sexp variant lands at the
algebra’s match site without a module-path indirection. A
future extension (e.g. Sexp::Vector for #(...) reader
syntax, Sexp::Map for {...}) extends THIS method + the
SexpShape algebra + the free function’s delegation in
lockstep — exhaustively checked by rustc across the Sexp
match.
Frontier inspiration: MLIR’s mlir::Operation::getName() —
the typed-IR operation projects through an inherent method
to its closed-set name on the operation algebra; Sexp::shape
is the unstructured-Rust peer on the Sexp algebra for the
outer-shape projection surface, with SexpShape standing in
for MLIR’s OperationName taxonomy. Racket’s (syntax-e stx)
composed with a datum-prim classifier on the closed-set
syntax-taxonomy projects a syntax object to its outer shape via
a single primitive on the syntax algebra; Sexp::shape is the
substrate’s typed-Rust peer.
Sourcepub fn witness(&self) -> SexpWitness
pub fn witness(&self) -> SexpWitness
Project this Sexp to its SexpWitness — the typed joint
identity pairing the structural SexpShape with the
renderable [Sexp::Display] projection in ONE owned value.
The joint-identity peer on the Sexp algebra of
Self::shape (the structural-shape-only projection) and
[fmt::Display for Sexp] (the rendered-literal-only
projection) — completes the substrate’s Sexp-projection
family by lifting the free-function dispatcher
crate::domain::sexp_witness onto the typed Sexp algebra
alongside its Self::shape peer.
Composition law: s.witness() == crate::domain::sexp_witness(s) for every s: &Sexp. The
free function continues to exist as a thin delegate (its
callers in macro_expand.rs’s 8 typed-entry rejection
builders, domain.rs’s missing_head_err caller +
rewriter_non_list_err typed-exit builder, and downstream
tests route through s.witness() after this lift), so the
(Sexp variant, SexpWitness identity) pairing now binds at
ONE inherent method on the algebra rather than at a free
function domain consumers must reach into the module path
to invoke. Body composes the two algebra-level projections
— self.shape() for the structural identity, self.to_string()
for the renderable identity — into ONE
SexpWitness::new call. Pre-lift the dispatcher lived as
a free function in domain.rs; post-lift the canonical site
is the inherent method and the free function delegates
(mirrors the Self::shape lift in 121bb60 exactly).
Sibling-shape lift to Self::shape (the structural-shape
projection): where shape() carries the typed-shape axis on
the Sexp algebra, witness() carries the JOINT typed-shape
and renderable-literal axis — the typed identity an authoring
tool diagnostic owes the operator AT the typed-entry or
typed-exit rejection boundary. Every rejection-builder
helper in macro_expand.rs that previously projected &Sexp
through crate::domain::sexp_witness(_) at the variant
boundary now reaches a method on the value rather than a
free function imported from domain.
Theory anchor: THEORY.md §V.1 — knowable platform; the
(Sexp variant, SexpWitness identity) pairing becomes an
inherent algebra projection rather than a free function in
domain.rs, so the projection sits next to the rest of the
typed Sexp algebra (Self::shape, Self::as_atom,
Self::as_list, Self::as_quote_form,
Self::head_symbol, Self::as_call) the substrate
carries. THEORY.md §II.1 invariant 2 — free middle; every
consumer that needs the typed joint identity at a
rejection-boundary slot (NonSymbolUnquoteTarget.got,
SpliceOutsideList.got, NonSymbolParam.got,
RestParamMissingName.got, RestParamTrailingTokens.first,
OptionalParamMalformed.got, DefmacroNonSymbolName.got,
DefmacroNonListParams.got, MissingHeadSymbol.got,
RewriterNonList.got, future LSP / REPL / tatara-check
typed-pattern matchers) now reaches a method on the value
rather than a free function imported from domain.
THEORY.md §VI.1 — generation over composition; the inline
dispatch lifted to crate::domain::sexp_witness is now
lifted ONE algebra level higher — from the free function
to the inherent method — completing the Sexp-projection
family alongside Self::shape. A future Sexp variant
extension (e.g. Sexp::Vector for #(...) reader syntax,
Sexp::Map for {...}) reaches this method through the
already-lifted Self::shape + [fmt::Display for Sexp]
pair — no new arm needed here.
Frontier inspiration: MLIR’s diagnostic builder pattern —
op.emitOpError() << op projects the offending operation
through inherent methods (getName(), print()) into ONE
diagnostic value; Sexp::witness is the unstructured-Rust
peer on the Sexp algebra for the joint typed-shape +
renderable-literal projection surface, with SexpWitness
standing in for MLIR’s InFlightDiagnostic typed payload.
Sourcepub fn type_name(&self) -> &'static str
pub fn type_name(&self) -> &'static str
Project this Sexp to its stable, human-readable outer-shape
label — the &'static str axis on the Sexp algebra. Lifts
the free-function dispatcher crate::domain::sexp_type_name
onto the typed Sexp algebra alongside its Self::shape /
Self::witness / Self::to_json / Self::from_json
sibling projections, completing the substrate’s
Sexp-projection family at the canonical-label axis the way
Self::shape completes the typed-shape axis and
[fmt::Display for Sexp] completes the canonical-string axis.
Composition law: s.type_name() == s.shape().label() == crate::domain::sexp_type_name(s) for every s: &Sexp.
Pre-lift the projection lived as a free function in
domain.rs consumers (in particular the LispError::TypeMismatch
got slot in compile.rs and the legacy substring-grep
rejection-message tests) reached across module boundaries to
invoke; post-lift the canonical site is the inherent method on
the Sexp algebra and the free function delegates so existing
callers continue to compile. Body composes through
Self::shape + SexpShape::label so a future Sexp
variant (e.g. Sexp::Vector for #(...) reader syntax,
Sexp::Map for {...}) lands at one extension site
(Self::shape’s exhaustive arm) rather than a parallel
&'static str match — the projection is structurally derived,
not duplicated.
Sibling-shape lift to Self::shape (the typed-shape
projection): where shape() carries the typed
SexpShape identity (matchable, exhaustive across Sexp
variants), type_name() carries the &'static str literal
the rendered diagnostic surface wants (still derived from
the typed identity, but flattened through
SexpShape::label for substring-grep callers and the
TypeMismatch.got slot). The &'static str lifetime makes
the projection cheap to embed in any error variant without
allocation.
Theory anchor: THEORY.md §V.1 — knowable platform; the
(Sexp variant, &'static str label) pairing becomes an
inherent algebra projection rather than a free function in
domain.rs, so the projection sits next to the rest of the
typed Sexp algebra (Self::shape, Self::witness,
Self::to_json, Self::from_json, Self::as_atom,
Self::as_list, Self::as_quote_form,
Self::head_symbol, Self::as_call) the substrate
carries. THEORY.md §II.1 invariant 2 — free middle; every
consumer that needs the outer-shape label
(LispError::TypeMismatch.got projection in compile.rs,
legacy substring-grep rejection-message tests, future LSP /
REPL diagnostic surfaces) now reaches a method on the value
rather than a free function imported from domain.
THEORY.md §VI.1 — generation over composition; the inline
s.shape().label() recipe lifted to
crate::domain::sexp_type_name is now lifted ONE algebra
level higher — from the free function to the inherent
method — completing the Sexp-projection family alongside
Self::shape / Self::witness / Self::to_json /
Self::from_json. The domain.rs sexp_* free-function
namespace is now structurally reserved for free functions
that genuinely need a domain-module reach (registry
dispatch, kwargs gates, registry suggestions), not
algebra-layer projections.
Frontier inspiration: MLIR’s mlir::Operation::getName()
composed with OperationName::getStringRef() — the typed-IR
operation projects through inherent methods to its closed-set
label on the operation algebra; Sexp::type_name is the
unstructured-Rust peer on the Sexp algebra for the
canonical-label projection surface, with SexpShape::label
standing in for MLIR’s OperationName::getStringRef second
hop. Racket’s (syntax-name stx) — the typed inverse of
(syntax-e stx) on the syntax algebra; Sexp::type_name
composes the typed-shape projection with its closed-set
label projection at the inherent-method site rather than
the typeclass-method site, matching pleme-io’s
“rust-typed, not trait-typed” idiom for closed-set algebras.
Sourcepub fn to_json(&self) -> Result<Value, LispError>
pub fn to_json(&self) -> Result<Value, LispError>
Project this Sexp to its canonical serde_json::Value
rendering — the typed-algebra peer of Atom::to_json at the
Sexp layer. Lifts the free-function dispatcher
crate::domain::sexp_to_json onto the typed Sexp algebra
alongside its Self::shape / Self::witness sibling
projections, completing the JSON-projection axis at the
algebra layer the way [fmt::Display for Sexp] completes the
canonical-string axis. The free function continues to exist
as a thin delegate (its callers in tatara-lisp-derive’s
derive output route through it via the
crate::domain::sexp_to_json import); the
from_value_with_path private helper in domain.rs and the
recursive sub-calls inside this method route through the
inherent method directly so the canonical-site indirection
disappears at every internal callsite.
Rules (preserve byte-identical pre-lift behavior at the
sexp_to_json callsite):
Self::Nil→serde_json::Value::Null.Self::Atom→Atom::to_json(the typed-algebra peer at the atomic-payload layer; pinned bysexp_to_json_atom_arms_route_through_atom_to_jsonindomain.rs).Self::Listwith kwargs shape(:k v :k v …)→serde_json::Value::Objectkeyed by [crate::domain::kebab_to_camel] of each:k’s name. A duplicate kebab→camel key inside any nested kwargs-list fails withcrate::domain::duplicate_kwarg— same typed-entry posturecrate::domain::parse_kwargstakes at the top level.Self::Listotherwise →serde_json::Value::Arraymapping each element through this method recursively.Self::Quote/Self::Quasiquote/Self::Unquote/Self::UnquoteSplice→ recurse on the inner viaSelf::expect_quote_form(strips the wrapper; the round-trip viacrate::domain::json_to_sexpre-emits the inner without an enclosing wrapper). All four arms route through ONESelf::as_quote_form-derived projection so the per-variant pairing binds at ONE site on theQuoteFormalgebra rather than four byte-identical inline arms.
Composition law: s.to_json() == crate::domain::sexp_to_json(s)
for every s: &Sexp. Pre-lift the dispatcher lived as a free
function in domain.rs; post-lift the canonical site is the
inherent method and the free function delegates (same lift
posture as Self::shape in 121bb60 and Self::witness
in a427e3b).
Sibling-shape lift to Self::shape (the structural-shape
projection), Self::witness (the joint structural-shape +
renderable-literal projection), and [fmt::Display for Sexp]
(the renderable-literal projection): where those three carry
the Lisp-canonical-form / structural-identity axes on the
algebra, to_json carries the JSON canonical-form axis. The
substrate’s Sexp algebra now binds ALL THREE canonical-form
projection surfaces (Lisp Display, JSON, and the feature-gated
iac-forge From<&Sexp> for SExpr) at the algebra layer, with
per-variant atomic rendering composed through the corresponding
Atom projection family (Atom::Display, Atom::to_json,
Atom::to_iac_forge_sexpr).
Theory anchor: THEORY.md §VI.1 — generation over composition;
the inline dispatch the prior runs lifted onto
crate::domain::sexp_to_json (the free function) is now
lifted ONE algebra level higher — from the free function to
the inherent method — completing the Sexp-projection family
alongside Self::shape and Self::witness. THEORY.md
§II.1 invariant 2 — free middle; the typed-exit JSON
projection (every consumer that round-trips a Sexp through
serde_json::from_value::<T> for typed-domain
deserialization, the typed-rewriter at
[crate::domain::TypedRewriter], the derive macro’s
compile_from_args fallthrough, and any future canonical-
form surface) all route through ONE inherent algebra method
rather than reach into the domain module path for a free
function. THEORY.md §V.1 — knowable platform; a future
Sexp variant extension (e.g. Sexp::Vector for #(...)
reader syntax, Sexp::Map for {...}) reaches this method
through the already-lifted Self::as_quote_form +
Atom::to_json pair — one arm added here for the new
outer-shape variant; rustc enforces the per-variant body is
named.
Frontier inspiration: MLIR’s mlir::AsmPrinter::printOp —
the typed-IR printer dispatches on the closed-set Op so
every printer body for an op lives at ONE implementation site;
Sexp::to_json is the unstructured-Rust peer on the Sexp
algebra for the JSON canonical-form surface (where
[fmt::Display for Sexp] is the Lisp-canonical-form peer
and From<&Sexp> for iac_forge::SExpr is the
canonical-attestation-form peer). Racket’s (syntax->datum stx) then a serializer over the datum prim — to_json is
the substrate’s serializer at the Sexp layer composed
through Atom::to_json at the atomic-payload layer, with
the closed-set AtomKind standing in for Racket’s
datum-prim taxonomy.
Sourcepub fn from_json(v: &Value) -> Sexp
pub fn from_json(v: &Value) -> Sexp
Inverse of Self::to_json — project a serde_json::Value back
onto a Sexp. The closed-set serde_json::Value discriminator
maps directly onto the corresponding Sexp constructor:
serde_json::Value::Null→Self::Nil.serde_json::Value::Bool→Self::boolean.serde_json::Value::Number→Self::intwhen the value fits ani64, otherwiseSelf::floatwhen it fits anf64; the structural impossibility “neither i64 nor f64” collapses toSelf::int(0)as a typed floor —serde_json::Number’s closed-set discriminator excludes this case in practice (everyserde_json::Numberis either i64-fitting, u64-fitting projected through f64, or f64-fitting directly), but the typed floor stays explicit so a futureserde_jsonextension does not silently misroute. Mirror ofAtom::to_json’sSelf::int/Self::floatbifurcation.serde_json::Value::String→Self::string. Theserde_json::Value::Stringdiscriminator is type-erased — a serde-projected symbol AND a serde-projected keyword AND a genuine string literal ALL inhabit it on the JSON side — so the back-projection choosesSelf::stringas the lossless floor for theAtom::Symbol/Atom::Keyword/Atom::Strthree-way collapse. Consumers that need the symbol-vs-string distinction must preserve it BEFORE the JSON round-trip (e.g. through a typed enum’s serde projection rather than a rawSexp-to-JValueround-trip).serde_json::Value::Array→Self::Listmapping each element through this method recursively.serde_json::Value::Object→Self::Listof alternating:key valuepairs inserde_json::Map’s iteration order (sorted by key underserde_json’s defaultBTreeMapbacking; insertion order under the optionalpreserve_orderfeature, which the substrate does NOT enable today), with each JSON key projected through [crate::domain::camel_to_kebab] to recover the:k’s kebab-case authoring shape and each JSON value recursed through this method. Inverse ofSelf::to_json’sSelf::Listkwargs-shape arm: that arm projects:k v :k v …into a JSON object via [crate::domain::kebab_to_camel]; this arm projects the object back into aSelf::Listof alternating keyword / value via the inverse [crate::domain::camel_to_kebab].
Composition law: Self::from_json(&s.to_json()?) projects back
to a Sexp whose Self::to_json re-projection produces the
SAME JValue (modulo the lossy Symbol / Keyword / Str
three-way collapse documented above; for the round-trippable
subset, Sexp::Nil, the six Atom kinds within their
discriminator class, and recursively Sexp::List of round-
trippable elements, the law holds byte-for-byte).
Sibling-lift posture: this method mirrors the prior
crate::domain::sexp_to_json → Self::to_json (commit
875ee3b) / crate::domain::sexp_shape → Self::shape
(commit 121bb60) / crate::domain::sexp_witness →
Self::witness (commit a427e3b) family of lifts, all of which
promoted a free function in domain.rs to the inherent-method
canonical site on the Sexp algebra. Pre-lift the
json_to_sexp dispatcher lived in domain.rs as the canonical
site; post-lift this inherent method is the canonical site and
the free function delegates so every existing caller continues
to compile.
Sibling-shape lift on the round-trip closure: the substrate’s
Sexp ↔ serde_json::Value round-trip now lives entirely as
two inherent methods on the Sexp algebra — Self::to_json
(forward) and Self::from_json (inverse). Consumers that
previously round-tripped a typed value through Lisp forms via
domain::sexp_to_json + domain::json_to_sexp now bind to ONE
algebra (the inherent-method family) rather than reaching across
the domain module path for two free functions. A future
canonical-form surface (e.g., a YAML round-trip via
[serde_yaml], a Nix-expression round-trip via the typed Nix
surface in tatara-nix) hangs off the SAME Sexp algebra at
Self::to_yaml / Self::from_yaml / Self::to_nix /
Self::from_nix — the naming pattern is now structurally
established by this pair.
Theory anchor: THEORY.md §VI.1 — generation over composition;
the inline json_to_sexp dispatcher in domain.rs is lifted
ONE algebra level higher (from free function to inherent
method), completing the Sexp ↔ JValue round-trip closure
alongside Self::to_json. THEORY.md §V.1 — knowable
platform; the inverse projection becomes a NAMED primitive on
the substrate’s Sexp algebra rather than a domain-module
free function consumers reach across module boundaries to call.
THEORY.md §II.1 invariant 2 — free middle; every consumer that
round-trips through JSON (the typed-rewriter at
[crate::domain::TypedRewriter], the derive macro’s
compile_from_args JSON fallthrough, the test round-trip
fixtures) routes through ONE inherent algebra method — the
typed round-trip closure is structurally complete on the
Sexp algebra.
Frontier inspiration: MLIR’s mlir::parseAttribute(str, ctx) —
the typed-IR parser inverse of printAttribute lives on the
same Attribute algebra as its printer dual; the substrate’s
Self::from_json is the unstructured-Rust peer on the
Sexp algebra for the JSON canonical-form inverse, paired
with Self::to_json as the closed round-trip. Racket’s
(datum->syntax stx datum) — the round-trip inverse of
(syntax->datum stx), projected at the datum algebra layer;
Self::from_json is the substrate’s peer at the Sexp layer
(one algebra level lower than Racket’s syntax wrapper).
pub fn as_symbol(&self) -> Option<&str>
pub fn as_keyword(&self) -> Option<&str>
pub fn as_string(&self) -> Option<&str>
pub fn as_int(&self) -> Option<i64>
Sourcepub fn as_float(&self) -> Option<f64>
pub fn as_float(&self) -> Option<f64>
Some(f) for Atom::Float(f), AND Some(n as f64) for
Atom::Int(n) — caller convenience at the numeric-kwarg
boundary. The Int-widening face lives at this consumer layer
rather than at Atom::as_float (strict per the typed-identity
discipline pinned at Atom::as_int’s docstring); the typed
soft-projection algebra on Atom stays strict, and the
Sexp::as_float consumer composes the strict typed projection
with a fallback widening branch on Atom::as_int.
pub fn as_bool(&self) -> Option<bool>
Sourcepub fn as_symbol_or_string(&self) -> Option<&str>
pub fn as_symbol_or_string(&self) -> Option<&str>
foo or "foo" — useful for names that may be authored either way.
Structural-lift composition: routes through Sexp::as_atom + the
algebra-level Atom::as_symbol_or_string union projection — the
same as_atom().and_then(Atom::as_X) composition pattern
Sexp::as_symbol / Sexp::as_keyword / Sexp::as_string /
Sexp::as_int / Sexp::as_bool route through on the
per-variant axis. Lifts the disjunctive
self.as_symbol().or_else(|| self.as_string()) composition at this
site’s pre-lift body (TWO Sexp::as_atom traversals — one per
per-variant projection) onto ONE typed-algebra union projection
reached via ONE Sexp::as_atom traversal.
Composition law: s.as_symbol_or_string() == s.as_atom().and_then(Atom::as_symbol_or_string)
for every Sexp s. See Atom::as_symbol_or_string for the
algebra-level peer’s docstring (per-variant family completion +
theory grounding).
Sourcepub fn head_symbol(&self) -> Option<&str>
pub fn head_symbol(&self) -> Option<&str>
The symbol in operator position — Some(s) iff this is a non-empty
list whose first element is a symbol ((defpoint …) → Some("defpoint")).
None for every other shape: a non-list (foo, 5, :kw), the
empty list (), and a list whose head is not a symbol ((5 …),
(:kw …), ((nested) …)).
This is the operator-position projection — the structural query
every form-dispatch site in the substrate keys on: “what operator
does this form invoke?” Macroexpansion (Expander::expand looks up
the head against the macro table; macro_def_from reads it to
recognize a defmacro head) and the typed compilers
(compile_typed / compile_named_from_forms match it against
T::KEYWORD) all asked the same self.as_list()?.first()?.as_symbol()
question inline. Naming it once makes “operator position” a primitive
of the Sexp algebra rather than four byte-identical inline chains.
This is the SOFT face of operator-position dispatch — it answers
“is this form an invocation of some operator?” and yields None
(skip / fall through) for everything that isn’t, with no diagnostic.
Its STRICT sibling is TataraDomain::compile_from_sexp, which on a
matched-arity form distinguishes the empty-list and
present-but-not-a-symbol head sub-modes to emit a rich
MissingHeadSymbol rejection. The two are the dispatch (head_symbol)
and the gate (compile_from_sexp) faces of the same projection;
keeping both lets a site choose “skip silently” or “reject loudly”
without re-deriving the head.
head_symbol is the operator projection of Sexp::as_call: it
keeps the head and discards the argument tail. The
as_list()?.first()?.as_symbol() chain lives in ONE place
(as_call); this is its first component.
Sourcepub fn as_call(&self) -> Option<(&str, &[Sexp])>
pub fn as_call(&self) -> Option<(&str, &[Sexp])>
Decompose a call form into its operator and argument tail —
Some((op, args)) iff this is a non-empty list whose first element
is a symbol, where op is that head symbol and args is the
remaining elements (&self[1..], possibly empty). None for every
shape head_symbol rejects: a non-list, the empty list, and a list
whose head is present but not a symbol.
This is the call-form decomposition — the structural shape of a
Lisp invocation: an operator applied to an argument tail. It pairs
the operator-position projection (head_symbol) with the argument
tail every dispatch site reads immediately after matching the
operator. Macroexpansion (Expander::expand) applies the matched
macro to &list[1..]; the typed compilers (compile_typed,
compile_named_from_forms) feed &list[1..] into
T::compile_from_args. Before this query each site bound
as_list() for the tail AND independently called head_symbol()
(which itself re-derives as_list().first()) for the operator —
two traversals of the same list, two projections. as_call yields
both from one match, so the operator and its arguments can never
drift out of agreement at a dispatch site.
Soft face, like head_symbol: it answers “is this an invocation of
some operator, and what are its arguments?” and yields None (skip
/ fall through) for everything that isn’t, with no diagnostic. The
strict gate sibling is TataraDomain::compile_from_sexp, which
distinguishes the empty-list and non-symbol-head sub-modes to reject
loudly.
Sourcepub fn call<H, I>(head: H, args: I) -> Sexp
pub fn call<H, I>(head: H, args: I) -> Sexp
Canonical call-form outer constructor — composes the atomic-
payload construct family’s Self::symbol (the head-position
construct on the 6-of-12 atomic carving of SexpShape) with
the residual-axis construct family’s Self::list (via
std::iter::once(head_sexp).chain(args)) to build a symbol-
headed list-shaped Sexp value at ONE site on the closed-set
Sexp algebra. The call-form section-for-retraction sibling
of the existing Self::as_call soft-projection ([Option<(& str, &[Sexp])>]): where the projection soft-decomposes a
symbol-headed list into its head symbol and argument tail, this
constructor embeds a fresh (head string, item sequence) pair
into the matching call-shaped wrapper.
Composition sibling of the atomic-payload construct family
(Self::symbol, Self::keyword, Self::string,
Self::int, Self::float, Self::boolean — routing
through the typed Atom family on the 6-of-12 atomic carving),
the quote-family construct family (Self::quote,
Self::quasiquote, Self::unquote, Self::unquote_splice
— routing through the typed QuoteForm::wrap family on the
4-of-12 quote-family carving), and the residual-axis construct
Self::list (routing owned or iterable item sequences into
the tuple-variant on the 2-of-12 residual carving): those close
the (construct, project) algebra dual on their respective
STRUCTURAL carvings; this closes the (construct, project)
algebra dual on the SYMBOL-HEADED-LIST TYPED DECOMPOSITION — the
load-bearing shape every Lisp invocation, every (defX …)
typed-domain call form, and every macroexpander template head
takes on the outer Sexp algebra.
Composition law (forward, through the outer algebra’s atomic +
residual construct families): Sexp::call(head, args) == Sexp::list(std::iter::once(Sexp::symbol(head)).chain(args)) for
every head: impl Into<String> + args: impl IntoIterator<Item = Sexp>. The body binds through the SAME two construct methods
consumers already reach for when threading a head-then-rest
sequence into a call form — the composition law lifts that
two-method inline pattern to ONE named query on the outer
Sexp algebra.
Round-trip law (section-for-retraction with the soft-projection
sibling): for every head: &str + args: Vec<Sexp>,
Sexp::call(head, args.clone()).as_call() == Some((head, args.as_slice())) — the outer algebra’s call-form typed
constructor pairs section-for-retraction with the outer
algebra’s soft call-form projection, and the (head symbol,
args slice) cross-projection preserves identity. Keyword-
matched round-trip law: for every head: &str + args: Vec<Sexp>, Sexp::call(head, args.clone()).as_call_to(head) == Some(args.as_slice()) — the keyword-typed projection recovers
the args tail iff its argument keyword matches the constructor’s
head. Head-symbol composition law: Sexp::call(head, args).head_symbol() == Some(head.as_str()) for every head: impl Into<String> + args: impl IntoIterator<Item = Sexp> —
the head-position projection recovers the constructor’s head
byte-for-byte.
Outer-shape composition law: Sexp::call(head, args).shape() == SexpShape::List for every input — a call form is a list-shaped
Sexp, and the outer-shape identity binds through the typed-
shape lattice at the residual arm. Structural-carving-marker
composition law: Sexp::call(head, args).as_structural_kind() == Some(StructuralKind::List) — the residual-axis carving
marker binds through the closed-set StructuralKind algebra
at ONE arm, symmetric with the atomic-axis’s Sexp::X_atom( payload).as_atom_kind() == Some(AtomKind::X) marker
composition.
Pre-lift the Sexp::List(std::iter::once(Sexp::symbol(head)) .chain(args).collect()) composition (or equivalently the
Sexp::List(vec![Sexp::symbol(head), args...]) welded triple)
appeared inline at every consumer that builds a call-shaped
Sexp value — well past the ≥2 PRIME-DIRECTIVE trigger once
the call-form shape is named. Post-lift consumers that have a
head string + an owned or iterable sequence of args bind to ONE
typed-algebra method on the outer Sexp algebra with the
impl Into<String> bound on the head absorbing &str /
String / &String and the impl IntoIterator<Item = Sexp>
bound on the args absorbing Vec<Sexp> / [Sexp; N] /
.map(...) chains without a per-site .collect::<Vec<Sexp>>()
coercion.
Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
(head string, args sequence, Self::List tuple-variant
constructor) triple binds at ONE typed-algebra method on the
outer Sexp algebra, closing the call-form (construct,
project) algebra dual pair with Self::as_call /
Self::as_call_to / Self::head_symbol. THEORY.md §II.1
invariant 2 — free middle; every consumer that has a head
string + an owned or iterable sequence of args and wants to
build a call-shaped Sexp routes through the SAME typed
method, so a regression that drifts one consumer’s construction
from the others (e.g. a copy-edit that emits Sexp::keyword( head) for the head position, or that swaps in a Sexp::string
head that Self::as_call then rejects at the projection
site) cannot reach the substrate’s runtime. THEORY.md §V.1 —
knowable platform; the call-form typed-construct becomes a TYPE
projection on the substrate’s outer Sexp algebra sitting
next to the typed-project family Self::as_call /
Self::as_call_to rather than bare tuple-variant constructor
paired with per-site Sexp::List(vec![Sexp::symbol(...), ...])
discipline. THEORY.md §VI.1 — generation over composition; the
call-form pair emerges from ONE typed-algebra composition
through Self::list composed with Self::symbol rather
than from per-consumer per-callsite literals; a future call-
form shape extension (e.g. a keyword-headed call form for a
Kernel-style applicative-vs-operative split) lands as ONE peer
constructor on this algebra alongside the residual, quote-
family, and atomic-payload construct families.
Sourcepub fn named_call<H, N, I>(head: H, name: N, spec_args: I) -> Sexp
pub fn named_call<H, N, I>(head: H, name: N, spec_args: I) -> Sexp
Canonical named-call-form outer constructor — composes the call-
form typed constructor Self::call with the atomic-payload
construct family’s Self::symbol (for the NAME slot) via
std::iter::once(Self::symbol(name)).chain(spec_args) to build a
(head NAME spec_args…) symbol-headed named list-shaped Sexp
value at ONE site on the closed-set Sexp algebra. The named-
call-form section-for-retraction sibling of the existing
Self::as_named_call_to soft-projection ([Option<crate::error:: Result<(&str, &[Sexp])>>]): where the projection soft-decomposes
a (<keyword> NAME spec_args…) symbol-headed list into its NAME
symbol and spec args tail through the named-form gate
(crate::compile::split_name_slot), this constructor embeds a
fresh (head string, name string, spec_args sequence) triple
into the matching named-call-shaped wrapper. Composition sibling
of the call-form construct Self::call on the outer algebra:
where Self::call closes the (construct, project) dual on the
CALL-FORM TYPED DECOMPOSITION ((head args…)), this closes the
dual on the NAMED-CALL-FORM TYPED DECOMPOSITION ((head NAME spec_args…)) — the load-bearing shape every (defX NAME …)
typed-domain named authoring form takes on the outer Sexp
algebra, and the section-for-retraction dual of the
crate::compile::split_name_slot gate at the value level.
Composition law (forward, through the call-form + atomic-payload
construct families): Sexp::named_call(head, name, spec_args) == Sexp::call(head, std::iter::once(Sexp::symbol(name)).chain( spec_args)) for every head: impl Into<String> + name: impl Into<String> + spec_args: impl IntoIterator<Item = Sexp>. The
body binds through the SAME two construct methods consumers
already reach for when threading a head-then-name-then-rest
sequence into a named call form — the composition law lifts that
two-method inline pattern to ONE named query on the outer
Sexp algebra.
Round-trip law (section-for-retraction with the named-form soft-
projection): for every head: &'static str + name: &str +
spec_args: Vec<Sexp>, Sexp::named_call(head, name, spec_args .clone()).as_named_call_to(head) == Some(Ok((name, spec_args .as_slice()))) — the outer algebra’s named-call-form typed
constructor pairs section-for-retraction with the outer
algebra’s soft named-call-form projection, and the (head symbol,
NAME symbol, spec args slice) cross-projection preserves
identity. Call-form projection composition: Sexp::named_call( head, name, spec_args).as_call() == Some((head, once(Sexp::symbol(name)).chain(spec_args).collect().as_slice() )) — the call-form soft-projection recovers (head, [name, spec_args…]) with the NAME symbol as the first arg, mirroring
the Self::call round-trip on the encompassing call algebra.
Keyword-matched round-trip law: for every head: &'static str +
name: &str + spec_args: Vec<Sexp>, Sexp::named_call(head, name, spec_args.clone()).as_call_to(head) == Some( [Sexp::symbol(name), spec_args…].as_slice()) — the keyword-
typed projection recovers the NAME-headed args tail iff its
argument keyword matches the constructor’s head. Head-symbol
composition law: Sexp::named_call(head, name, spec_args) .head_symbol() == Some(head.as_str()) — the head-position
projection recovers the constructor’s head byte-for-byte.
Outer-shape composition law: Sexp::named_call(head, name, spec_args).shape() == SexpShape::List for every input — a
named call form is a list-shaped Sexp, the outer-shape
identity binds through the typed-shape lattice at the residual
arm. Structural-carving-marker composition law: Sexp:: named_call(head, name, spec_args).as_structural_kind() == Some(StructuralKind::List) — the residual-axis carving marker
binds through the closed-set StructuralKind algebra at ONE
arm, symmetric with Self::call’s residual-arm marker
composition.
Named-form gate composition law: crate::compile::split_name_slot( &Sexp::named_call(head, name, spec_args).as_call_to(head) .unwrap(), head) == Ok((name, spec_args.as_slice())) — the
substrate’s named-form arity + NAME-shape gate accepts every
output of this constructor byte-for-byte, closing the section-
for-retraction pair at the gate level as well as at the
projection level. A constructor emission that drifts into a
missing-NAME shape (empty spec_args yields (head), which the
call-form projection recovers but the named-form gate rejects
with NamedFormMissingName) or a non-symbol-NAME shape
(Sexp::keyword(name) for the NAME position, which the gate
rejects with NamedFormNonSymbolName) becomes structurally
impossible — the impl Into<String> NAME bound admits string
payloads only, and the Self::symbol wrap routes to the
symbol atom variant as_symbol_or_string accepts.
Pre-lift the Sexp::call(head, std::iter::once(Sexp::symbol( name)).chain(spec_args)) composition (or equivalently the
Sexp::List(vec![Sexp::symbol(head), Sexp::symbol(name), spec_args...]) welded quadruple) appeared inline at every
consumer that builds a (defX NAME …)-shaped Sexp value
— well past the ≥2 PRIME-DIRECTIVE trigger once the named
call-form shape is named. Post-lift consumers that have a head
string + a NAME string + an owned or iterable sequence of spec
args bind to ONE typed-algebra method on the outer Sexp
algebra with the two impl Into<String> bounds absorbing &str
/ String / &String on both string positions and the
impl IntoIterator<Item = Sexp> bound on the spec args
absorbing Vec<Sexp> / [Sexp; N] / .map(...) chains without
a per-site .collect::<Vec<Sexp>>() coercion.
Frontier inspiration: Racket’s syntax-parse
(~datum keyword) name:id spec ... pattern binds the NAME slot
through the name:id capture binder and consumers reference it
downstream; the constructor peer on the same surface is
syntax-e composed with datum->syntax wrapping a
(list #'keyword name-id spec-list ...) triple. Sexp:: named_call is the unstructured-Rust peer — a section-for-
retraction constructor on the outer algebra that mirrors the
~datum keyword name:id spec ... pattern’s NAME capture on the
construct side. Tree-sitter’s query-matched named captures
have the same shape on the tree side: the query pattern
binds a NAME capture, the constructor peer (ts_node_new
composed with ts_node_field_set) embeds a fresh NAME child at
the corresponding field slot. The typed structural rejection
chain the substrate’s named-form gate emits
(crate::error::LispError::NamedFormMissingName,
crate::error::LispError::NamedFormNonSymbolName) is
preserved by construction — the constructor cannot emit a
value the gate rejects, symmetric with the ~datum /
name:id reader-side rejection that fires BEFORE any
downstream binding sees the drifted shape.
Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
(head string, NAME string, spec args sequence, Self::call
call-form constructor) quadruple binds at ONE typed-algebra
method on the outer Sexp algebra, closing the named-call-
form (construct, project) algebra dual pair with
Self::as_named_call_to / Self::as_named_call_to_any on
the projection side and crate::compile::split_name_slot on
the gate side. THEORY.md §II.1 invariant 2 — free middle;
every consumer that has a head + NAME + spec args and wants to
build a named-call-shaped Sexp routes through the SAME
typed method, so a regression that drifts one consumer’s
construction from the others (e.g. a copy-edit that emits
Sexp::keyword(name) for the NAME position, which the
named-form gate would reject with NamedFormNonSymbolName)
cannot reach the substrate’s runtime. THEORY.md §V.1 —
knowable platform; the named-call-form typed-construct becomes
a TYPE projection on the substrate’s outer Sexp algebra
sitting next to the typed-project family [Self:: as_named_call_to] / Self::as_named_call_to_any +
crate::ast::iter_named_calls_to /
crate::ast::iter_named_calls_to_any rather than a per-site
inline composition. THEORY.md §VI.1 — generation over
composition; the named-call-form pair emerges from ONE typed-
algebra composition through Self::call composed with
Self::symbol rather than from per-consumer per-callsite
literals; a future named-form shape extension (e.g. a
dotted-NAME form, or a typed-NAME form where the NAME slot
carries a compile-time-decoded typed witness) lands as ONE
peer constructor on this algebra alongside the call-form,
residual, quote-family, and atomic-payload construct
families.
Sourcepub fn as_call_to(&self, keyword: &str) -> Option<&[Sexp]>
pub fn as_call_to(&self, keyword: &str) -> Option<&[Sexp]>
Decompose a call form into its argument tail IFF the head matches the
supplied keyword — Some(args) iff this is a non-empty list whose
first element is a symbol equal to keyword, where args is the
remaining elements (&self[1..], possibly empty). None for every
shape as_call rejects AND for every call whose head is present but
differs from keyword.
This is the keyword-typed call decomposition — the natural
extension of Sexp::as_call for the “is this a call to ONE
specific operator?” question every typed-domain dispatch site asks
after macroexpansion. compile_typed
and compile_named_from_forms
both opened the same two-step chain inline —
if let Some((head, args)) = form.as_call() { if head == T::KEYWORD { … } }
— at every form they walked; the chain IS this projection. Naming
it lifts “is this form a call to T?” from a two-step inline pattern
to ONE structural query on the Sexp algebra. A regression that
drifts one consumer’s comparison from == to != , or that
compares against a different label than T::KEYWORD (e.g.
substring-grepping the rendered head), becomes structurally
impossible: there is exactly one implementation both dispatchers
route through.
Soft face, like the rest of the as_* family: it answers “is this
a call to keyword, and what are its arguments?” and yields None
for everything that isn’t (skip / fall through), with no
diagnostic. The strict gate sibling is
TataraDomain::compile_from_sexp, which distinguishes the
not-a-list / empty-list / non-symbol-head / wrong-keyword
sub-modes to reject loudly. The two are the dispatch
(as_call_to) and the gate (compile_from_sexp) faces of the
same projection; keeping both lets a site choose “skip silently”
or “reject loudly” without re-deriving the head.
Structural identity binding it to its siblings:
as_call_to(keyword) == as_call().and_then(|(h, args)| (h == keyword).then_some(args))as_call_to(keyword).is_some() == (head_symbol() == Some(keyword))
The returned &[Sexp] borrows from the list’s tail verbatim — no
copy, no allocation, same lifetime as Sexp::as_call’s tail.
Slice-side sibling: iter_calls_to lifts this per-form projection
onto a &[Sexp], yielding the args slices of every matching form in
source order — the substrate’s typed-keyword filter over a batch of
forms, structurally bound to this per-form projection via the
closed-form composition
iter_calls_to(forms, k) == forms.iter().filter_map(|f| f.as_call_to(k)).
Sourcepub fn as_call_to_any<F, T>(&self, decode: F) -> Option<(T, &[Sexp])>
pub fn as_call_to_any<F, T>(&self, decode: F) -> Option<(T, &[Sexp])>
Decompose a call form whose head decodes through a caller-supplied
classifier — Some((decoded, args)) iff this is a non-empty list
whose first element is a symbol AND decode(head) returns
Some(decoded), where args is the remaining elements
(&self[1..], possibly empty). None for every shape
Sexp::as_call rejects AND for every call whose head is present
but decode rejects.
This is the typed-decoded call decomposition — the closure-typed
extension of Sexp::as_call_to for the “is this a call whose head
belongs to a CLOSED SET (or a LIVE REGISTRY) that decodes to a typed
witness?” question. Where Sexp::as_call_to filters by ONE
constant keyword, as_call_to_any filters AND TYPES by a caller-
supplied projection — every dispatch site that asks “is this form
an invocation of any of N operators, decoded as a typed enum or
resolved against a runtime table?” binds to ONE structural query
on the Sexp algebra. Two consumers route through it:
- The macro-expander’s
macro_def_from— closed-set classifier:as_call_to_any(MacroDefHead::from_keyword)decides which of{defmacro, defpoint-template, defcheck}a top-level form invokes, decoded to the typedMacroDefHeadenum. Pre-lift the site opened the same three-step chain inline —let Some(list) = form.as_list()…; let Some(head) = form.head_symbol()…; let Some(decoded) = MacroDefHead::from_keyword(head)…. - The macro-expander’s
Expander::expand— live-registry classifier:as_call_to_any(|h| self.macros.get(h))decides which of the registered macros (aHashMap<String, MacroDef>populated byexpand_program’sdefmacrorecognition) a form invokes, decoded to&MacroDef. Pre-lift the site opened the sameas_list() + as_call() + self.macros.get(head)chain inline —as_list()for the children-walk fallthrough,as_call()for the (head, args) pair (which itself re-derivesas_list()internally), andself.macros.get(head)for the registry lookup.
Naming the projection lifts “is this form a call to any of N operators, decoded to T?” from the three-step inline pattern to ONE structural query — closed-set enum classifier OR live-registry HashMap classifier, the family primitive is uniform under both.
Soft face, like the rest of the as_* family: it answers “is this
a call whose head decodes through F, and what are its arguments?”
and yields None for everything that isn’t (skip / fall through),
with no diagnostic. The strict gate sibling stays
TataraDomain::compile_from_sexp — that distinguishes the
not-a-list / empty-list / non-symbol-head / wrong-keyword sub-modes
to reject loudly for a single-keyword consumer. The two are the
closed-set-decoded dispatch (as_call_to_any) and the
single-keyword gate (compile_from_sexp) faces of the typed-domain
recognition problem; keeping both lets a site choose “skip
silently if the head isn’t ours” or “reject loudly if the head
isn’t the exact keyword” without re-deriving the head.
Structural identity binding it to its siblings:
as_call_to_any(decode) == as_call().and_then(|(h, args)| decode(h).map(|d| (d, args)))as_call_to(k) == as_call_to_any(|h| (h == k).then_some(())).map(|(_, a)| a)(modulo the discarded())as_call_to_any(decode).is_some() == as_call().map_or(false, |(h, _)| decode(h).is_some())
The returned &[Sexp] borrows from the list’s tail verbatim — no
copy, no allocation, same lifetime as Sexp::as_call’s tail.
T is owned because decode is FnOnce(&str) -> Option<T> and a
&'_ str borrow into the head symbol would not outlive the helper
boundary; consumers projecting to a typed Copy enum (e.g.
MacroDefHead) get the value directly, consumers projecting to a
borrowed &'static str (a closed-set head) project to
&'static str and inherit the static lifetime through the
classifier.
Slice-side sibling: iter_calls_to_any lifts this per-form
projection onto a &[Sexp], yielding the (decoded, &[Sexp])
pair of every matching form in source order — the substrate’s
typed-decoded filter over a batch of forms, structurally bound
to this per-form projection via the closed-form composition
iter_calls_to_any(forms, decode) == forms.iter().filter_map(|f| f.as_call_to_any(&mut decode)). The slice-side primitive
promotes the closure constraint from FnOnce (per-form, one
call per invocation) to FnMut (slice-side, one call per
element) so a decoder that captures mutable state (a counter, a
registry cache) maintains state across the batch walk.
Sourcepub fn as_named_call_to_any<F, T>(
&self,
decode: F,
) -> Option<Result<(T, &str, &[Sexp]), LispError>>
pub fn as_named_call_to_any<F, T>( &self, decode: F, ) -> Option<Result<(T, &str, &[Sexp]), LispError>>
Decompose a named call form (a (<keyword> NAME :k v …) shape) whose
head decodes through a caller-supplied classifier — Some(Ok((decoded, name, spec_args))) iff this is a non-empty list whose first element
is a symbol AND decode(head) returns Some((decoded, kw)) AND the
remaining elements split cleanly into a NAME slot (symbol or string
at position 1) and a spec args tail (position 2..), Some(Err(…)) iff
the head decodes but the NAME slot is missing
([LispError::NamedFormMissingName]) or non-symbol-or-string
([LispError::NamedFormNonSymbolName]), None for every shape
Sexp::as_call_to_any rejects AND for every call whose head is
present but decode returns None for.
This is the per-form named-classifier projection — the per-form
peer of iter_named_calls_to_any on the slice algebra and of
crate::macro_expand::Expander::expand_and_collect_named_calls_to_any
on the expander surface. Closes the (per-form × classifier × named)
corner of the soft-dispatch cube the substrate’s per-form algebra
(as_call_to{,_any}) and slice algebra (iter_calls_to{,_any},
iter_named_calls_to{,_any}) collectively shape — pre-lift the cube
at the per-form × named corner was “(composed inline at each named
consumer)” (the documented gap the cube table inside
iter_named_calls_to_any called out), post-lift the per-form ×
named row binds to ONE primitive every per-form named consumer
composes through:
| bare-kwargs | named NAME-then-kwargs | |
|---|---|---|
| per-form | Sexp::as_call_to_any | as_named_call_to_any (this) |
| slice | iter_calls_to_any | iter_named_calls_to_any |
| expander | expand_and_collect_calls_to_any | expand_and_collect_named_calls_to_any |
The slice-side iter_named_calls_to_any now routes through THIS
per-form primitive via the SAME forms.iter().filter_map(_)
skeleton iter_calls_to_any uses to route through
Sexp::as_call_to_any, so a regression that drifts ONE row’s
instrumentation, span-aware borrow walker, or fused-iterator
invariant from the bare row to the named row (or vice versa) is
structurally impossible.
Composes Sexp::as_call_to_any with
crate::compile::split_name_slot: the classifier filter precedes
the named gate, mirroring how split_name_slot is composed AFTER
the classifier-decoded args tail is already in hand inside
iter_named_calls_to_any. Decoder signature FnOnce(&str) -> Option<(T, &'static str)> pairs the typed witness T with the
canonical static keyword threaded through the
NamedFormMissingName.keyword / NamedFormNonSymbolName.keyword
slots of the named-form gate — the &'static constraint pins the
same compile-time discipline crate::compile::split_name_slot’s
keyword: &'static str parameter pins at the slice-side boundary,
AND that the slice-side decoder signature pins on the slice
algebra.
Three-arm result shape — Option<Result<…>> — preserves both the
classifier filter face (None for “not our head, skip silently”,
matching the per-form soft-projection posture of every other as_*
method on Sexp) AND the named gate face (Err for “matched head
but malformed NAME”, surfacing the typed structural-rejection
variants LispError::NamedFormMissingName /
LispError::NamedFormNonSymbolName the slice-side and expander-
surface consumers already short-circuit on). A consumer that wants
“fold over every per-form result, short-circuiting on the first
malformed NAME” composes .transpose() (yielding
Result<Option<…>>) and ?-routes the outer Result; a consumer
that wants “skip every non-matching form AND every malformed
matched form” composes .and_then(|res| res.ok()) (yielding
Option<(T, &str, &[Sexp])>); a consumer that wants the raw
three-arm shape pattern-matches directly.
Two plausible future consumer shapes the per-form named-classifier projection admits with no boilerplate:
- LSP hover tooltip — an authoring tool that surfaces a
tooltip on the symbol under the cursor wants to ask “is THIS
form (the one I just resolved to under the cursor) a named
call to any registered domain, decoded to a typed kind, with
the borrowed NAME slot extracted for the tooltip body?”. Pre-
lift the tool would re-derive
form.as_call_to_any(decode) .and_then(|((kind, kw), args)| split_name_slot(args, kw).ok().map(|(name, rest)| (kind, name, rest)))inline; post-lift the tool binds to ONE primitive. - REPL single-form dispatcher — a `:dispatch
Structural identity binding it to its siblings:
as_named_call_to_any(decode) == as_call_to_any(decode).map(|((d, kw), args)| split_name_slot(args, kw).map(|(name, rest)| (d, name, rest)))as_named_call_to(k) == as_named_call_to_any(|h| (h == k).then_some(((), k))).map(|res| res.map(|(_, name, rest)| (name, rest)))as_named_call_to_any(decode).is_none() == as_call_to_any(decode).is_none()(the classifier filter face is identical to the bare-kwargs sibling’s)
The returned &str NAME slot and &[Sexp] spec args tail borrow
from &self verbatim — no copy, no allocation, same lifetime as
Sexp::as_call_to_any’s tail AND crate::compile::split_name_slot’s
pair. T is owned because the underlying Sexp::as_call_to_any
classifier is FnOnce(&str) -> Option<(T, &'static str)> and T
must outlive the helper boundary; consumers projecting to a typed
Copy enum (e.g. a closed-set Kind) get the value directly,
consumers projecting to a borrowed &'static str (a closed-set
head sourced from ClosedSet::ALL.label()) project to &'static str and inherit the static lifetime through the classifier.
Soft face on the classifier filter, strict face on the named gate:
“is this a named call whose head decodes through F, and what
are its NAME and spec args?” yielding None for “not our head”
(skip / fall through, no diagnostic) AND Some(Err(…)) for “our
head but malformed NAME” (reject loudly, structural variant). The
soft-classifier-then-strict-named composition matches the
slice-side iter_named_calls_to_any yielded Result shape (with
non-matching forms skipped by the iterator filter) and the
expander-surface expand_and_collect_named_calls_to_any collect
shape (with Result::collect short-circuiting on the first
malformed NAME) — every layer of the cube preserves both faces.
Theory anchor: THEORY.md §VI.1 — generation over composition; the
per-form × classifier × named cell of the soft-dispatch cube is a
CONSEQUENCE of Sexp::as_call_to_any + crate::compile::split_name_slot,
named on the substrate’s Sexp algebra rather than re-derived
inline at every per-form named consumer site. THEORY.md §V.1 —
knowable platform; the per-form named-classifier projection
becomes a NAMED primitive on the Sexp algebra, discoverable by
any future authoring tool (LSP, REPL, tatara-check) that holds
a single form in isolation. THEORY.md §II.1 invariant 2 — free
middle; the slice-side sibling iter_named_calls_to_any now
routes through this per-form primitive via the same
forms.iter().filter_map(_) skeleton the bare-kwargs row uses
to route through Sexp::as_call_to_any, so the bare and named
rows share ONE filter-and-fuse implementation skeleton on the
Sexp/&[Sexp] algebras.
Frontier inspiration: MLIR’s mlir::dyn_cast<NamedOpInterface>(op)
— the typed downcast from a polymorphic IR node onto a NAMED-op
interface that exposes both the typed witness AND the
symbol-name accessor is the MLIR idiom; as_named_call_to_any is
the unstructured-Rust peer on the substrate’s Sexp algebra,
with Option<Result<(T, &str, &[Sexp])>> standing in for MLIR’s
typed-downcast-then-name-accessor pair, and the Result face
carrying the typed structural rejection MLIR encodes via verifier
diagnostics. Racket’s syntax-parse ~or* ((~datum defX) name:id arg ...) ((~datum defY) name:id arg ...) on a single syntax
object — typed named-form decomposition with name:id capture
binding is the Racket idiom; this method is the per-form
Rust-typed peer with the typed structural rejection
(NamedFormMissingName / NamedFormNonSymbolName) preserved
across the boundary.
Sourcepub fn as_named_call_to(
&self,
keyword: &'static str,
) -> Option<Result<(&str, &[Sexp]), LispError>>
pub fn as_named_call_to( &self, keyword: &'static str, ) -> Option<Result<(&str, &[Sexp]), LispError>>
Decompose a named call form whose head matches a constant
keyword — Some(Ok((name, spec_args))) iff this is a non-empty
list whose first element is the symbol keyword AND the remaining
elements split cleanly into a NAME slot and a spec args tail,
Some(Err(…)) iff the head matches but the NAME slot is missing
or non-symbol-or-string, None for every shape
Sexp::as_call_to rejects.
Constant-keyword sibling of Sexp::as_named_call_to_any and
per-form sibling of iter_named_calls_to on the slice algebra.
Routes through the typed-decoded sibling with a constant-classifier
decoder (|h| (h == keyword).then_some(((), keyword))) — the same
constant-classifier composition Sexp::as_call_to uses to route
through Sexp::as_call_to_any on the bare-kwargs axis, and that
iter_named_calls_to uses to route through
iter_named_calls_to_any on the slice algebra. The discarded
() typed witness (then_some(((), keyword))) is consumed by the
wrapper projection so the consumer’s per-form mapper sees only the
(name, spec_args) borrowed pair, matching the bare projection
signature on the named axis.
keyword: &'static str threads verbatim through the
NamedFormMissingName.keyword / NamedFormNonSymbolName.keyword
slots of the named-form gate — same &'static discipline
crate::compile::split_name_slot pins at its boundary, AND that
iter_named_calls_to pins on the slice algebra. Consumers that
want a runtime keyword whose lifetime is shorter use
Sexp::as_named_call_to_any directly with a constant-classifier
decoder that converts post-resolution.
Structural identity binding it to its siblings:
as_named_call_to(k) == as_named_call_to_any(|h| (h == k).then_some(((), k))).map(|res| res.map(|(_, name, rest)| (name, rest)))as_named_call_to(k).is_none() == as_call_to(k).is_none()iter_named_calls_to(forms, k) == forms.iter().filter_map(|f| f.as_named_call_to(k))
Theory anchor: see Sexp::as_named_call_to_any — the constant-
keyword sibling shares the same lift posture, threading the
&'static str keyword constraint through the named-form gate’s
canonical-keyword slot rather than admitting an arbitrary runtime
keyword.
Sourcepub fn as_unquote(&self) -> Option<(UnquoteForm, &Sexp)>
pub fn as_unquote(&self) -> Option<(UnquoteForm, &Sexp)>
Decompose an unquote-family form into its typed marker and inner
expression — Some((UnquoteForm::Unquote, inner)) iff this is ,x
(a Sexp::Unquote wrapper), Some((UnquoteForm::Splice, inner))
iff this is ,@x (a Sexp::UnquoteSplice wrapper), None for
every other shape (Quote, Quasiquote, Nil, Atom, List).
This is the unquote-family projection — the typed-marker peer of
Sexp::as_call for the macro-template substitution surface. Where
Sexp::as_call decomposes (op args …) into a (head, args)
pair, as_unquote decomposes ,x / ,@x into a (form, inner)
pair where form: UnquoteForm is the closed-set typed marker
(Unquote for ,, Splice for ,@) and inner: &Sexp is the
borrowed body. The pairing of Sexp::Unquote ↔ UnquoteForm::Unquote
and Sexp::UnquoteSplice ↔ UnquoteForm::Splice is the structural
invariant the macro-expander’s substitution path keys every
rejection on — naming the projection lifts the pair from
per-callsite discipline (two Sexp::Unquote(inner) arms paired
with two UnquoteForm::Unquote literals at distinct sites, two
Sexp::UnquoteSplice(inner) arms paired with two
UnquoteForm::Splice literals at distinct sites) into ONE typed
projection both expansion strategies route through.
Three consumers in macro_expand route
through this primitive:
compile_node(bytecode-template compile path) —,xbecomesTemplateOp::Subst(idx),,@xbecomesTemplateOp::Splice(idx); both arms share the gate-1+gate-2 compositionresolve_unquote_in_params(inner, params, form)?keyed on the typedformprojection.substitutetop-level (substitute fallback path) —,xresolves to its bound value,,@xrejects withLispError::SpliceOutsideList(a splice form with no containing list to flatten into).substitutelist-inner (substitute fallback path’s per-item walk) —,@xitems splice their bound list/nil/scalar value into the assembled list builder via [crate::macro_expand::splice_value_into]; non-splice items recurse intosubstitute.
Pre-lift each site opened the same per-variant match arms —
Sexp::Unquote(inner) => … UnquoteForm::Unquote … and
Sexp::UnquoteSplice(inner) => … UnquoteForm::Splice … —
independently. The (Sexp variant, UnquoteForm variant) pairing was
load-bearing across distinct sites yet only enforced by callsite
discipline. Post-lift the pair binds at ONE projection function the
type system threads through (UnquoteForm, &Sexp): a regression
that drifts ONE site’s pairing (e.g. a future emitter that matches
Sexp::Unquote(_) but threads UnquoteForm::Splice into
unquote_target_symbol — type-checks but renders a misleading
diagnostic) becomes structurally impossible.
Soft face, like the rest of the as_* family on Sexp: it answers
“is this form an unquote-family marker, and what does it wrap?” and
yields None for everything that isn’t (skip / fall through), with
no diagnostic. The strict siblings —
[crate::macro_expand::splice_value_into] for the bound-list
coercion, non_symbol_unquote_target /
splice_outside_list for the per-failure-mode rejections — keep
their loud-reject posture; this projection is the dispatch face the
soft pre-rejection walk binds to.
Structural identity binding it to the unquote-family variants:
as_unquote() == Some((UnquoteForm::Unquote, inner))iffself == Sexp::Unquote(inner)as_unquote() == Some((UnquoteForm::Splice, inner))iffself == Sexp::UnquoteSplice(inner)as_unquote().is_some() == matches!(self, Sexp::Unquote(_) | Sexp::UnquoteSplice(_))
The returned &Sexp borrows the inner box’s body verbatim — no
clone, no allocation — same lifetime as &self. The closed-set
guarantee on UnquoteForm (exactly Unquote ⊎ Splice) is
threaded through this projection’s return tuple, so consumers that
pattern-match on form: UnquoteForm get rustc-enforced
exhaustiveness — a future Sexp variant must extend UnquoteForm
AND this match arm together (or stay outside the unquote family
and project to None), eliminating the silent two-site
extension-drift this lift was already designed to forbid.
Theory anchor: THEORY.md §VI.1 — generation over composition; the
(Sexp::Unquote, UnquoteForm::Unquote) and
(Sexp::UnquoteSplice, UnquoteForm::Splice) pairings appear ≥3
times across compile_node (2 arms) + substitute (top-level +
list-inner) — past the PRIME-DIRECTIVE trigger once the structural
shape is named. THEORY.md §V.1 — knowable platform; the
unquote-family projection becomes a NAMED primitive on the
substrate’s Sexp algebra rather than per-site Sexp::Unquote(_) | Sexp::UnquoteSplice(_) inline matches paired with per-site
UnquoteForm::Unquote / UnquoteForm::Splice literals.
THEORY.md §II.1 invariant 1 — typed entry; the macro-template
substitution surface’s typed-marker projection IS the rust-level
typed-entry gate’s structural component, lifted from per-site
duplication onto ONE rust method the substrate’s diagnostic
promotions hang off of. THEORY.md §II.1 invariant 2 — free middle;
both expansion strategies (bytecode compile_node and substitute
fallback substitute) route through the SAME projection, so a
regression that drifts ONE strategy’s (Sexp variant, UnquoteForm
variant) pairing from the other cannot reach the substrate’s
runtime — the type system binds both strategies to the
projection’s single emission shape.
Frontier inspiration: Racket’s syntax-parse ~or* (~unquote stx) (~unquote-splice stx) pattern — every macro-template pattern over
,id / ,@id binds to ONE typed decomposition that surfaces the
marker identity alongside the inner expression; the substrate’s
as_unquote is the Rust-typed peer of that pattern, lifted onto
the Sexp algebra with UnquoteForm standing in for Racket’s
pattern-class identity. MLIR’s typed-IR projection
mlir::dyn_cast<UnquoteFamilyOp>(op) — the typed downcast from a
polymorphic IR node onto a closed-set op family is the MLIR idiom;
as_unquote is the unstructured-projection peer on the substrate’s
Sexp algebra, with Option<(UnquoteForm, &Sexp)> standing in for
MLIR’s typed downcast result.
Sourcepub fn as_unquote_form(&self) -> Option<UnquoteForm>
pub fn as_unquote_form(&self) -> Option<UnquoteForm>
Soft projection onto the closed-set UnquoteForm template-
substitution carving marker — the 2-of-12 carving of the
SexpShape algebra covering the two
homoiconic template-substitution wrappers (Self::Unquote and
Self::UnquoteSplice), which is itself a 2-of-4 subset of the
quote-family carving (QuoteForm). Returns
Some(UnquoteForm::Unquote) iff this is ,x (a Self::Unquote
wrapper), Some(UnquoteForm::Splice) iff this is ,@x (a
Self::UnquoteSplice wrapper), None for every other outer
shape (Self::Nil, every Self::Atom variant, Self::List,
and the two non-substitution quote-family wrappers Self::Quote
and Self::Quasiquote).
Direct value-level peer of the shape-level projection
SexpShape::as_unquote_form
— the pair (Sexp::as_unquote_form, SexpShape::as_unquote_form)
binds the (Sexp value, UnquoteForm carving marker) pairing at ONE
typed method on each algebra, closing the unquote-subset cell of
the (Sexp value → carving marker) matrix. Marker-only sibling of
Self::as_unquote (which returns
Option<(UnquoteForm, &Sexp)> — marker + wrapped inner) and
direct 2-of-4 subset peer of Self::as_quote_form (which
covers the 4-of-12 quote-family carving with Option<(QuoteForm, &Sexp)>). Post-lift the substrate’s value-level marker-only
carving-marker matrix closes ONE more cell: the atomic axis via
Self::as_atom_kind (6-of-12), the residual axis via
Self::as_structural_kind (2-of-12), the quote-family axis via
Self::as_quote_form().map(|(qf, _)| qf) (4-of-12, marker + inner
available via the pre-existing method), and now the unquote-
subset axis via Self::as_unquote_form (2-of-12, marker only) —
symmetric with the shape-level marker-only projection family on
SexpShape.
Composition laws (three-way agreement — bindings): for every
s: &Sexp,
s.as_unquote_form() == s.as_unquote().map(|(uf, _)| uf) == s.shape().as_unquote_form() == s.as_quote_form().and_then(|(qf, _)| qf.as_unquote_form()).
Pre-lift the unquote-subset carving marker at the value level
was reachable only via one of these three-step compositions —
either through the parent Self::as_unquote projection
(discarding the inner), through the shape algebra
(shape().as_unquote_form()), or through the parent quote-family
projection composed with the 2-of-4 subset gate
QuoteForm::as_unquote_form. Post-lift the projection lands at
ONE typed method on the value algebra, and all three compositions
are pinned as agreement laws (see
sexp_as_unquote_form_agrees_with_as_unquote_map_marker_for_every_variant,
sexp_as_unquote_form_agrees_with_shape_as_unquote_form_for_every_variant,
and
sexp_as_unquote_form_agrees_with_as_quote_form_and_quote_form_as_unquote_form_for_every_variant
in this module). A regression that drifts any of the four
projections from the others surfaces immediately.
Symmetric with Self::as_atom_kind and Self::as_structural_kind
on the marker-only shape (returns just the closed-set marker, no
inner-payload borrow) — where Self::as_quote_form and
Self::as_unquote surface both the marker AND the wrapped
inner &Sexp (because the four quote-family arms and the two
substitution arms structurally carry a boxed inner value),
as_unquote_form returns a marker-only projection: consumers that
need the wrapped inner reach the marker-plus-inner sibling
Self::as_unquote, while consumers that only need the closed-
set carving-marker identity (typed-pattern matchers, diagnostic
filters, coverage sweeps, LSP/REPL structural-navigation gates)
reach this projection and never allocate the tuple.
Composes cleanly with UnquoteForm::marker to project the value-
level substitution carving membership onto its canonical marker
string (, / ,@):
s.as_unquote_form().map(UnquoteForm::marker) — the marker-string
witness for the substitution subset, sibling to
s.as_atom_kind().map(AtomKind::label) on the atomic axis, both
routing through the closed-set marker enum’s canonical-vocabulary
projection at ONE canonical site (UnquoteForm::marker —
itself composed through QuoteForm::prefix).
Structural identity (pinned as a truth-table by
sexp_as_unquote_form_projects_each_variant_to_canonical_unquote_form
and sexp_as_unquote_form_rejects_non_unquote_subset_outer_shapes):
as_unquote_form() == Some(UnquoteForm::Unquote)iffmatches!(self, Sexp::Unquote(_))as_unquote_form() == Some(UnquoteForm::Splice)iffmatches!(self, Sexp::UnquoteSplice(_))as_unquote_form() == Noneiff!matches!(self, Sexp::Unquote(_) | Sexp::UnquoteSplice(_))
Theory anchor: THEORY.md §V.1 — knowable platform; the substitution-
subset carving marker at the value level becomes a NAMED
primitive on the substrate’s Sexp algebra rather than a per-
site composition through either Self::as_unquote (discarding
its &Sexp inner) or Self::shape (walking through the full
12-variant SexpShape closed set to arrive at the 2-of-12
carving marker) or the parent Self::as_quote_form combined
with QuoteForm::as_unquote_form (the 2-of-4 subset gate).
THEORY.md §II.1 invariant 2 — free middle; every consumer that
wants the substitution-subset carving identity without needing
the wrapped inner (a future tatara-check predicate
(check-value-projects-to-unquote-subset …) that filters
diagnostics keyed on the substitution-subset cohort; a future LSP
structural-navigation filter that keys on the substitution-subset
carving membership at the value level; a future
TypedRewriter<TemplateOp> sweep that walks Sexp values whose
substitution-arm identity is Some(UnquoteForm::_) regardless of
inner payload identity; a future REPL pretty-printer that chooses
rendering paths keyed on the value-level substitution carving
marker without needing the inner payload) binds to ONE typed
method on the value algebra. THEORY.md §VI.1 — generation over
composition; the (Sexp variant, UnquoteForm variant) pairing
binds at ONE inherent method on the algebra rather than at three
parallel compositions (as_unquote().map(…), shape() .as_unquote_form(), as_quote_form().and_then(|(qf, _)| qf.as_unquote_form())), so a regression that drifts ONE
composition’s pairing from the others cannot reach the substrate’s
runtime — the type system binds all three compositions to the
projection’s single emission shape.
Frontier inspiration: MLIR’s mlir::dyn_cast<UnquoteFamilyOp>(op) .map(|op| op.marker()) — every typed rewriter that only needs
the op-family identity (without the op’s operands) binds to the
typed-downcast projection composed with an operand-discarding
marker extract; Sexp::as_unquote_form is the marker-only peer
on the substrate’s Sexp algebra, with Option<UnquoteForm>
standing in for MLIR’s Optional<OperationName> marker-only
downcast result. Racket’s syntax-parse ~or* (~unquote _) (~unquote-splice _) — every syntax-class pattern that keys on
the substitution-subset marker identity without binding the
inner form; Sexp::as_unquote_form is the Rust-typed peer that
surfaces the marker identity through a single primitive on the
syntax algebra.
Sourcepub fn as_quote_form(&self) -> Option<(QuoteForm, &Sexp)>
pub fn as_quote_form(&self) -> Option<(QuoteForm, &Sexp)>
Decompose a quote-family form into its typed marker and inner
expression — Some((QuoteForm::Quote, inner)) iff this is 'x
(a Sexp::Quote wrapper), Some((QuoteForm::Quasiquote, inner))
iff this is `x (a Sexp::Quasiquote wrapper),
Some((QuoteForm::Unquote, inner)) iff this is ,x (a
Sexp::Unquote wrapper), Some((QuoteForm::UnquoteSplice, inner))
iff this is ,@x (a Sexp::UnquoteSplice wrapper), None for
every other shape (Nil, Atom, List).
This is the quote-family projection — the typed-marker peer of
Sexp::as_unquote generalized across all four homoiconic
prefix-wrappers. Where Sexp::as_unquote keys the macro-template
SUBSTITUTION surface on the closed pair {Unquote, Splice} (the
two prefixes whose template-time semantic is substitution),
as_quote_form keys the WIRE-SHAPE surfaces (Display rendering,
Hash discrimination, canonical-form interop) on the closed superset
{Quote, Quasiquote, Unquote, UnquoteSplice} — all four prefixes
the reader can tokenize and the writer must round-trip. The
Sexp::as_unquote projection now derives structurally from
as_quote_form’s output via QuoteForm::as_unquote_form — the
2-of-4 subset gate — so the two projections share a SINGLE
implementation site on the Sexp algebra and the
(Sexp variant, QuoteForm variant) pairing binds at ONE rust
function regardless of whether the consumer wants the substitution
subset or the wire-shape superset.
Three consumers in this file route through this primitive:
Hash for Sexp— the fourQuote/Quasiquote/Unquote/UnquoteSplicearms (pre-lift each carrying its own<discr>.hash(h); inner.hash(h)body) collapse to ONE arm that routes throughas_quote_formand reads the discriminator viaQuoteForm::hash_discriminator.Display for Sexp— the fourwrite!(f, "<prefix>{inner}")arms (pre-lift each carrying its own literal prefix string) collapse to ONE arm that routes throughas_quote_formand reads the prefix viaQuoteForm::prefix.Sexp::as_unquote— derivesOption<(UnquoteForm, &Sexp)>by composingas_quote_formwithQuoteForm::as_unquote_form(the 2-of-4 subset projection), so the macro-template substitution surface inherits the (Sexp variant, marker) pairing through this projection’s typed dispatch rather than re-deriving its own arm-based match.
The closed-set guarantee on QuoteForm (exactly
Quote ⊎ Quasiquote ⊎ Unquote ⊎ UnquoteSplice) is threaded through
this projection’s return tuple, so consumers that pattern-match on
form: QuoteForm get rustc-enforced exhaustiveness — a future
Sexp wrapper variant must extend QuoteForm AND this match arm
together (or stay outside the quote family and project to None),
eliminating the silent multi-site extension-drift this lift was
designed to forbid.
Soft face, like the rest of the as_* family on Sexp: it
answers “is this form a quote-family marker, and what does it
wrap?” and yields None for everything that isn’t (skip / fall
through), with no diagnostic.
Structural identity binding it to the quote-family variants and
its as_unquote subset sibling:
as_quote_form() == Some((QuoteForm::Quote, inner))iffself == Sexp::Quote(inner)as_quote_form() == Some((QuoteForm::Quasiquote, inner))iffself == Sexp::Quasiquote(inner)as_quote_form() == Some((QuoteForm::Unquote, inner))iffself == Sexp::Unquote(inner)as_quote_form() == Some((QuoteForm::UnquoteSplice, inner))iffself == Sexp::UnquoteSplice(inner)as_quote_form().is_some() == matches!(self, Sexp::Quote(_) | Sexp::Quasiquote(_) | Sexp::Unquote(_) | Sexp::UnquoteSplice(_))as_unquote() == as_quote_form().and_then(|(qf, inner)| qf.as_unquote_form().map(|uf| (uf, inner)))
The returned &Sexp borrows the inner box’s body verbatim — no
clone, no allocation — same lifetime as &self and same posture
as Sexp::as_unquote’s tail.
Theory anchor: THEORY.md §VI.1 — generation over composition; the
quote-family (Sexp variant, prefix string, hash discriminator)
triple appeared inline at three sites (Hash for Sexp,
Display for Sexp, as_unquote) — well past the ≥2 PRIME-DIRECTIVE
trigger once the structural shape is named. THEORY.md §V.1 —
knowable platform; the quote-family typed-marker projection becomes
a NAMED primitive on the substrate’s Sexp algebra rather than
per-site inline matches paired with per-site discriminator literals
and prefix literals. THEORY.md §II.1 invariant 1 — typed entry; the
reader’s prefix-to-variant dispatch ([crate::reader::read_quoted])
AND the Display impl’s variant-to-prefix dispatch are dual
typed-entry / typed-exit gates over the same closed set; the
QuoteForm algebra threads BOTH gates through ONE typed enum so a
regression that drifts one side’s prefix from the other (e.g. the
reader gains a fifth prefix but the Display impl doesn’t) is no
longer a silent two-site divergence — rustc binds both sides to
the same closed-set enum. THEORY.md §II.1 invariant 2 — free
middle; the three consumers (Hash, Display, as_unquote) route
through the SAME projection, so a regression that drifts ONE
consumer’s (Sexp variant, marker) pairing from the others cannot
reach the substrate’s runtime.
Frontier inspiration: Racket’s syntax-parse ~or* (~quote stx) (~quasiquote stx) (~unquote stx) (~unquote-splice stx) pattern —
every macro-template pattern over '/`/,/,@ binds to
ONE typed decomposition that surfaces the marker identity
alongside the inner expression; the substrate’s as_quote_form is
the Rust-typed peer of that pattern, lifted onto the Sexp
algebra with QuoteForm standing in for Racket’s pattern-class
identity at the homoiconic prefix surface. MLIR’s typed-IR
projection mlir::dyn_cast<QuoteFamilyOp>(op) — the typed downcast
from a polymorphic IR node onto a closed-set op family is the MLIR
idiom; as_quote_form is the unstructured-projection peer on the
substrate’s Sexp algebra, with Option<(QuoteForm, &Sexp)>
standing in for MLIR’s typed downcast result.
Sourcepub fn as_quote_form_marker(&self) -> Option<QuoteForm>
pub fn as_quote_form_marker(&self) -> Option<QuoteForm>
Soft projection onto the closed-set QuoteForm quote-family
carving marker — the 4-of-12 carving of the SexpShape algebra
covering the four homoiconic prefix-wrappers (Self::Quote,
Self::Quasiquote, Self::Unquote, Self::UnquoteSplice).
Returns Some(QuoteForm::Quote) iff this is 'x (a
Self::Quote wrapper), Some(QuoteForm::Quasiquote) iff this
is `x (a Self::Quasiquote wrapper),
Some(QuoteForm::Unquote) iff this is ,x (a Self::Unquote
wrapper), Some(QuoteForm::UnquoteSplice) iff this is ,@x (a
Self::UnquoteSplice wrapper), None for every other outer
shape (Self::Nil, every Self::Atom variant, Self::List).
Direct value-level peer of the shape-level projection
SexpShape::as_quote_form
— the pair (Sexp::as_quote_form_marker, SexpShape::as_quote_form)
binds the (Sexp value, QuoteForm carving marker) pairing at ONE
typed method on each algebra, closing the quote-family cell of
the (Sexp value → carving marker) matrix at the marker-only
value-level projection surface. Marker-only sibling of
Self::as_quote_form (which returns Option<(QuoteForm, &Sexp)>
— marker + wrapped inner). Post-lift the substrate’s value-level
marker-only carving-marker matrix closes its FINAL cell: the
atomic axis via Self::as_atom_kind (6-of-12), the residual
axis via Self::as_structural_kind (2-of-12), the unquote-
subset axis via Self::as_unquote_form (2-of-12), and NOW the
quote-family axis via Self::as_quote_form_marker (4-of-12) —
symmetric with the shape-level marker-only projection family on
SexpShape.
Composition laws (two-way agreement — bindings): for every
s: &Sexp,
s.as_quote_form_marker() == s.as_quote_form().map(|(qf, _)| qf) == s.shape().as_quote_form(). Pre-lift the quote-family carving
marker at the value level was reachable only via one of these
two-step compositions — either through the parent
Self::as_quote_form projection (discarding the wrapped inner
via .map(|(qf, _)| qf)) or through the shape algebra
(s.shape().as_quote_form(), walking the full 12-variant
SexpShape closed set to arrive at
the 4-of-12 carving marker). Post-lift the projection lands at
ONE typed method on the value algebra, and both compositions
are pinned as agreement laws (see
sexp_as_quote_form_marker_agrees_with_as_quote_form_map_marker_for_every_variant
and
sexp_as_quote_form_marker_agrees_with_shape_as_quote_form_for_every_variant
in this module).
Superset-gate contract with Self::as_unquote_form: for every
s: &Sexp, s.as_unquote_form().is_some() implies
s.as_quote_form_marker().is_some() (the 2-of-12 substitution
subset is a proper subset of the 4-of-12 quote family). The two
non-substitution quote-family wrappers (Self::Quote and
Self::Quasiquote) satisfy as_quote_form_marker().is_some()
AND as_unquote_form().is_none() — the value-level image of the
2-of-4 subset gate QuoteForm::as_unquote_form. Pinned by
sexp_as_quote_form_marker_extends_as_unquote_form_to_full_quote_family.
Structural identity binding it to the quote-family variants:
as_quote_form_marker() == Some(QuoteForm::Quote)iffmatches!(self, Sexp::Quote(_))as_quote_form_marker() == Some(QuoteForm::Quasiquote)iffmatches!(self, Sexp::Quasiquote(_))as_quote_form_marker() == Some(QuoteForm::Unquote)iffmatches!(self, Sexp::Unquote(_))as_quote_form_marker() == Some(QuoteForm::UnquoteSplice)iffmatches!(self, Sexp::UnquoteSplice(_))as_quote_form_marker() == Noneiff!matches!(self, Sexp::Quote(_) | Sexp::Quasiquote(_) | Sexp::Unquote(_) | Sexp::UnquoteSplice(_))
Theory anchor: THEORY.md §V.1 — knowable platform; the quote-
family carving marker at the value level becomes a NAMED
primitive on the substrate’s Sexp algebra rather than a per-
site two-step composition through either Self::as_quote_form
(discarding its &Sexp inner) or Self::shape (walking through
the full 12-variant SexpShape closed
set to arrive at the 4-of-12 carving marker). THEORY.md §II.1
invariant 2 — free middle; every consumer that wants the quote-
family carving identity without needing the wrapped inner (a
future tatara-check predicate (check-value-projects-to-quote- family …) that filters diagnostics keyed on the quote-family
cohort; a future LSP structural-navigation filter that keys on
the quote-family carving membership at the value level; a
future TypedRewriter<QuoteFamilyOp> sweep that walks Sexp
values whose quote-family arm identity is Some(QuoteForm::_)
regardless of inner payload identity; a future REPL pretty-
printer that chooses rendering paths keyed on the value-level
quote-family carving marker without needing the inner payload)
routes through ONE typed method rather than reaching into one of
the two composition sites, and both compositions are pinned as
agreement laws so a regression that drifts ONE composition’s
pairing from the other cannot reach the substrate’s runtime.
THEORY.md §VI.1 — generation over composition; the (Sexp variant,
QuoteForm variant) pairing binds at ONE inherent method on the
algebra rather than at two parallel compositions, so a future
extension (e.g. a fifth Sexp quote-family wrapper) lands at
ONE match arm in the parent as_quote_form projection and
inherits through this method’s structural composition.
Frontier inspiration: MLIR’s mlir::dyn_cast<QuoteFamilyOp>(op) .map(|op| op.marker()) — every typed rewriter that only needs
the op-family identity (without the op’s operands) binds to the
typed-downcast projection composed with an operand-discarding
marker extract; Sexp::as_quote_form_marker is the marker-only
peer on the substrate’s Sexp algebra, with
Option<QuoteForm> standing in for MLIR’s
Optional<OperationName> marker-only downcast result. Racket’s
syntax-parse ~or* (~quote _) (~quasiquote _) (~unquote _) (~unquote-splice _) — every syntax-class pattern that keys on
the quote-family marker identity without binding the inner form;
Sexp::as_quote_form_marker is the Rust-typed peer that
surfaces the marker identity through a single primitive on the
syntax algebra.
Sourcepub fn expect_quote_form(&self) -> (QuoteForm, &Sexp)
pub fn expect_quote_form(&self) -> (QuoteForm, &Sexp)
Quote-family projection, asserted-total face of Sexp::as_quote_form.
Returns (QuoteForm, &Sexp) verbatim — same borrowed-inner posture,
same closed-set marker — but panics with QUOTE_FAMILY_PROJECTION_INVARIANT
instead of yielding None for non-quote-family variants. Use AFTER
an outer pattern match has narrowed the discriminant union to the
quote family (Sexp::Quote(_) | Sexp::Quasiquote(_) | Sexp::Unquote(_) | Sexp::UnquoteSplice(_)); the panic message states the invariant the
caller’s outer pattern already proves.
Pre-lift the five production-site quote-family-arm consumers —
Hash for Sexp::hash_discriminator, Display for Sexp::prefix,
domain::sexp_shape, domain::sexp_to_json, interop::iac_forge_tag —
each carried a verbatim copy of the 4-arm wildcard pattern AND a
verbatim copy of the inline
.as_quote_form().expect("matched quote-family variant must project to Some via as_quote_form") re-projection. The (pattern, expect message) pair appeared bit-for-bit at FIVE sites. Post-lift the
expect message lives at ONE named const and the projection-with-
assertion lives at ONE primitive on the Sexp algebra; the five
callsites collapse to ONE typed query each. A future quote-family
extension that drifts ONE site’s panic text from the others becomes
structurally impossible (one const, one method); a future site that
needs the same “outer-narrowed, total projection” shape lands on
this primitive directly without re-deriving the expect literal.
#[track_caller] ensures a panic surfaces the consumer’s source
position, not this projection’s — so the diagnostic stays
load-bearing under the lift.
Sibling posture to the expect_* family of typed-projection
asserted-total faces across the substrate’s closed-set algebras
(Option::expect, Result::expect) — the assertion is the same
shape, the message is named on the algebra it asserts about.
§Panics
Panics with QUOTE_FAMILY_PROJECTION_INVARIANT if self is not
a quote-family variant. The outer pattern match at every caller
site is the proof of the invariant; the panic is the static
fall-through for a regression that drifts that proof.
Theory anchor: THEORY.md §VI.1 — generation over composition; the
(4-arm wildcard pattern, expect re-projection) pair appeared bit-
for-bit at five production sites — well past the ≥2 PRIME-DIRECTIVE
trigger. THEORY.md §V.1 — knowable platform; the panic message and
the projection-with-assertion are now ONE named primitive on the
substrate’s Sexp algebra, structurally binding the invariant
across every consumer that asserts an outer narrowing.
Sourcepub fn iac_forge_tag(&self) -> Option<&'static str>
pub fn iac_forge_tag(&self) -> Option<&'static str>
Cross-crate canonical iac-forge tag for the outer Sexp value —
the OUTER-VALUE peer of the shape-level [crate::error::SexpShape ::iac_forge_tag] one algebra layer down. Some(&'static str) for
the four homoiconic prefix-wrapper arms — Self::Quote → Some("quote"), Self::Quasiquote → Some("quasiquote"),
Self::Unquote → Some("unquote"), Self::UnquoteSplice → Some("unquote-splicing") — and None for the outer atomic-payload
arm (Self::Atom) AND the two structural-residual arms
(Self::Nil, Self::List). The 4-of-7 partial projection on
the outer-Sexp algebra surfaces
crate::ast::QuoteForm::iac_forge_tag’s cross-crate canonical-
form tag surface at the outermost value-carrier algebra level,
composed through the pre-existing Self::shape projection and
crate::error::SexpShape::iac_forge_tag’s shape-level partial
projection.
Composition law: sexp.iac_forge_tag() == sexp.shape().iac_forge_tag() for every sexp: &Sexp — the outer-
Sexp cross-crate canonical-form tag surface routes through
Self::shape into the shape-level partial projection, which in
turn composes through crate::error::SexpShape::as_quote_form
with crate::ast::QuoteForm::iac_forge_tag’s canonical 4-of-4
closed-set tag projection. Post-lift the outer-Sexp cross-crate
canonical-form tag surface closes at FOUR typed layers: outer
Self::iac_forge_tag (7-arm outer dispatch on the outer
Sexp algebra, this method) → shape-level
crate::error::SexpShape::iac_forge_tag (12-arm shape-level
dispatch on the crate::error::SexpShape algebra) →
quote-family carving crate::error::SexpShape::as_quote_form
(4-of-12 quote-family sub-carving) → sub-carving tag
crate::ast::QuoteForm::iac_forge_tag (4-arm quote-family
sub-carving’s canonical-form tag projection).
Pre-lift a consumer with a typed Sexp value in hand (a
generation-side canonical-form emitter, a downstream iac-forge
attestation site, an LSP / REPL / audit-trail metric keyed on the
observed outer value) wanting the cross-crate iac-forge canonical
tag string had to spell the two-step composition
sexp.shape().iac_forge_tag() at every callsite, or route through
Self::as_quote_form_marker composed with
crate::ast::QuoteForm::iac_forge_tag via map as the
crate::interop (removed) From<&Sexp> for iac_forge::SExpr impl does
for its four quote-family arms via Self::expect_quote_form
composed with crate::ast::QuoteForm::iac_forge_tag. Post-lift
the outer-Sexp canonical-form tag projection binds at ONE
typed-algebra method on the outer value-carrier — the SEVENTH
consumer of the outer-Sexp projection surface (sibling of
Self::shape, Self::type_name,
Self::hash_discriminator, Self::as_atom, Self::as_list,
Self::as_quote_form, Self::as_quote_form_marker,
Self::as_unquote, Self::as_unquote_form), matching the
same shape-composition posture Self::hash_discriminator takes
through the outer → shape one-step delegation.
The Option<&'static str> return shape mirrors
crate::error::SexpShape::iac_forge_tag’s partial-projection
shape one algebra level down — the outer-Sexp seven-arm closed
set’s projection PARTIALIZES on the three non-quote-family shapes
(Nil, Atom, List) exactly as the shape-level twelve-arm
closed set’s projection PARTIALIZES on the eight non-quote-family
shapes. The kernel’s outer cardinality (three: Nil / Atom /
List) matches the shape-level kernel’s cardinality (eight)
through Self::shape’s six-atomic-arms → outer Atom collapse
— the outer three-arm kernel {Nil, Atom, List} corresponds to
the shape-level eight-arm kernel {Nil, Symbol, Keyword, String, Int, Float, Bool, List} under the outer → shape projection.
The &'static str lifetime is load-bearing: every iac-forge
consumer projects through this method into the canonical
2-element-list head without an allocation, parallel to how
crate::ast::QuoteForm::iac_forge_tag on the sub-carving,
crate::error::SexpShape::iac_forge_tag on the shape-level
projection, and crate::error::UnquoteForm::iac_forge_tag on
the template-substitution subset project their respective closed
sets. A future eighth Sexp variant (e.g. a hypothetical
Vector for #(...) reader syntax, Map for {...}, Char for
#\x) extends crate::error::SexpShape (adding a None-arm
non-quote-family shape) — this method picks up the new arm’s
None mechanically through the shape composition, with rustc’s
exhaustiveness binding the extension end-to-end at
crate::error::SexpShape::as_quote_form’s closed match.
Theory anchor: THEORY.md §V.1 — knowable platform; the (outer
Sexp variant, canonical iac-forge tag) pairing becomes a TYPE
projection on the outermost value-carrier algebra rather than an
inline .shape().iac_forge_tag() two-step at every consumer. A
typo or swap at the projection site is no longer a runtime tag
drift but a compile error against the typed composition — the
Sexp ↔ SexpShape ↔ QuoteForm ↔ tag string chain is rustc-
enforced end-to-end. THEORY.md §II.1 invariant 2 — free middle;
the (outer value, canonical iac-forge tag) pairing now binds at
ONE site on the outer-Sexp algebra, composing through the pre-
existing shape-level partial projection rather than duplicating
the four-arm match here. THEORY.md §VI.1 — generation over
composition; the outer-Sexp cross-crate canonical-form tag
surface closes at FOUR typed layers (outer → shape → carving →
sub-carving-tag), each keyed on the SAME canonical-form tag
projection carried at the closed-set sub-carving level.
Frontier inspiration: MLIR’s mlir::Operation::getName() typed
projection composed with mlir::OperationName::getStringRef() —
narrowing an operation-carrier value through its typed op-name
identity yields the canonical cross-boundary string identity in
ONE typed composition. Self::iac_forge_tag is the Rust-typed
peer where the “project to shape” step (Self::shape) composes
with the “read the shape’s canonical tag” step
(crate::error::SexpShape::iac_forge_tag) into ONE outer-value
projection.
Sourcepub fn prefix(&self) -> Option<&'static str>
pub fn prefix(&self) -> Option<&'static str>
Canonical reader-punctuation prefix for the outer Sexp value —
the OUTER-VALUE peer of the shape-level
crate::error::SexpShape::prefix one algebra layer down.
Some(&'static str) for the four homoiconic prefix-wrapper arms —
Self::Quote → Some("'"), Self::Quasiquote → Some("“), Self::Unquote → Some(”,“), Self::UnquoteSplice → Some(”,@“)— andNone for the outer atomic-payload arm ([Self::Atom]) AND the two structural-residual arms ([Self::Nil], [Self::List]). The 4-of-7 partial projection on the outer-Sexp algebra surfaces [crate::ast::QuoteForm::prefix]'s reader-punctuation surface at the outermost value-carrier algebra level, composed through the pre-existing [Self::shape] projection and [crate::error::SexpShape::prefix`]’s shape-level partial
projection.
Composition law: sexp.prefix() == sexp.shape().prefix() for
every sexp: &Sexp — the outer-Sexp reader-punctuation surface
routes through Self::shape into the shape-level partial
projection, which in turn composes through
crate::error::SexpShape::as_quote_form with
crate::ast::QuoteForm::prefix’s canonical 4-of-4 closed-set
prefix projection. Post-lift the outer-Sexp reader-punctuation
surface closes at FOUR typed layers: outer Self::prefix
(7-arm outer dispatch on the outer Sexp algebra, this method)
→ shape-level crate::error::SexpShape::prefix (12-arm shape-
level dispatch on the crate::error::SexpShape algebra) →
quote-family carving crate::error::SexpShape::as_quote_form
(4-of-12 quote-family sub-carving) → sub-carving prefix
crate::ast::QuoteForm::prefix (4-arm quote-family sub-
carving’s canonical reader-punctuation projection).
Pre-lift a consumer with a typed Sexp value in hand (an
[fmt::Display for Sexp] impl that renders the four quote-family
arms as <prefix><inner>, an LSP hover / REPL completion that
echoes the source-punctuation prefix of a wrapper value, an
audit-trail metric keyed on the observed outer value) wanting
the canonical reader-punctuation prefix string had to spell the
two-step composition sexp.shape().prefix() at every callsite,
or route through Self::as_quote_form_marker composed with
crate::ast::QuoteForm::prefix via map as the
[fmt::Display for Sexp] impl does for its four quote-family
arms via Self::expect_quote_form composed with
crate::ast::QuoteForm::prefix. Post-lift the outer-Sexp
reader-punctuation projection binds at ONE typed-algebra method
on the outer value-carrier — the natural next rung on the
trajectory mirroring the Self::iac_forge_tag →
crate::error::SexpShape::iac_forge_tag ladder one vocabulary
axis over, matching the same shape-composition posture
Self::hash_discriminator and Self::iac_forge_tag take
through the outer → shape one-step delegation.
The Option<&'static str> return shape mirrors
crate::error::SexpShape::prefix’s partial-projection shape one
algebra level down — the outer-Sexp seven-arm closed set’s
projection PARTIALIZES on the three non-quote-family shapes
(Nil, Atom, List) exactly as the shape-level twelve-arm
closed set’s projection PARTIALIZES on the eight non-quote-family
shapes. The kernel’s outer cardinality (three: Nil / Atom /
List) matches the shape-level kernel’s cardinality (eight)
through Self::shape’s six-atomic-arms → outer Atom collapse
— the outer three-arm kernel {Nil, Atom, List} corresponds to
the shape-level eight-arm kernel {Nil, Symbol, Keyword, String, Int, Float, Bool, List} under the outer → shape projection.
The reader-punctuation vocabulary this method projects ("'" /
"`" / "," / ",@") is INTENTIONALLY DISJOINT from the
two sibling &'static str outer-value projection axes:
Self::iac_forge_tag— cross-crate canonical form ("quote"/"quasiquote"/"unquote"/"unquote-splicing"), BLAKE3 attestation keys, render-cache shape (load-bearing for byte-identical inter-crate compatibility with the iac-forge ecosystem);Self::type_name— operator-facing diagnostic label ("nil"/"atom"/"list"/"quote"/"quasiquote"/"unquote"/"unquote-splice") on the outer 7-arm surface,crate::error::LispError::TypeMismatch’sgotrendering, REPL / LSP shape-of-witness surface.
This method projects the reader’s SOURCE-TEXT vocabulary — the
four punctuation characters that appear literally in Lisp source
at each variant’s homoiconic prefix. The three outer-value
closed-set projections key the SAME seven-arm outer algebra on
THREE distinct &'static str vocabularies (source-punctuation,
diagnostic-label, cross-crate canonical-form); consolidating any
two would silently break either the reader round-trip, the
operator-facing diagnostic surface, OR the iac-forge attestation
pipeline. The three vocabularies’ distinctness is pinned bit-for-
bit through the composition law across the closed-set typed
algebra.
The &'static str lifetime is load-bearing: every reader / LSP
/ REPL / [fmt::Display for Sexp] consumer projects through this
method into the canonical prefix character without an allocation,
parallel to how crate::ast::QuoteForm::prefix on the sub-
carving, crate::error::SexpShape::prefix on the shape-level
projection, Self::iac_forge_tag on the cross-crate canonical-
form axis, and crate::error::UnquoteForm::marker on the
template-marker axis project their respective closed sets. A
future eighth Sexp variant (e.g. a hypothetical Vector for
#(...) reader syntax, Map for {...}, Char for #\x)
extends crate::error::SexpShape (adding a None-arm non-
quote-family shape) — this method picks up the new arm’s None
mechanically through the shape composition, with rustc’s
exhaustiveness binding the extension end-to-end at
crate::error::SexpShape::as_quote_form’s closed match.
Theory anchor: THEORY.md §V.1 — knowable platform; the (outer
Sexp variant, reader-punctuation prefix) pairing becomes a
TYPE projection on the outermost value-carrier algebra rather
than an inline .shape().prefix() two-step at every consumer.
A typo or swap at the projection site is no longer a runtime
prefix drift but a compile error against the typed composition
— the Sexp ↔ SexpShape ↔ QuoteForm ↔ prefix character
chain is rustc-enforced end-to-end. THEORY.md §II.1 invariant 2
— free middle; the (outer value, reader-punctuation prefix)
pairing now binds at ONE site on the outer-Sexp algebra,
composing through the pre-existing shape-level partial
projection rather than duplicating the four-arm match here.
THEORY.md §VI.1 — generation over composition; the outer-Sexp
reader-punctuation surface closes at FOUR typed layers (outer
→ shape → carving → sub-carving-prefix), each keyed on the SAME
reader-punctuation projection carried at the closed-set sub-
carving level.
Frontier inspiration: MLIR’s mlir::Operation::getName() typed
projection composed with mlir::OperationName::getStringRef()
— narrowing an operation-carrier value through its typed op-name
identity yields the canonical cross-boundary string identity in
ONE typed composition. Self::prefix is the Rust-typed peer
where the “project to shape” step (Self::shape) composes
with the “read the shape’s canonical reader-punctuation” step
(crate::error::SexpShape::prefix) into ONE outer-value
projection — sibling of Self::iac_forge_tag one vocabulary
axis over on the cross-crate canonical-form surface.
Sourcepub fn node_count(&self) -> usize
pub fn node_count(&self) -> usize
Total structural node count of the outer Sexp value — one
node per outer-algebra arm plus the recursive node count of
each child. Self::Nil and Self::Atom contribute one
node apiece (the outer arm itself); Self::List contributes
one node for the outer arm plus the summed node count of each
child element; the four homoiconic wrapper arms
(Self::Quote, Self::Quasiquote, Self::Unquote,
Self::UnquoteSplice) each contribute one node for the
outer arm plus the node count of the wrapped inner form. The
projection is a structural size on the AST — every closed-set
arm counts as one, so the count is well-defined on ANY
Sexp value regardless of how it was constructed.
Load-bearing arithmetic identities:
Sexp::Nil.node_count() == 1Sexp::Atom(_).node_count() == 1Sexp::list(items).node_count() == 1 + sum(item.node_count())Sexp::quote(inner).node_count() == 1 + inner.node_count()- (peer identity for each other quote-family arm)
The identities compose: node_count is monotone in tree
growth (a strictly-larger tree — one containing more arms —
has a strictly-larger count), so a resource ceiling keyed on
node_count bounds the total AST arm-count reachable at that
ceiling. A future Self::UnquoteSplice wrapper appearing
inside a list contributes one node for the wrapper AND one
node for the outer list arm plus the node count of the
wrapper’s inner form — the identity holds compositionally
through the wrapper’s Box<Sexp> payload.
Consumers so far: crate::macro_expand::Expander’s
max_expansion_size ceiling — the RESOURCE-axis peer of the
max_expansion_depth (recursion length) and
max_cache_entries (memoization width) ceilings — projects
the freshly-applied macro expansion through node_count to
decide whether the result crosses the “expansion bomb”
threshold on the OUTPUT-SIZE axis. A #[derive(TataraDomain)]
consumer that wants to reject “this macro produced a giant
blob” at the expander boundary now inherits the projection
mechanically through the ceiling; no per-consumer walker
discipline required.
The usize return shape is the natural resource-count
carrier — sibling to HashMap::len (the cache-width ceiling
consumes) and to the usize depth counter (the recursion
ceiling consumes) — so all three ceilings compose against a
single arithmetic type without cross-cast overhead.
Theory anchor: THEORY.md §V.1 — knowable platform; the
structural size of a value on the outer Sexp algebra
becomes a first-class TYPED projection rather than a
per-consumer hand-rolled walker. THEORY.md §VI.1 — generation
over composition; a future ceiling on a subtree count (e.g.
“reject any expansion whose LIST arms exceed N”) emerges
naturally as a peer projection on the same closed-set
algebra, not as a separate walker.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Sexp
impl<'de> Deserialize<'de> for Sexp
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<Sexp, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<Sexp, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl MacroArgCarrier for Sexp
impl MacroArgCarrier for Sexp
Source§type Site = ()
type Site = ()
Sexp carries no position, so there is nothing to thread — the unit
site is the structural statement that this carrier synthesizes values
without context.