Skip to main content

macroonz_compiler/token/generation/
preserve.rs

1//! Projecting a captured fragment into the generated-token vocabulary without a source-string round trip.
2//!
3//! The capture remains the identity-bearing authored reading.
4//! This projection exists only where a renderer must repeat exact caller-owned Rust inside generated output.
5
6use super::{
7    FragmentGenerationIssue, FragmentGenerationRefusal, GeneratedDelimiter, GeneratedLiteral,
8    GeneratedToken, GeneratedTree,
9};
10use crate::token::{CapturedDelimiter, CapturedFragment, CapturedPayload, CapturedTokenTree};
11
12impl CapturedFragment<'_> {
13    /// Project this exact captured fragment into one generated tree.
14    ///
15    /// Every token is converted structurally.
16    /// No inspected source string is built or parsed, and the captured fragment remains available with its original span handles.
17    ///
18    /// # Errors
19    ///
20    /// Returns the exact captured span where an admitted generated literal or generated-token magnitude refuses.
21    pub fn generated(self) -> Result<GeneratedTree, FragmentGenerationRefusal> {
22        preserved_tree(self.tokens())
23    }
24}
25
26/// Preserve one already bounded captured-token slice and its producer spans.
27pub(crate) fn preserved_tree(
28    tokens: &[CapturedTokenTree],
29) -> Result<GeneratedTree, FragmentGenerationRefusal> {
30    let mut source_spans = Vec::new();
31    let generated = tokens
32        .iter()
33        .map(|token| preserved_token(token, &mut source_spans))
34        .collect::<Result<Vec<_>, _>>()?;
35    GeneratedTree::preserved(generated, source_spans).map_err(|_| FragmentGenerationRefusal {
36        issue: FragmentGenerationIssue::Unbounded,
37        at: tokens.first().map(CapturedTokenTree::span),
38    })
39}
40
41impl FragmentGenerationRefusal {
42    /// The exact conversion issue this projection established.
43    pub const fn issue(self) -> FragmentGenerationIssue {
44        self.issue
45    }
46
47    /// The exact captured producer span available at the refusal site.
48    #[must_use]
49    pub const fn token(self) -> Option<crate::token::SpanHandle> {
50        self.at
51    }
52}
53
54/// Preserve one captured token and every nested token below it.
55fn preserved_token(
56    token: &CapturedTokenTree,
57    source_spans: &mut Vec<Option<crate::token::SpanHandle>>,
58) -> Result<GeneratedToken, FragmentGenerationRefusal> {
59    source_spans.push(Some(token.span()));
60    match token.payload() {
61        CapturedPayload::Word(word) => Ok(GeneratedToken::word(word)),
62        CapturedPayload::Punct(mark) => Ok(GeneratedToken::alone(*mark)),
63        CapturedPayload::Text(text) => Ok(GeneratedToken::text(text)),
64        CapturedPayload::Number(spelling) => GeneratedLiteral::number(spelling)
65            .map(GeneratedToken::literal)
66            .map_err(|issue| literal_refusal(issue, token)),
67        CapturedPayload::Group { delimiter, trees } => {
68            preserved_group(*delimiter, trees.as_slice(), token, source_spans)
69        }
70        CapturedPayload::ByteText(material) => Ok(GeneratedToken::byte_text(material)),
71        CapturedPayload::Character(character) => Ok(GeneratedToken::literal(
72            GeneratedLiteral::character(*character),
73        )),
74        CapturedPayload::Byte(byte) => Ok(GeneratedToken::literal(GeneratedLiteral::byte(*byte))),
75        CapturedPayload::NulTerminatedText(material) => {
76            GeneratedLiteral::nul_terminated_text(material)
77                .map(GeneratedToken::literal)
78                .map_err(|issue| literal_refusal(issue, token))
79        }
80        CapturedPayload::RawIdentifier(name) => Ok(GeneratedToken::raw_identifier(name)),
81        CapturedPayload::JointPunct(mark) => Ok(GeneratedToken::joint(*mark)),
82    }
83}
84
85/// Preserve one captured group with its written or invisible delimiter.
86fn preserved_group(
87    delimiter: CapturedDelimiter,
88    members: &[CapturedTokenTree],
89    source: &CapturedTokenTree,
90    source_spans: &mut Vec<Option<crate::token::SpanHandle>>,
91) -> Result<GeneratedToken, FragmentGenerationRefusal> {
92    let tokens = members
93        .iter()
94        .map(|member| preserved_token(member, source_spans))
95        .collect::<Result<Vec<_>, _>>()?;
96    GeneratedToken::group(generated_delimiter(delimiter), tokens).map_err(|_| {
97        FragmentGenerationRefusal {
98            issue: FragmentGenerationIssue::Unbounded,
99            at: Some(source.span()),
100        }
101    })
102}
103
104/// The generated delimiter corresponding to one captured delimiter.
105const fn generated_delimiter(delimiter: CapturedDelimiter) -> GeneratedDelimiter {
106    match delimiter {
107        CapturedDelimiter::Parenthesis => GeneratedDelimiter::Parenthesis,
108        CapturedDelimiter::Brace => GeneratedDelimiter::Brace,
109        CapturedDelimiter::Bracket => GeneratedDelimiter::Bracket,
110        CapturedDelimiter::Bare => GeneratedDelimiter::Bare,
111    }
112}
113
114/// Bind one generated-literal refusal to its exact captured token.
115const fn literal_refusal(
116    issue: super::GeneratedLiteralRefusal,
117    token: &CapturedTokenTree,
118) -> FragmentGenerationRefusal {
119    FragmentGenerationRefusal {
120        issue: FragmentGenerationIssue::Literal(issue),
121        at: Some(token.span()),
122    }
123}