Skip to main content

macroonz_compiler/stamp/
type_guard.rs

1//! The stamp home's invariant nucleus: every road that reaches a private field, and the one road that composes a published artifact.
2//!
3//! Declared inside `types.rs` as its own child, which is what makes this home's claims structural rather than remembered.
4//! A spelling is admitted against the alphabet here, so a name the consumer's compiler would read as something else is not a value anybody can hold.
5//! A pattern's seats and a stamp's sites are closed here, so a definition that binds one metavariable twice, or a manifest that names one site twice, is refused before a token exists.
6//! And an artifact is composed here, so there is no half-rendered publication unit for a reader to mistake for a whole one.
7
8use super::super::render;
9use super::{
10    Landing, PART_LIMIT, PATH_SEGMENT_LIMIT, Part, Pattern, PublicationGround, PublicationRecord,
11    PublishedStamp, SITE_LIMIT, Seat, Seating, Site, SiteRoot, Stamp, StampError, StampName,
12    StampedPlan, Visibility,
13};
14use crate::bounded::{Bounded, NonEmpty, NonEmptyError};
15use crate::identity::{self, Identity};
16use crate::plan::DigestContract;
17use crate::token::{GeneratedTree, rendered_name};
18use std::collections::BTreeSet;
19
20impl Seat {
21    /// Declare one metavariable seat.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`StampError::NotAnIdentifier`] where the name cannot seat a metavariable — not one Rust identifier, or a keyword the language already took, either of which a matcher refuses.
26    pub fn declared(name: &str, seating: Seating) -> Result<Self, StampError> {
27        if !rendered_name(name) {
28            return Err(StampError::NotAnIdentifier);
29        }
30        Ok(Self {
31            name: name.to_owned(),
32            seating,
33        })
34    }
35
36    /// The name material travels under.
37    #[must_use]
38    pub fn name(&self) -> &str {
39        self.name.as_str()
40    }
41
42    /// The shape it travels in.
43    #[must_use]
44    pub const fn seating(&self) -> Seating {
45        self.seating
46    }
47}
48
49impl Pattern {
50    /// Declare one pattern: the sentence its definition is documented with, the shape it is invoked in, and the body that shape expands into.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`StampError::SeatNameDoubled`] where two seats carry one name, [`StampError::PatternEmpty`] where no part was stated, and [`StampError::PatternUnbounded`] where the parts outgrow the declared magnitude.
55    ///
56    /// The namespace is closed before the magnitude, because a collision is a defect in what was declared and a caller repairing a magnitude first would repair the collision second.
57    pub fn declared(note: &str, parts: Vec<Part>, body: GeneratedTree) -> Result<Self, StampError> {
58        seat_names_closed(&parts)?;
59        let admitted: NonEmpty<Part, PART_LIMIT> =
60            NonEmpty::new(parts).map_err(|refusal| match refusal {
61                NonEmptyError::Empty(_) => StampError::PatternEmpty,
62                NonEmptyError::Overflow(overflow) => StampError::PatternUnbounded { overflow },
63            })?;
64        Ok(Self {
65            note: note.to_owned(),
66            parts: admitted,
67            body,
68        })
69    }
70
71    /// The sentence the definition is documented with.
72    #[must_use]
73    pub fn note(&self) -> &str {
74        self.note.as_str()
75    }
76
77    /// The declared shape, in the order it is written; structurally at least one part.
78    ///
79    /// # Ordering
80    ///
81    /// This order is meaning: a matcher and every invocation are walks over it, so the same parts stated in another order are another grammar.
82    #[must_use]
83    pub fn parts(&self) -> &NonEmpty<Part, PART_LIMIT> {
84        &self.parts
85    }
86
87    /// The body the shape expands into.
88    #[must_use]
89    pub const fn body(&self) -> &GeneratedTree {
90        &self.body
91    }
92
93    /// The seats of the shape, in the order a site supplies arguments for them.
94    pub fn seats(&self) -> impl Iterator<Item = &Seat> {
95        self.parts.iter().filter_map(|part| match part {
96            Part::Seat(seat) => Some(seat),
97            Part::Literal(_) | Part::Reach => None,
98        })
99    }
100
101    /// How many seats the shape declares.
102    #[must_use]
103    pub fn seat_count(&self) -> usize {
104        self.seats().count()
105    }
106
107    /// Whether the shape gives a site's visibility a coordinate.
108    #[must_use]
109    pub fn reaches(&self) -> bool {
110        self.parts.iter().any(|part| match part {
111            Part::Reach => true,
112            Part::Literal(_) | Part::Seat(_) => false,
113        })
114    }
115}
116
117impl StampName {
118    /// The name one published stamp is exported under.
119    ///
120    /// # Errors
121    ///
122    /// Returns [`StampError::NotAnIdentifier`] where the spelling cannot name an exported item — not one Rust identifier, or a keyword the language already took.
123    pub fn declared(spelling: &str) -> Result<Self, StampError> {
124        if !rendered_name(spelling) {
125            return Err(StampError::NotAnIdentifier);
126        }
127        Ok(Self {
128            spelling: spelling.to_owned(),
129        })
130    }
131
132    /// The exported spelling a site invokes this stamp by.
133    #[must_use]
134    pub fn spelling(&self) -> &str {
135        self.spelling.as_str()
136    }
137}
138
139impl SiteRoot {
140    /// The path one site reaches its stamp by, parsed from the segments the caller stated.
141    ///
142    /// # Errors
143    ///
144    /// Returns [`StampError::NotAnIdentifier`] where a segment cannot name a step of a site's path, [`StampError::PathEmpty`] where no segment was stated, and [`StampError::PathUnbounded`] where the segments outgrow the declared magnitude.
145    ///
146    /// The checks are in that order, so exactly one cause is true of any refused root.
147    /// The reading is position-aware the way the language's own path grammar is: the root position admits the qualifiers a site lawfully roots itself under — `crate`, `self`, or a leading run of `super` — and every segment past the qualifiers names an item, read against the composed law that refuses the keyword roster.
148    pub fn spelled(segments: Vec<String>) -> Result<Self, StampError> {
149        let names = match segments.split_first() {
150            Some((root, rest)) if root.as_str() == "crate" || root.as_str() == "self" => rest,
151            Some(_) | None => {
152                let qualifiers = segments
153                    .iter()
154                    .take_while(|segment| segment.as_str() == "super")
155                    .count();
156                segments.get(qualifiers..).unwrap_or(&[])
157            }
158        };
159        for segment in names {
160            if !rendered_name(segment.as_str()) {
161                return Err(StampError::NotAnIdentifier);
162            }
163        }
164        let admitted: NonEmpty<String, PATH_SEGMENT_LIMIT> =
165            NonEmpty::new(segments).map_err(|refusal| match refusal {
166                NonEmptyError::Empty(_) => StampError::PathEmpty,
167                NonEmptyError::Overflow(overflow) => StampError::PathUnbounded { overflow },
168            })?;
169        Ok(Self { segments: admitted })
170    }
171
172    /// The segments, in the order they were stated; structurally at least one.
173    #[must_use]
174    pub fn segments(&self) -> &NonEmpty<String, PATH_SEGMENT_LIMIT> {
175        &self.segments
176    }
177
178    /// How many segments the root carries; structurally at least one.
179    #[must_use]
180    pub fn count(&self) -> usize {
181        self.segments.count()
182    }
183}
184
185impl Site {
186    /// Declare one site that adopts a stamp.
187    ///
188    /// The name is a label rather than a spelling: it is what the manifest calls this landing, and no token is ever written from it.
189    ///
190    /// # Errors
191    ///
192    /// Returns [`StampError::ArgumentsUnbounded`] where the arguments outgrow the declared magnitude.
193    /// Whether they are the RIGHT arguments is settled where the site meets its pattern, in [`Stamp::declared`].
194    pub fn declared(
195        name: &str,
196        root: SiteRoot,
197        reach: Visibility,
198        arguments: Vec<GeneratedTree>,
199    ) -> Result<Self, StampError> {
200        let admitted: Bounded<GeneratedTree, PART_LIMIT> = Bounded::new(arguments)
201            .map_err(|overflow| StampError::ArgumentsUnbounded { overflow })?;
202        Ok(Self {
203            name: name.to_owned(),
204            root,
205            reach,
206            arguments: admitted,
207        })
208    }
209
210    /// What the manifest calls this landing.
211    #[must_use]
212    pub fn name(&self) -> &str {
213        self.name.as_str()
214    }
215
216    /// The path this site reaches its stamp by.
217    #[must_use]
218    pub const fn root(&self) -> &SiteRoot {
219        &self.root
220    }
221
222    /// The reach this site writes.
223    #[must_use]
224    pub const fn reach(&self) -> Visibility {
225        self.reach
226    }
227
228    /// The material this site supplies, one argument per declared seat, in seat order.
229    #[must_use]
230    pub fn arguments(&self) -> &[GeneratedTree] {
231        self.arguments.as_slice()
232    }
233}
234
235impl Stamp {
236    /// Declare the complete payload one published stamp is rendered from.
237    ///
238    /// # Errors
239    ///
240    /// Returns [`StampError::SiteNameDoubled`] where two sites carry one name, [`StampError::ArgumentsUnmatched`] where a site supplies a different number of arguments than the pattern declares seats, [`StampError::ReachUnseated`] where a site declares a reach the pattern gives no coordinate to, [`StampError::SitesAbsent`] where no site was stated, and [`StampError::SitesUnbounded`] where the sites outgrow the declared magnitude.
241    ///
242    /// The namespace is closed first, then each site is settled against the pattern in the order the sites were stated, and the magnitude last.
243    pub fn declared(
244        name: StampName,
245        pattern: Pattern,
246        sites: Vec<Site>,
247    ) -> Result<Self, StampError> {
248        site_names_closed(&sites)?;
249        sites_seated(&pattern, &sites)?;
250        let admitted: NonEmpty<Site, SITE_LIMIT> =
251            NonEmpty::new(sites).map_err(|refusal| match refusal {
252                NonEmptyError::Empty(_) => StampError::SitesAbsent,
253                NonEmptyError::Overflow(overflow) => StampError::SitesUnbounded { overflow },
254            })?;
255        Ok(Self {
256            name,
257            pattern,
258            sites: admitted,
259        })
260    }
261
262    /// The name this stamp is exported under.
263    #[must_use]
264    pub const fn name(&self) -> &StampName {
265        &self.name
266    }
267
268    /// The pattern this stamp stamps.
269    #[must_use]
270    pub const fn pattern(&self) -> &Pattern {
271        &self.pattern
272    }
273
274    /// The sites covered, in the order they were declared; structurally at least one.
275    ///
276    /// # Ordering
277    ///
278    /// This order is meaning for a migration: one invocation is rendered per site in the order this yields.
279    #[must_use]
280    pub fn sites(&self) -> &NonEmpty<Site, SITE_LIMIT> {
281        &self.sites
282    }
283
284    /// How many sites this stamp covers; structurally at least one.
285    #[must_use]
286    pub fn count(&self) -> usize {
287        self.sites.count()
288    }
289}
290
291impl Landing {
292    /// The site this landing is for.
293    #[must_use]
294    pub fn site(&self) -> &str {
295        self.site.as_str()
296    }
297
298    /// The invocation written there.
299    #[must_use]
300    pub const fn invocation(&self) -> &GeneratedTree {
301        &self.invocation
302    }
303}
304
305impl PublicationRecord {
306    /// Why neither of the lighter roads expresses this output.
307    #[must_use]
308    pub const fn ground(&self) -> PublicationGround {
309        self.ground
310    }
311
312    /// The planned member this artifact materializes.
313    #[must_use]
314    pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
315        self.unit
316    }
317
318    /// What the eventual staged bytes' digest must satisfy.
319    #[must_use]
320    pub const fn staged(&self) -> DigestContract {
321        self.staged
322    }
323
324    /// The stamp the artifact was rendered from, whole.
325    #[must_use]
326    pub const fn covered(&self) -> &Stamp {
327        &self.stamp
328    }
329
330    /// What the unit contains, row by row.
331    pub fn manifest(&self) -> impl Iterator<Item = &str> {
332        self.stamp.sites().iter().map(Site::name)
333    }
334}
335
336impl StampedPlan {
337    /// One reading, from the one road that checked the seat it states.
338    ///
339    /// `pub` only within the stamp home: the checks live in [`planned`](crate::stamp::planned), and this is how that road writes down what it proved.
340    pub(in crate::stamp) const fn read(
341        unit: Identity<identity::GeneratedUnit>,
342        staged: DigestContract,
343    ) -> Self {
344        Self { unit, staged }
345    }
346
347    /// The planned member's semantic key.
348    #[must_use]
349    pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
350        self.unit
351    }
352
353    /// What the eventual staged bytes' digest must satisfy.
354    #[must_use]
355    pub const fn staged(&self) -> DigestContract {
356        self.staged
357    }
358}
359
360impl PublishedStamp {
361    /// Render one published stamp over what the plan decided, what the caller declared, and why the lighter roads are insufficient.
362    ///
363    /// The order is the road: the definition first, then one invocation per covered site, then the record — and the artifact only after all three, so no half-rendered publication unit exists.
364    ///
365    /// # Errors
366    ///
367    /// Returns [`StampError::TokensUnbounded`] where the definition, one invocation, or the tree either is assembled into outgrows the declared token magnitude.
368    pub fn rendered(
369        planned: &StampedPlan,
370        stamp: &Stamp,
371        ground: PublicationGround,
372    ) -> Result<Self, StampError> {
373        let definition = GeneratedTree::assembled(render::definition(stamp)?)?;
374        let mut landings: Vec<Landing> = Vec::new();
375        for site in stamp.sites() {
376            let invocation = GeneratedTree::assembled(render::invocation(stamp, site)?)?;
377            landings.push(Landing {
378                site: site.name().to_owned(),
379                invocation,
380            });
381        }
382        Ok(Self {
383            definition,
384            landings,
385            record: PublicationRecord {
386                ground,
387                unit: planned.unit,
388                staged: planned.staged,
389                stamp: stamp.clone(),
390            },
391        })
392    }
393
394    /// The name the stamp is exported under, read out of the record.
395    #[must_use]
396    pub const fn name(&self) -> &StampName {
397        self.record.covered().name()
398    }
399
400    /// The definition a publication road lands as visible source.
401    #[must_use]
402    pub const fn definition(&self) -> &GeneratedTree {
403        &self.definition
404    }
405
406    /// Every covered site's landing, in the order the stamp declares them.
407    ///
408    /// # Bounds
409    ///
410    /// Exactly as many as the stamp declares sites, because the road that built them walked that stamp once.
411    #[must_use]
412    pub fn landings(&self) -> &[Landing] {
413        self.landings.as_slice()
414    }
415
416    /// How many landings this artifact carries; structurally at least one.
417    #[must_use]
418    pub fn count(&self) -> usize {
419        self.landings.len()
420    }
421
422    /// This side's record of the publication act.
423    pub const fn record(&self) -> &PublicationRecord {
424        &self.record
425    }
426}
427
428/// The seat namespace one pattern closes.
429///
430/// Two seats under one name bind one metavariable twice, which the consumer's compiler would report inside an expansion nobody wrote.
431fn seat_names_closed(parts: &[Part]) -> Result<(), StampError> {
432    let mut named: BTreeSet<&str> = BTreeSet::new();
433    for (position, part) in parts.iter().enumerate() {
434        let Part::Seat(seat) = part else {
435            continue;
436        };
437        if !named.insert(seat.name()) {
438            return Err(StampError::SeatNameDoubled {
439                at: counted(position),
440            });
441        }
442    }
443    Ok(())
444}
445
446/// The site namespace one stamp closes.
447///
448/// Two sites under one name are one manifest row written twice, and nothing downstream could tell which landing a row is about.
449fn site_names_closed(sites: &[Site]) -> Result<(), StampError> {
450    let mut named: BTreeSet<&str> = BTreeSet::new();
451    for (position, site) in sites.iter().enumerate() {
452        if !named.insert(site.name()) {
453            return Err(StampError::SiteNameDoubled {
454                at: counted(position),
455            });
456        }
457    }
458    Ok(())
459}
460
461/// Every site settled against the pattern it adopts: one argument per seat, and a reach only where the pattern writes one.
462fn sites_seated(pattern: &Pattern, sites: &[Site]) -> Result<(), StampError> {
463    let seats = counted(pattern.seat_count());
464    let reaches = pattern.reaches();
465    for (position, site) in sites.iter().enumerate() {
466        let supplied = counted(site.arguments().len());
467        if supplied != seats {
468            return Err(StampError::ArgumentsUnmatched {
469                at: counted(position),
470                seats,
471                supplied,
472            });
473        }
474        if !reaches && site.reach() != Visibility::Private {
475            return Err(StampError::ReachUnseated {
476                at: counted(position),
477            });
478        }
479    }
480    Ok(())
481}
482
483/// One count as a refusal carries it.
484fn counted(value: usize) -> u32 {
485    u32::try_from(value).unwrap_or(u32::MAX)
486}