Skip to main content

macroonz_compiler/stamp/
render.rs

1//! The token half: the definition a pattern is written into, and the one invocation each site is migrated to.
2//!
3//! # Tokens, not text
4//!
5//! Every path is spelled as segments, every brace is a group, every sentence is a typed literal whose quoting the tree owns, and nothing here composes Rust source.
6//! The Rust a person reads is the tree's own projection, which is a projection of what is emitted rather than the thing itself.
7//!
8//! # One declaration, both halves
9//!
10//! A matcher and an invocation are two walks over one declared shape.
11//! A literal part is the same tokens on both walks, a seat is a metavariable on one and the site's own material on the other, and the reach coordinate is the only place where the two walks write different things — which is the one thing about a reach that cannot be got right by copying.
12//!
13//! # What is emitted calls nothing
14//!
15//! The definition's body is the caller's token material, and this compiler is named nowhere in it.
16//! The only tokens written from here are the grammar around that body: the arms, the two bracketed reaches, and the refusal.
17
18use super::{
19    Fragment, Part, Pattern, Seat, Seating, Site, Stamp, StampError, TransportedReach, Visibility,
20};
21use crate::bounded::Overflow;
22use crate::token::{self, GeneratedDelimiter, GeneratedToken};
23
24/// The internal arm a front arm forwards a declaration through.
25///
26/// Reserved: a shape whose first part is this word would be read as a forwarded declaration rather than as a site's own.
27pub const TRANSCRIBE_ARM: &str = "transcribe";
28
29/// The metavariable the site's own reach travels in, for a body to write at the site's coordinate.
30pub const DECLARED_REACH: &str = "declared_reach";
31
32/// The metavariable the transported reach travels in, for a body to write one module in.
33pub const TRANSPORTED_REACH: &str = "transported_reach";
34
35/// The sentence the refusing arm answers an opaque reach with.
36///
37/// It names the front door and no crate path: a consumer may reach a stamp under a name this side never learns, and a sentence spelling one would send a reader to a path their own crate does not have.
38pub const OPAQUE_REACH_REFUSAL: &str = "this stamp requires visibility tokens at its front door; \
39     an opaque forwarded `vis` fragment cannot be transported one module deeper, and a reach \
40     guessed in its place would publish an item nobody declared visible.";
41
42/// The metavariable the refusing arm catches an opaque reach in.
43const OPAQUE_REACH: &str = "opaque_reach";
44
45/// The fragment a visibility is matched as.
46///
47/// The one kind [`Fragment`] does not carry: only the two internal seats match one, and each of them is handed literal tokens by the arm that forwarded it.
48const VIS_FRAGMENT: &str = "vis";
49
50/// The published definition: the exported `macro_rules!` a publication road lands as visible source.
51///
52/// # Ordering
53///
54/// The rules are written in one order and it is the order that makes the grammar unambiguous: every front arm, then the internal arm, then the arm that refuses an opaque reach.
55/// A front arm naming no visibility token has to precede that last one, whose `vis` fragment also matches nothing, or a site writing no reach would reach the refusal instead of the body.
56///
57/// A pattern that gives a reach no coordinate is one rule and nothing else: there is no reach to transport, so there is nothing to forward through and nothing to refuse.
58///
59/// # Errors
60///
61/// Returns [`StampError::TokensUnbounded`] where the definition outgrows the declared token magnitude.
62pub fn definition(stamp: &Stamp) -> Result<Vec<GeneratedToken>, StampError> {
63    let pattern = stamp.pattern();
64    let mut rules: Vec<GeneratedToken> = Vec::new();
65    if pattern.reaches() {
66        for reach in Visibility::ALL {
67            rules.extend(front_arm(stamp, *reach)?);
68        }
69        rules.extend(transcribe_arm(pattern)?);
70        rules.extend(refusing_arm(pattern)?);
71    } else {
72        let expansion = pattern.body().tokens().to_vec();
73        rules.extend(rule(matched(pattern, &[])?, expansion)?);
74    }
75
76    let mut tokens = token::documentation(pattern.note())?;
77    tokens.extend(token::attribute(vec![GeneratedToken::word(
78        "macro_export",
79    )])?);
80    tokens.push(GeneratedToken::word("macro_rules"));
81    tokens.push(GeneratedToken::alone('!'));
82    tokens.push(GeneratedToken::word(stamp.name().spelling()));
83    tokens.push(token::group(GeneratedDelimiter::Brace, rules)?);
84    Ok(tokens)
85}
86
87/// The one invocation a covered site is migrated to: the stamp, reached the way that site reaches it, over the shape it was declared in.
88///
89/// The site's arguments are as many as the shape has seats, settled where the site met its pattern, so the walk fills every seat and runs out at neither end.
90///
91/// # Errors
92///
93/// Returns [`StampError::TokensUnbounded`] where the invocation outgrows the declared token magnitude.
94pub fn invocation(stamp: &Stamp, site: &Site) -> Result<Vec<GeneratedToken>, StampError> {
95    let mut body: Vec<GeneratedToken> = Vec::new();
96    let mut supplied = site.arguments().iter();
97    for part in stamp.pattern().parts() {
98        let written = match part {
99            Part::Literal(material) => material.tokens().to_vec(),
100            Part::Seat(_) => supplied
101                .next()
102                .map_or_else(Vec::new, |argument| argument.tokens().to_vec()),
103            Part::Reach => declared_reach(site.reach())?,
104        };
105        body.extend(written);
106    }
107
108    let mut tokens = stamp_path(site, stamp.name().spelling());
109    tokens.push(GeneratedToken::alone('!'));
110    tokens.push(token::group(GeneratedDelimiter::Brace, body)?);
111    Ok(tokens)
112}
113
114/// How one seat is written back: the tokens a body writes where that seat's material belongs.
115///
116/// # Errors
117///
118/// Returns [`StampError::TokensUnbounded`] where the tokens outgrow the declared token magnitude.
119pub fn forwarded(seat: &Seat) -> Result<Vec<GeneratedToken>, StampError> {
120    match seat.seating() {
121        Seating::One(_) => Ok(token::metavariable(seat.name())),
122        Seating::Many(_) => repeated(token::metavariable(seat.name()), Some(',')),
123        Seating::Attributes => {
124            let inner = token::group(
125                GeneratedDelimiter::Bracket,
126                token::metavariable(seat.name()),
127            )?;
128            repeated(vec![GeneratedToken::alone('#'), inner], None)
129        }
130    }
131}
132
133/// The literal tokens one declared reach is spelled with, at the coordinate the site wrote it.
134///
135/// # Errors
136///
137/// Returns [`StampError::TokensUnbounded`] where the tokens outgrow the declared token magnitude.
138pub fn declared_reach(reach: Visibility) -> Result<Vec<GeneratedToken>, StampError> {
139    Ok(declared_reach_tokens(reach)?)
140}
141
142/// The declared visibility tokens shared by compiler renderers.
143pub(crate) fn declared_reach_tokens(reach: Visibility) -> Result<Vec<GeneratedToken>, Overflow> {
144    match reach {
145        Visibility::Private => Ok(Vec::new()),
146        Visibility::Module => scoped(vec![GeneratedToken::word("self")]),
147        Visibility::Parent => scoped(vec![GeneratedToken::word("super")]),
148        Visibility::Crate => scoped(vec![GeneratedToken::word("crate")]),
149        Visibility::Public => Ok(vec![GeneratedToken::word("pub")]),
150    }
151}
152
153/// The literal tokens one transported reach is spelled with, at the coordinate one module deeper.
154///
155/// # Errors
156///
157/// Returns [`StampError::TokensUnbounded`] where the tokens outgrow the declared token magnitude.
158pub fn transported_reach(reach: TransportedReach) -> Result<Vec<GeneratedToken>, StampError> {
159    Ok(match reach {
160        TransportedReach::Enclosing => scoped(vec![GeneratedToken::word("super")])?,
161        TransportedReach::Ancestor => scoped(vec![
162            GeneratedToken::word("in"),
163            GeneratedToken::word("super"),
164            GeneratedToken::joint(':'),
165            GeneratedToken::alone(':'),
166            GeneratedToken::word("super"),
167        ])?,
168        TransportedReach::Crate => scoped(vec![GeneratedToken::word("crate")])?,
169        TransportedReach::Public => vec![GeneratedToken::word("pub")],
170    })
171}
172
173/// One front arm: the reach the site writes literally, and the forward into the internal arm carrying both reaches as literal tokens.
174///
175/// The two reaches are rendered here rather than captured, which is what makes the transport exact.
176fn front_arm(stamp: &Stamp, reach: Visibility) -> Result<Vec<GeneratedToken>, StampError> {
177    let pattern = stamp.pattern();
178    let declared = declared_reach(reach)?;
179    let matcher = matched(pattern, &declared)?;
180
181    let mut carried = vec![
182        GeneratedToken::joint('@'),
183        GeneratedToken::word(TRANSCRIBE_ARM),
184        bracketed_reach(transported_reach(reach.transported())?)?,
185        bracketed_reach(declared)?,
186    ];
187    carried.extend(restated(pattern)?);
188
189    let mut expansion = token::twin_path("crate", &[stamp.name().spelling()]);
190    expansion.push(GeneratedToken::alone('!'));
191    expansion.push(token::group(GeneratedDelimiter::Brace, carried)?);
192    rule(matcher, expansion)
193}
194
195/// The internal arm: the two reaches as bracketed visibilities, the declared shape behind them, and the caller's body as the whole expansion.
196fn transcribe_arm(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
197    let mut matcher = vec![
198        GeneratedToken::joint('@'),
199        GeneratedToken::word(TRANSCRIBE_ARM),
200        bracketed_reach(fragment_of(TRANSPORTED_REACH, VIS_FRAGMENT))?,
201        bracketed_reach(fragment_of(DECLARED_REACH, VIS_FRAGMENT))?,
202    ];
203    matcher.extend(matched(pattern, &[])?);
204    rule(matcher, pattern.body().tokens().to_vec())
205}
206
207/// The arm that refuses an opaque forwarded reach, in the consumer's own compile-time vocabulary.
208fn refusing_arm(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
209    let opaque = fragment_of(OPAQUE_REACH, VIS_FRAGMENT);
210    let matcher = matched(pattern, &opaque)?;
211    let mut expansion = token::absolute_path(&["core", "compile_error"]);
212    expansion.push(GeneratedToken::alone('!'));
213    expansion.push(token::group(
214        GeneratedDelimiter::Parenthesis,
215        vec![GeneratedToken::text(OPAQUE_REACH_REFUSAL)],
216    )?);
217    rule(matcher, expansion)
218}
219
220/// The declared shape as a matcher reads it, with the reach coordinate spelled by whatever arm is asking.
221fn matched(pattern: &Pattern, reach: &[GeneratedToken]) -> Result<Vec<GeneratedToken>, StampError> {
222    let mut tokens: Vec<GeneratedToken> = Vec::new();
223    for part in pattern.parts() {
224        let written = match part {
225            Part::Literal(material) => material.tokens().to_vec(),
226            Part::Seat(seat) => seat_matched(seat)?,
227            Part::Reach => reach.to_vec(),
228        };
229        tokens.extend(written);
230    }
231    Ok(tokens)
232}
233
234/// The declared shape as an expansion writes it back to the internal arm.
235///
236/// The reach is not part of it: it rides ahead in the two brackets, so the shape reaching the internal arm is one shape whichever front arm forwarded it.
237fn restated(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
238    let mut tokens: Vec<GeneratedToken> = Vec::new();
239    for part in pattern.parts() {
240        let written = match part {
241            Part::Literal(material) => material.tokens().to_vec(),
242            Part::Seat(seat) => forwarded(seat)?,
243            Part::Reach => Vec::new(),
244        };
245        tokens.extend(written);
246    }
247    Ok(tokens)
248}
249
250/// How one seat is matched.
251fn seat_matched(seat: &Seat) -> Result<Vec<GeneratedToken>, StampError> {
252    match seat.seating() {
253        Seating::One(fragment) => Ok(fragment_of(seat.name(), fragment.name())),
254        Seating::Many(fragment) => repeated(fragment_of(seat.name(), fragment.name()), Some(',')),
255        Seating::Attributes => {
256            let inner = token::group(
257                GeneratedDelimiter::Bracket,
258                fragment_of(seat.name(), Fragment::Attribute.name()),
259            )?;
260            repeated(vec![GeneratedToken::alone('#'), inner], None)
261        }
262    }
263}
264
265/// One matcher fragment: the metavariable and the kind it is matched as.
266fn fragment_of(name: &str, kind: &str) -> Vec<GeneratedToken> {
267    let mut tokens = token::metavariable(name);
268    tokens.push(GeneratedToken::alone(':'));
269    tokens.push(GeneratedToken::word(kind));
270    tokens
271}
272
273/// One internal visibility seat, terminated so the private empty `vis` can bind lawfully.
274///
275/// The comma is a matcher follow token rather than caller syntax: without it, the empty declared reach reaches the closing bracket before the `vis` fragment can bind.
276fn bracketed_reach(mut reach: Vec<GeneratedToken>) -> Result<GeneratedToken, StampError> {
277    reach.push(GeneratedToken::alone(','));
278    Ok(token::group(GeneratedDelimiter::Bracket, reach)?)
279}
280
281/// One repetition over the tokens inside it, separated where a separator was stated.
282fn repeated(
283    inner: Vec<GeneratedToken>,
284    separator: Option<char>,
285) -> Result<Vec<GeneratedToken>, StampError> {
286    let mut tokens = vec![
287        GeneratedToken::joint('$'),
288        token::group(GeneratedDelimiter::Parenthesis, inner)?,
289    ];
290    if let Some(mark) = separator {
291        tokens.push(GeneratedToken::alone(mark));
292    }
293    tokens.push(GeneratedToken::alone('*'));
294    Ok(tokens)
295}
296
297/// One scoped visibility `pub(inside)`.
298fn scoped(inside: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, Overflow> {
299    Ok(vec![
300        GeneratedToken::word("pub"),
301        token::group(GeneratedDelimiter::Parenthesis, inside)?,
302    ])
303}
304
305/// One rule: a matcher, the arrow, the expansion, and the separator.
306fn rule(
307    matcher: Vec<GeneratedToken>,
308    expansion: Vec<GeneratedToken>,
309) -> Result<Vec<GeneratedToken>, StampError> {
310    Ok(vec![
311        token::group(GeneratedDelimiter::Parenthesis, matcher)?,
312        GeneratedToken::joint('='),
313        GeneratedToken::alone('>'),
314        token::group(GeneratedDelimiter::Brace, expansion)?,
315        GeneratedToken::alone(';'),
316    ])
317}
318
319/// The path one site invokes its stamp by: the root it named, then the stamp's own spelling.
320fn stamp_path(site: &Site, name: &str) -> Vec<GeneratedToken> {
321    let mut tokens: Vec<GeneratedToken> = Vec::new();
322    for segment in site.root().segments() {
323        if !tokens.is_empty() {
324            tokens.push(GeneratedToken::joint(':'));
325            tokens.push(GeneratedToken::alone(':'));
326        }
327        tokens.push(GeneratedToken::word(segment.as_str()));
328    }
329    tokens.push(GeneratedToken::joint(':'));
330    tokens.push(GeneratedToken::alone(':'));
331    tokens.push(GeneratedToken::word(name));
332    tokens
333}