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