macroonz-compiler 0.2.0

Deterministic Rust code generation for procedural macros: plan, render, close, explain, and bind one sealed expansion from declared input.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
//! The stamp home's invariant nucleus: every road that reaches a private field, and the one road that composes a published artifact.
//!
//! Declared inside `types.rs` as its own child, which is what makes this home's claims structural rather than remembered.
//! 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.
//! 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.
//! And an artifact is composed here, so there is no half-rendered publication unit for a reader to mistake for a whole one.

use super::super::render;
use super::{
    Landing, PART_LIMIT, PATH_SEGMENT_LIMIT, Part, Pattern, PublicationGround, PublicationRecord,
    PublishedStamp, SITE_LIMIT, Seat, Seating, Site, SiteRoot, Stamp, StampError, StampName,
    StampedPlan, Visibility,
};
use crate::bounded::{Bounded, NonEmpty, NonEmptyError, first_duplicate_position};
use crate::identity::{self, Identity};
use crate::plan::DigestContract;
use crate::token::{GeneratedTree, rendered_name};

impl Seat {
    /// Declare one metavariable seat.
    ///
    /// # Errors
    ///
    /// 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.
    pub fn declared(name: &str, seating: Seating) -> Result<Self, StampError> {
        if !rendered_name(name) {
            return Err(StampError::NotAnIdentifier);
        }
        Ok(Self {
            name: name.to_owned(),
            seating,
        })
    }

    /// The name material travels under.
    #[must_use]
    pub fn name(&self) -> &str {
        self.name.as_str()
    }

    /// The shape it travels in.
    #[must_use]
    pub const fn seating(&self) -> Seating {
        self.seating
    }
}

impl Pattern {
    /// Declare one pattern: the sentence its definition is documented with, the shape it is invoked in, and the body that shape expands into.
    ///
    /// # Errors
    ///
    /// 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.
    ///
    /// 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.
    pub fn declared(note: &str, parts: Vec<Part>, body: GeneratedTree) -> Result<Self, StampError> {
        seat_names_closed(&parts)?;
        let admitted: NonEmpty<Part, PART_LIMIT> =
            NonEmpty::new(parts).map_err(|refusal| match refusal {
                NonEmptyError::Empty(_) => StampError::PatternEmpty,
                NonEmptyError::Overflow(overflow) => StampError::PatternUnbounded { overflow },
            })?;
        Ok(Self {
            note: note.to_owned(),
            parts: admitted,
            body,
        })
    }

    /// The sentence the definition is documented with.
    #[must_use]
    pub fn note(&self) -> &str {
        self.note.as_str()
    }

    /// The declared shape, in the order it is written; structurally at least one part.
    ///
    /// # Ordering
    ///
    /// This order is meaning: a matcher and every invocation are walks over it, so the same parts stated in another order are another grammar.
    #[must_use]
    pub fn parts(&self) -> &NonEmpty<Part, PART_LIMIT> {
        &self.parts
    }

    /// The body the shape expands into.
    #[must_use]
    pub const fn body(&self) -> &GeneratedTree {
        &self.body
    }

    /// The seats of the shape, in the order a site supplies arguments for them.
    pub fn seats(&self) -> impl Iterator<Item = &Seat> {
        self.parts.iter().filter_map(|part| match part {
            Part::Seat(seat) => Some(seat),
            Part::Literal(_) | Part::Reach => None,
        })
    }

    /// How many seats the shape declares.
    #[must_use]
    pub fn seat_count(&self) -> usize {
        self.seats().count()
    }

    /// Whether the shape gives a site's visibility a coordinate.
    #[must_use]
    pub fn reaches(&self) -> bool {
        self.parts.iter().any(|part| match part {
            Part::Reach => true,
            Part::Literal(_) | Part::Seat(_) => false,
        })
    }
}

impl StampName {
    /// The name one published stamp is exported under.
    ///
    /// # Errors
    ///
    /// Returns [`StampError::NotAnIdentifier`] where the spelling cannot name an exported item — not one Rust identifier, or a keyword the language already took.
    pub fn declared(spelling: &str) -> Result<Self, StampError> {
        if !rendered_name(spelling) {
            return Err(StampError::NotAnIdentifier);
        }
        Ok(Self {
            spelling: spelling.to_owned(),
        })
    }

    /// The exported spelling a site invokes this stamp by.
    #[must_use]
    pub fn spelling(&self) -> &str {
        self.spelling.as_str()
    }
}

impl SiteRoot {
    /// The path one site reaches its stamp by, parsed from the segments the caller stated.
    ///
    /// # Errors
    ///
    /// 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.
    ///
    /// The checks are in that order, so exactly one cause is true of any refused root.
    /// 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.
    pub fn spelled(segments: Vec<String>) -> Result<Self, StampError> {
        let names = match segments.split_first() {
            Some((root, rest)) if root.as_str() == "crate" || root.as_str() == "self" => rest,
            Some(_) | None => {
                let qualifiers = segments
                    .iter()
                    .take_while(|segment| segment.as_str() == "super")
                    .count();
                segments.get(qualifiers..).unwrap_or(&[])
            }
        };
        for segment in names {
            if !rendered_name(segment.as_str()) {
                return Err(StampError::NotAnIdentifier);
            }
        }
        let admitted: NonEmpty<String, PATH_SEGMENT_LIMIT> =
            NonEmpty::new(segments).map_err(|refusal| match refusal {
                NonEmptyError::Empty(_) => StampError::PathEmpty,
                NonEmptyError::Overflow(overflow) => StampError::PathUnbounded { overflow },
            })?;
        Ok(Self { segments: admitted })
    }

    /// The segments, in the order they were stated; structurally at least one.
    #[must_use]
    pub fn segments(&self) -> &NonEmpty<String, PATH_SEGMENT_LIMIT> {
        &self.segments
    }

    /// How many segments the root carries; structurally at least one.
    #[must_use]
    pub fn count(&self) -> usize {
        self.segments.count()
    }
}

impl Site {
    /// Declare one site that adopts a stamp.
    ///
    /// 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.
    ///
    /// # Errors
    ///
    /// Returns [`StampError::ArgumentsUnbounded`] where the arguments outgrow the declared magnitude.
    /// Whether they are the RIGHT arguments is settled where the site meets its pattern, in [`Stamp::declared`].
    pub fn declared(
        name: &str,
        root: SiteRoot,
        reach: Visibility,
        arguments: Vec<GeneratedTree>,
    ) -> Result<Self, StampError> {
        let admitted: Bounded<GeneratedTree, PART_LIMIT> = Bounded::new(arguments)
            .map_err(|overflow| StampError::ArgumentsUnbounded { overflow })?;
        Ok(Self {
            name: name.to_owned(),
            root,
            reach,
            arguments: admitted,
        })
    }

    /// What the manifest calls this landing.
    #[must_use]
    pub fn name(&self) -> &str {
        self.name.as_str()
    }

    /// The path this site reaches its stamp by.
    #[must_use]
    pub const fn root(&self) -> &SiteRoot {
        &self.root
    }

    /// The reach this site writes.
    #[must_use]
    pub const fn reach(&self) -> Visibility {
        self.reach
    }

    /// The material this site supplies, one argument per declared seat, in seat order.
    #[must_use]
    pub fn arguments(&self) -> &[GeneratedTree] {
        self.arguments.as_slice()
    }
}

impl Stamp {
    /// Declare the complete payload one published stamp is rendered from.
    ///
    /// # Errors
    ///
    /// 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.
    ///
    /// The namespace is closed first, then each site is settled against the pattern in the order the sites were stated, and the magnitude last.
    pub fn declared(
        name: StampName,
        pattern: Pattern,
        sites: Vec<Site>,
    ) -> Result<Self, StampError> {
        site_names_closed(&sites)?;
        sites_seated(&pattern, &sites)?;
        let admitted: NonEmpty<Site, SITE_LIMIT> =
            NonEmpty::new(sites).map_err(|refusal| match refusal {
                NonEmptyError::Empty(_) => StampError::SitesAbsent,
                NonEmptyError::Overflow(overflow) => StampError::SitesUnbounded { overflow },
            })?;
        Ok(Self {
            name,
            pattern,
            sites: admitted,
        })
    }

    /// The name this stamp is exported under.
    #[must_use]
    pub const fn name(&self) -> &StampName {
        &self.name
    }

    /// The pattern this stamp stamps.
    #[must_use]
    pub const fn pattern(&self) -> &Pattern {
        &self.pattern
    }

    /// The sites covered, in the order they were declared; structurally at least one.
    ///
    /// # Ordering
    ///
    /// This order is meaning for a migration: one invocation is rendered per site in the order this yields.
    #[must_use]
    pub fn sites(&self) -> &NonEmpty<Site, SITE_LIMIT> {
        &self.sites
    }

    /// How many sites this stamp covers; structurally at least one.
    #[must_use]
    pub fn count(&self) -> usize {
        self.sites.count()
    }
}

impl Landing {
    /// The site this landing is for.
    #[must_use]
    pub fn site(&self) -> &str {
        self.site.as_str()
    }

    /// The invocation written there.
    #[must_use]
    pub const fn invocation(&self) -> &GeneratedTree {
        &self.invocation
    }
}

impl PublicationRecord {
    /// Why neither of the lighter roads expresses this output.
    #[must_use]
    pub const fn ground(&self) -> PublicationGround {
        self.ground
    }

    /// The planned member this artifact materializes.
    #[must_use]
    pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
        self.unit
    }

    /// What the eventual staged bytes' digest must satisfy.
    #[must_use]
    pub const fn staged(&self) -> DigestContract {
        self.staged
    }

    /// The stamp the artifact was rendered from, whole.
    #[must_use]
    pub const fn covered(&self) -> &Stamp {
        &self.stamp
    }

    /// What the unit contains, row by row.
    pub fn manifest(&self) -> impl Iterator<Item = &str> {
        self.stamp.sites().iter().map(Site::name)
    }
}

impl StampedPlan {
    /// One reading, from the one road that checked the seat it states.
    ///
    /// `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.
    pub(in crate::stamp) const fn read(
        unit: Identity<identity::GeneratedUnit>,
        staged: DigestContract,
    ) -> Self {
        Self { unit, staged }
    }

    /// The planned member's semantic key.
    #[must_use]
    pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
        self.unit
    }

    /// What the eventual staged bytes' digest must satisfy.
    #[must_use]
    pub const fn staged(&self) -> DigestContract {
        self.staged
    }
}

impl PublishedStamp {
    /// Render one published stamp over what the plan decided, what the caller declared, and why the lighter roads are insufficient.
    ///
    /// 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.
    ///
    /// # Errors
    ///
    /// Returns [`StampError::TokensUnbounded`] where the definition, one invocation, or the tree either is assembled into outgrows the declared token magnitude.
    pub fn rendered(
        planned: &StampedPlan,
        stamp: &Stamp,
        ground: PublicationGround,
    ) -> Result<Self, StampError> {
        let definition = GeneratedTree::assembled(render::definition(stamp)?)?;
        let mut landings: Vec<Landing> = Vec::new();
        for site in stamp.sites() {
            let invocation = GeneratedTree::assembled(render::invocation(stamp, site)?)?;
            landings.push(Landing {
                site: site.name().to_owned(),
                invocation,
            });
        }
        Ok(Self {
            definition,
            landings,
            record: PublicationRecord {
                ground,
                unit: planned.unit,
                staged: planned.staged,
                stamp: stamp.clone(),
            },
        })
    }

    /// The name the stamp is exported under, read out of the record.
    #[must_use]
    pub const fn name(&self) -> &StampName {
        self.record.covered().name()
    }

    /// The definition a publication road lands as visible source.
    #[must_use]
    pub const fn definition(&self) -> &GeneratedTree {
        &self.definition
    }

    /// Every covered site's landing, in the order the stamp declares them.
    ///
    /// # Bounds
    ///
    /// Exactly as many as the stamp declares sites, because the road that built them walked that stamp once.
    #[must_use]
    pub fn landings(&self) -> &[Landing] {
        self.landings.as_slice()
    }

    /// How many landings this artifact carries; structurally at least one.
    #[must_use]
    pub fn count(&self) -> usize {
        self.landings.len()
    }

    /// This side's record of the publication act.
    pub const fn record(&self) -> &PublicationRecord {
        &self.record
    }
}

/// The seat namespace one pattern closes.
///
/// Two seats under one name bind one metavariable twice, which the consumer's compiler would report inside an expansion nobody wrote.
fn seat_names_closed(parts: &[Part]) -> Result<(), StampError> {
    let doubled = first_duplicate_position(parts, |left, right| match (left, right) {
        (Part::Seat(left), Part::Seat(right)) => left.name() == right.name(),
        (Part::Literal(_) | Part::Reach, _) | (_, Part::Literal(_) | Part::Reach) => false,
    });
    if let Some(position) = doubled {
        return Err(StampError::SeatNameDoubled {
            at: counted(position),
        });
    }
    Ok(())
}

/// The site namespace one stamp closes.
///
/// Two sites under one name are one manifest row written twice, and nothing downstream could tell which landing a row is about.
fn site_names_closed(sites: &[Site]) -> Result<(), StampError> {
    if let Some(position) =
        first_duplicate_position(sites, |left, right| left.name() == right.name())
    {
        return Err(StampError::SiteNameDoubled {
            at: counted(position),
        });
    }
    Ok(())
}

/// Every site settled against the pattern it adopts: one argument per seat, and a reach only where the pattern writes one.
fn sites_seated(pattern: &Pattern, sites: &[Site]) -> Result<(), StampError> {
    let seats = counted(pattern.seat_count());
    let reaches = pattern.reaches();
    for (position, site) in sites.iter().enumerate() {
        let supplied = counted(site.arguments().len());
        if supplied != seats {
            return Err(StampError::ArgumentsUnmatched {
                at: counted(position),
                seats,
                supplied,
            });
        }
        if !reaches && site.reach() != Visibility::Private {
            return Err(StampError::ReachUnseated {
                at: counted(position),
            });
        }
    }
    Ok(())
}

/// One count as a refusal carries it.
fn counted(value: usize) -> u32 {
    u32::try_from(value).unwrap_or(u32::MAX)
}