regen/sdk/
util.rs

1use crate::sdk::{token::TokenType, TokenImpl};
2use std::fmt::Write;
3
4/// A parse tree node that contains the original node, as well as the result from a parse tree hook
5#[derive(Debug)]
6pub struct ParseHook<H, P> {
7    pub pt: P,
8    pub val: Option<H>,
9}
10
11impl<H, P> ParseHook<H, P> {
12    /// Take the value from the parse tree hook without checking if it is present.
13    ///
14    /// This is a warpper for `val.take().unwrap_or_else(...)` that should be used
15    /// when the value is always present. If you need to check the value, use the `val` field instead
16    pub fn take_unchecked(&mut self) -> H {
17        self.val.take().unwrap_or_else(||panic!("ParseHook::take() called on None. Make sure your parse hook function is not taking the value twice."))
18    }
19}
20
21/// Struct that represents result of [`merge_list_tail`] and [`merge_list_tail_optional_first`] macro
22#[derive(Debug)]
23pub struct MergedListTail<A, P> {
24    /// If the first member is present.
25    ///
26    /// This will always be true from [`merge_list_tail`] macro.
27    pub has_first: bool,
28    /// The merged list of values.
29    pub vals: Vec<P>,
30    /// The merged list of ast node references.
31    pub asts: Vec<A>,
32}
33
34impl<A, P> IntoIterator for MergedListTail<A, P> {
35    type Item = (A, P);
36    type IntoIter = std::iter::Zip<std::vec::IntoIter<A>, std::vec::IntoIter<P>>;
37
38    /// Convert into a parallel iterator of ast node references and values.
39    fn into_iter(self) -> Self::IntoIter {
40        self.asts.into_iter().zip(self.vals)
41    }
42}
43
44/// Util macro to merge `first` and `rest` in a parse tree.
45///
46/// The language definition syntax sometimes forces a list to be defined as a first element and a tail.
47/// This macro makes it easier to parse such lists.
48///
49/// See [`MergedListTail`] for more details of the merged result.
50///
51/// # Example
52/// ```nocompile
53/// // Example definition of a list of numbers separated with "+" sign
54/// // rule NumberList(first: token Number, rest: optional NumberListTail+);
55/// // rule NumberListTail(_: token Symbol"+", n: token Number);
56///
57/// let pt = /* instance of the generated parse tree */;
58/// The processor function takes a &String, which is the type of `token Number` in the parse tree
59/// let merged = merge_list_tail!(pt, m_first, m_rest, m_n);
60#[macro_export]
61macro_rules! merge_list_tail {
62    ($pt:ident, $first:ident, $rest:ident, $rest_member:ident) => {{
63        let mut vals = vec![$pt.$first.as_ref()];
64        let mut asts = vec![&$pt.ast.$first];
65        for x in &mut $pt.$rest {
66            vals.push(x.$rest_member.as_ref());
67            asts.push(&x.ast.$rest_member);
68        }
69        $crate::sdk::MergedListTail {
70            has_first: true,
71            vals,
72            asts,
73        }
74    }};
75    (mut $pt:ident, $first:ident, $rest:ident, $rest_member:ident) => {{
76        let mut vals = vec![$pt.$first.as_mut()];
77        let mut asts = vec![&$pt.ast.$first];
78        for x in &mut $pt.$rest {
79            vals.push(x.$rest_member.as_mut());
80            asts.push(&x.ast.$rest_member);
81        }
82        $crate::sdk::MergedListTail {
83            has_first: true,
84            vals,
85            asts,
86        }
87    }};
88}
89
90/// Util macro to merge `first` and `rest` in a parse tree.
91///
92/// Like [`merge_list_tail`], but the first member is optional.
93///
94/// # Example
95/// ```nocompile
96/// // Example definition of a list of numbers separated with "+" sign
97/// // rule NumberList(first: optional token Number, rest: optional NumberListTail+);
98/// // rule NumberListTail(_: token Symbol"+", n: token Number);
99///
100/// let pt = /* instance of the generated parse tree */;
101/// The processor function takes a &String, which is the type of `token Number` in the parse tree
102/// let merged = merge_list_tail_optional_first!(pt, m_first, m_rest, m_n, |x: &String| x.parse::<i32>());
103/// ```
104#[macro_export]
105macro_rules! merge_list_tail_optional_first {
106    ($pt:ident, $first:ident, $rest:ident, $rest_member:ident) => {{
107        let (has_first, mut vals, mut asts) = match &$pt.$first {
108            Some(x) => (
109                true,
110                vec![x.as_ref()],
111                vec![$pt.ast.$first.as_ref().unwrap()],
112            ),
113            None => (false, vec![], vec![]),
114        };
115        for x in &$pt.$rest {
116            vals.push(x.$rest_member.as_ref());
117            asts.push(&x.ast.$rest_member);
118        }
119        $crate::sdk::MergedListTail {
120            has_first,
121            vals,
122            asts,
123        }
124    }};
125    (mut $pt:ident, $first:ident, $rest:ident, $rest_member:ident) => {{
126        let (has_first, mut vals, mut asts) = match &mut $pt.$first {
127            Some(x) => (
128                true,
129                vec![x.as_mut()],
130                vec![$pt.ast.$first.as_ref().unwrap()],
131            ),
132            None => (false, vec![], vec![]),
133        };
134        for x in &mut $pt.$rest {
135            vals.push(x.$rest_member.as_mut());
136            asts.push(&x.ast.$rest_member);
137        }
138        $crate::sdk::MergedListTail {
139            has_first,
140            vals,
141            asts,
142        }
143    }};
144}
145
146/// Error type for the parser.
147#[derive(PartialEq, Debug, Clone)]
148pub struct Error {
149    /// Error message
150    pub msg: String,
151    /// Optional help message
152    pub help: Option<String>,
153    /// Abosolute character position in the source file
154    pub pos: (usize, usize),
155}
156
157impl Error {
158    pub fn global(msg: String, help: String) -> Self {
159        Self {
160            msg,
161            help: Some(help),
162            pos: (0, 1),
163        }
164    }
165
166    pub fn from_token<T>(token: &TokenImpl<T>, msg: String, help: String) -> Self
167    where
168        T: TokenType,
169    {
170        Self {
171            msg,
172            help: Some(help),
173            pos: token.pos,
174        }
175    }
176
177    pub fn from_token_without_help<T>(token: &TokenImpl<T>, msg: String) -> Self
178    where
179        T: TokenType,
180    {
181        Self {
182            msg,
183            help: None,
184            pos: token.pos,
185        }
186    }
187
188    pub fn pretty(&self, source: &str, context: usize) -> Result<String, std::fmt::Error> {
189        let mut output = String::new();
190        let (l, c) = self.to_line_col(source);
191        let m = &self.msg;
192        writeln!(output, "error: {m}")?;
193        writeln!(output, "   --> {l}:{c}")?;
194        writeln!(output, "    |")?;
195
196        let start = if l > context { l - context } else { 1 };
197        for (i, line_str) in source
198            .lines()
199            .skip(start - 1)
200            .take(context * 2 + 1)
201            .enumerate()
202        {
203            let current = i + start;
204            writeln!(output, "{current:3} | {line_str}")?;
205            let help = self
206                .help
207                .as_ref()
208                .map(|s| format!(" help: {}", s))
209                .unwrap_or_default();
210            if current == l {
211                writeln!(
212                    output,
213                    "{:3} | {:>width$}{t}{help}",
214                    "",
215                    "",
216                    width = c - 1,
217                    t = "^".repeat(self.pos.1 - self.pos.0)
218                )?;
219            }
220        }
221
222        Ok(output)
223    }
224
225    fn to_line_col(&self, content: &str) -> (usize, usize) {
226        let (start, _) = self.pos;
227        let mut cur = 0;
228        let mut l = 1;
229        for line in content.split('\n') {
230            let line_len = line.len() + 1;
231            if cur + line_len > start {
232                return (l, start - cur + 1);
233            }
234            cur += line_len;
235            l += 1;
236        }
237        (l, 0)
238    }
239}