Skip to main content

macroonz_compiler/token/generation/
compose.rs

1//! Composing the Rust a renderer writes, out of the tokens that spell it.
2//!
3//! A renderer states what it means — a path, a call, a binding, an attribute — and never assembles punctuation by hand.
4//! A path stated as segments cannot be mis-spaced and cannot lose a colon; a call stated as a path and its arguments cannot lose a parenthesis; and neither can be built out of a string somebody supplied.
5//!
6//! Every helper here composes and never bounds.
7//! The declared magnitude bites only where a group closes, so exactly the helpers that write a group return [`Overflow`] and the rest are total.
8
9use super::{GeneratedDelimiter, GeneratedToken};
10use crate::bounded::Overflow;
11
12/// Whether one spelling is a single Rust identifier a rendering is willing to write.
13///
14/// ASCII only, and `_` alone is refused because it is the wildcard pattern rather than a name.
15/// ONE alphabet, seated with the token home, for every spelling any home renders in identifier position — a path segment, an exported address, a stamped item's own name, a declared grammar's word.
16/// A second copy would agree with this one until one of them was edited, and the failure would surface in a consumer's build with no idea where the name came from; the homes that once each carried a copy now all read this seat.
17#[must_use]
18pub fn rendered_identifier(spelling: &str) -> bool {
19    let mut characters = spelling.chars();
20    let Some(head) = characters.next() else {
21        return false;
22    };
23    if !head.is_ascii_alphabetic() && head != '_' {
24        return false;
25    }
26    if spelling == "_" {
27        return false;
28    }
29    characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
30}
31
32/// Whether one spelling is a Rust keyword no rendered item can be named by.
33///
34/// The language's own roster — the strict and reserved keywords through edition 2024 — written down once beside the identifier alphabet, because it is the same law from the other side: an alphabet says which spellings CAN be a name, and this roster says which of those the language already took.
35/// A grammar that let a keyword through would refuse nowhere and hand the collision to the adopter's build, inside an expansion whose lints rustc has silenced.
36#[must_use]
37pub fn rust_keyword(spelling: &str) -> bool {
38    matches!(
39        spelling,
40        "abstract"
41            | "as"
42            | "async"
43            | "await"
44            | "become"
45            | "box"
46            | "break"
47            | "const"
48            | "continue"
49            | "crate"
50            | "do"
51            | "dyn"
52            | "else"
53            | "enum"
54            | "extern"
55            | "false"
56            | "final"
57            | "fn"
58            | "for"
59            | "gen"
60            | "if"
61            | "impl"
62            | "in"
63            | "let"
64            | "loop"
65            | "macro"
66            | "match"
67            | "mod"
68            | "move"
69            | "mut"
70            | "override"
71            | "priv"
72            | "pub"
73            | "ref"
74            | "return"
75            | "self"
76            | "Self"
77            | "static"
78            | "struct"
79            | "super"
80            | "trait"
81            | "true"
82            | "try"
83            | "type"
84            | "typeof"
85            | "unsafe"
86            | "unsized"
87            | "use"
88            | "virtual"
89            | "where"
90            | "while"
91            | "yield"
92    )
93}
94
95/// Whether one spelling can NAME a rendered item: a single identifier the language has not already taken.
96///
97/// The two seats above are the two halves of one law, and this is the law whole — the alphabet says which spellings can be a name, the keyword roster says which of those the language took, and an item name must clear both.
98/// The direct grammars keep reading the two halves separately, because an authored declaration deserves a refusal naming which half disagreed at which token; every constructor that mints a name or a path segment programmatically reads this one.
99#[must_use]
100pub fn rendered_name(spelling: &str) -> bool {
101    rendered_identifier(spelling) && !rust_keyword(spelling)
102}
103
104/// One delimited group.
105///
106/// # Errors
107///
108/// Returns [`Overflow`] where the group carries more tokens than the declared magnitude admits.
109pub fn group(
110    delimiter: GeneratedDelimiter,
111    tokens: Vec<GeneratedToken>,
112) -> Result<GeneratedToken, Overflow> {
113    GeneratedToken::group(delimiter, tokens)
114}
115
116/// One macro metavariable, as the two tokens that spell it.
117///
118/// The `$` is written joint, so the projection a person reads is `$name` rather than `$ name`.
119#[must_use]
120pub fn metavariable(name: &str) -> Vec<GeneratedToken> {
121    vec![GeneratedToken::joint('$'), GeneratedToken::word(name)]
122}
123
124/// The absolute path `::a::b::c`.
125#[must_use]
126pub fn absolute_path(segments: &[&str]) -> Vec<GeneratedToken> {
127    let mut tokens = Vec::new();
128    extend_path(&mut tokens, segments);
129    tokens
130}
131
132/// The path `root::a::b::c`, rooted at a crate the caller named.
133///
134/// The root is written as a plain word, so a caller that renamed its dependency is named the way it named itself.
135#[must_use]
136pub fn bound_path(root: &str, segments: &[&str]) -> Vec<GeneratedToken> {
137    let mut tokens = vec![GeneratedToken::word(root)];
138    extend_path(&mut tokens, segments);
139    tokens
140}
141
142/// The path `$binding::a::b::c`, rooted at a metavariable a macro shell will bind.
143#[must_use]
144pub fn twin_path(binding: &str, segments: &[&str]) -> Vec<GeneratedToken> {
145    let mut tokens = metavariable(binding);
146    extend_path(&mut tokens, segments);
147    tokens
148}
149
150/// The path `$root $(:: $segment)*::a::b`, rooted at the complete segmented path a macro matcher captured.
151///
152/// The repetition group has fixed arity, so its fit under the generated-token magnitude is settled at compile time.
153#[must_use]
154pub(crate) fn segmented_twin_path(
155    root_binding: &str,
156    segment_binding: &str,
157    segments: &[&str],
158) -> Vec<GeneratedToken> {
159    let mut tokens = metavariable(root_binding);
160    tokens.push(GeneratedToken::joint('$'));
161    tokens.push(GeneratedToken::fixed_group(
162        GeneratedDelimiter::Parenthesis,
163        [
164            GeneratedToken::joint(':'),
165            GeneratedToken::alone(':'),
166            GeneratedToken::joint('$'),
167            GeneratedToken::word(segment_binding),
168        ],
169    ));
170    tokens.push(GeneratedToken::alone('*'));
171    extend_path(&mut tokens, segments);
172    tokens
173}
174
175/// Write `::segment` for each segment onto a path being built.
176fn extend_path(tokens: &mut Vec<GeneratedToken>, segments: &[&str]) {
177    for segment in segments {
178        tokens.push(GeneratedToken::joint(':'));
179        tokens.push(GeneratedToken::alone(':'));
180        tokens.push(GeneratedToken::word(segment));
181    }
182}
183
184/// One call `path(arguments)`.
185///
186/// # Errors
187///
188/// Returns [`Overflow`] where the argument list outgrows the declared magnitude.
189pub fn call(
190    mut path: Vec<GeneratedToken>,
191    arguments: Vec<GeneratedToken>,
192) -> Result<Vec<GeneratedToken>, Overflow> {
193    path.push(group(GeneratedDelimiter::Parenthesis, arguments)?);
194    Ok(path)
195}
196
197/// One call `receiver.method(arguments)`.
198///
199/// # Errors
200///
201/// Returns [`Overflow`] where the argument list outgrows the declared magnitude.
202pub fn method_call(
203    mut receiver: Vec<GeneratedToken>,
204    method: &str,
205    arguments: Vec<GeneratedToken>,
206) -> Result<Vec<GeneratedToken>, Overflow> {
207    receiver.push(GeneratedToken::alone('.'));
208    receiver.push(GeneratedToken::word(method));
209    call(receiver, arguments)
210}
211
212/// One chain `receiver.first().second().third()`, every method taking no argument.
213///
214/// # Errors
215///
216/// Returns [`Overflow`] where a call in the chain outgrows the declared magnitude.
217pub fn method_chain(
218    mut receiver: Vec<GeneratedToken>,
219    methods: &[&str],
220) -> Result<Vec<GeneratedToken>, Overflow> {
221    for method in methods {
222        receiver = method_call(receiver, method, Vec::new())?;
223    }
224    Ok(receiver)
225}
226
227/// One statement `let name = expression;`.
228///
229/// A value a rendered block needs twice is bound once, which makes the agreement between its two readers structural rather than a comparison of two separately built values.
230#[must_use]
231pub fn bound_local(name: &str, expression: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
232    let mut tokens = vec![
233        GeneratedToken::word("let"),
234        GeneratedToken::word(name),
235        GeneratedToken::alone('='),
236    ];
237    tokens.extend(expression);
238    tokens.push(GeneratedToken::alone(';'));
239    tokens
240}
241
242/// The type `::core::result::Result<ok, error>`.
243#[must_use]
244pub fn result_type(ok: Vec<GeneratedToken>, error: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
245    let mut tokens = absolute_path(&["core", "result", "Result"]);
246    tokens.push(GeneratedToken::alone('<'));
247    tokens.extend(ok);
248    tokens.push(GeneratedToken::alone(','));
249    tokens.extend(error);
250    tokens.push(GeneratedToken::alone('>'));
251    tokens
252}
253
254/// One item `const name: kind = value;`.
255///
256/// The visibility is the caller's and is written before this.
257#[must_use]
258pub fn constant(
259    name: &str,
260    kind: Vec<GeneratedToken>,
261    value: Vec<GeneratedToken>,
262) -> Vec<GeneratedToken> {
263    let mut tokens = vec![GeneratedToken::word("const"), GeneratedToken::word(name)];
264    tokens.push(GeneratedToken::alone(':'));
265    tokens.extend(kind);
266    tokens.push(GeneratedToken::alone('='));
267    tokens.extend(value);
268    tokens.push(GeneratedToken::alone(';'));
269    tokens
270}
271
272/// One item `fn name(parameters) -> result { body }`.
273///
274/// The visibility is the caller's and is written before this, so one helper spells a private function, a `pub` one, and a `pub(crate)` one.
275///
276/// # Errors
277///
278/// Returns [`Overflow`] where the parameter list or the body outgrows the declared magnitude.
279pub fn function(
280    name: &str,
281    parameters: Vec<GeneratedToken>,
282    result: Vec<GeneratedToken>,
283    body: Vec<GeneratedToken>,
284) -> Result<Vec<GeneratedToken>, Overflow> {
285    let mut tokens = vec![
286        GeneratedToken::word("fn"),
287        GeneratedToken::word(name),
288        group(GeneratedDelimiter::Parenthesis, parameters)?,
289        GeneratedToken::joint('-'),
290        GeneratedToken::alone('>'),
291    ];
292    tokens.extend(result);
293    tokens.push(group(GeneratedDelimiter::Brace, body)?);
294    Ok(tokens)
295}
296
297/// One comparison `left == right`.
298#[must_use]
299pub fn equality(mut left: Vec<GeneratedToken>, right: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
300    left.push(GeneratedToken::joint('='));
301    left.push(GeneratedToken::alone('='));
302    left.extend(right);
303    left
304}
305
306/// Every comparison joined by `&&`.
307#[must_use]
308pub fn and_all(comparisons: Vec<Vec<GeneratedToken>>) -> Vec<GeneratedToken> {
309    let mut tokens = Vec::new();
310    for (position, comparison) in comparisons.into_iter().enumerate() {
311        if position > 0 {
312            tokens.push(GeneratedToken::joint('&'));
313            tokens.push(GeneratedToken::alone('&'));
314        }
315        tokens.extend(comparison);
316    }
317    tokens
318}
319
320/// Two token runs separated by a comma.
321#[must_use]
322pub fn comma(mut left: Vec<GeneratedToken>, right: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
323    left.push(GeneratedToken::alone(','));
324    left.extend(right);
325    left
326}
327
328/// Every token run separated by a comma, with no trailing one.
329#[must_use]
330pub fn comma_many(parts: Vec<Vec<GeneratedToken>>) -> Vec<GeneratedToken> {
331    let mut tokens = Vec::new();
332    for (position, part) in parts.into_iter().enumerate() {
333        if position > 0 {
334            tokens.push(GeneratedToken::alone(','));
335        }
336        tokens.extend(part);
337    }
338    tokens
339}
340
341/// Two spellings as the two comma-separated text literals a two-argument parser takes.
342#[must_use]
343pub fn text_pair(first: &str, second: &str) -> Vec<GeneratedToken> {
344    vec![
345        GeneratedToken::text(first),
346        GeneratedToken::alone(','),
347        GeneratedToken::text(second),
348    ]
349}
350
351/// One attribute `#[body]`.
352///
353/// # Errors
354///
355/// Returns [`Overflow`] where the body outgrows the declared magnitude.
356pub fn attribute(body: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, Overflow> {
357    Ok(vec![
358        GeneratedToken::alone('#'),
359        group(GeneratedDelimiter::Bracket, body)?,
360    ])
361}
362
363/// One documentation attribute over one sentence.
364///
365/// # Errors
366///
367/// Returns [`Overflow`] where the attribute outgrows the declared magnitude.
368pub fn documentation(sentence: &str) -> Result<Vec<GeneratedToken>, Overflow> {
369    attribute(vec![
370        GeneratedToken::word("doc"),
371        GeneratedToken::alone('='),
372        GeneratedToken::text(sentence),
373    ])
374}
375
376/// The `::std::vec![…]` a roster-taking constructor is handed.
377///
378/// # Errors
379///
380/// Returns [`Overflow`] where the roster outgrows the declared magnitude.
381pub fn roster(items: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, Overflow> {
382    let mut tokens = absolute_path(&["std", "vec"]);
383    tokens.push(GeneratedToken::alone('!'));
384    tokens.push(group(GeneratedDelimiter::Bracket, items)?);
385    Ok(tokens)
386}