logix_type/parser/
mod.rs

1use std::{ops::Range, path::Path};
2
3use crate::{
4    error::{ParseError, Result, Wanted, Warn},
5    loader::{CachedFile, LogixLoader},
6    span::SourceSpan,
7    token::{parse_token, Brace, Delim, ParseRes, Token},
8    type_trait::Value,
9    types::ShortStr,
10    LogixType,
11};
12use bstr::ByteSlice;
13use logix_vfs::LogixVfs;
14
15mod delimited;
16pub use delimited::ParseDelimited;
17
18#[derive(Clone)]
19struct ParseState {
20    cur_pos: usize,
21    cur_col: usize,
22    cur_ln: usize,
23    last_was_newline: bool,
24    eof: bool,
25}
26
27/// The parser used by the `LogixType` trait
28pub struct LogixParser<'fs, 'f, FS: LogixVfs> {
29    loader: &'fs mut LogixLoader<FS>,
30    file: &'f CachedFile,
31    state: ParseState,
32}
33
34impl<'fs, 'f, FS: LogixVfs> LogixParser<'fs, 'f, FS> {
35    pub(crate) fn new(loader: &'fs mut LogixLoader<FS>, file: &'f CachedFile) -> Self {
36        Self {
37            loader,
38            file,
39            state: ParseState {
40                cur_pos: 0,
41                cur_col: 0,
42                cur_ln: 1,
43                last_was_newline: true,
44                eof: false,
45            },
46        }
47    }
48
49    pub fn warning(&self, warning: Warn) -> Result<()> {
50        // TODO(2023.10): Make it possible to allow warnings
51        Err(ParseError::Warning(warning))
52    }
53
54    pub fn cur_span(&self) -> SourceSpan {
55        self.calc_span(0..0)
56    }
57
58    fn calc_span(&self, range: Range<usize>) -> SourceSpan {
59        SourceSpan::new(
60            self.file,
61            self.state.cur_pos + range.start,
62            self.state.cur_ln,
63            self.state.cur_col + range.start,
64            range.len(),
65        )
66    }
67
68    pub fn peek_token(&mut self) -> Result<(SourceSpan, Token<'f>)> {
69        let mut fork = LogixParser {
70            loader: self.loader,
71            file: self.file,
72            state: self.state.clone(),
73        };
74        fork.next_token()
75    }
76
77    pub fn next_token(&mut self) -> Result<(SourceSpan, Token<'f>)> {
78        if self.state.eof {
79            return Ok((self.calc_span(0..0), Token::Newline(true)));
80        }
81
82        'outer: loop {
83            let last_was_newline = std::mem::take(&mut self.state.last_was_newline);
84
85            'ignore_token: loop {
86                let buf = &self.file.data()[self.state.cur_pos..];
87                let (span, token) = {
88                    let ParseRes {
89                        len,
90                        range,
91                        lines,
92                        token,
93                    } = parse_token(buf);
94                    let span = self.calc_span(range);
95
96                    self.state.cur_pos += len;
97                    if lines > 0 {
98                        debug_assert_ne!(len, 0);
99
100                        self.state.cur_ln += lines;
101                        if len == 1 {
102                            self.state.cur_col = 0;
103                        } else {
104                            self.state.cur_col = self.file.data()[..self.state.cur_pos]
105                                .lines()
106                                .next_back()
107                                .unwrap()
108                                .len();
109                        }
110                    } else {
111                        self.state.cur_col += len;
112                    }
113                    (span, token)
114                };
115
116                return match token {
117                    Ok(
118                        token @ (Token::Ident(..)
119                        | Token::Action(..)
120                        | Token::Brace { .. }
121                        | Token::Delim(..)
122                        | Token::Literal(..)),
123                    ) => Ok((span, token)),
124                    Ok(Token::Newline(eof)) => {
125                        self.state.last_was_newline = true;
126                        if !eof && last_was_newline {
127                            continue 'outer;
128                        }
129                        self.state.eof = eof;
130                        Ok((span, Token::Newline(eof)))
131                    }
132                    Ok(Token::Comment(_)) => {
133                        continue 'ignore_token;
134                    }
135                    Err(error) => Err(ParseError::TokenError { span, error }),
136                };
137            }
138        }
139    }
140
141    // TODO(2023.10): Switch to using this where possible
142    /// Create a new parser that must start with the given brace, and once it returns, must
143    /// point to the ending brace
144    pub fn req_wrapped<R>(
145        &mut self,
146        while_parsing: &'static str,
147        brace: Brace,
148        f: impl FnOnce(&mut LogixParser<FS>) -> Result<R>,
149    ) -> Result<Value<R>> {
150        let start = self.req_token(while_parsing, Token::Brace { start: true, brace })?;
151        let value = f(self)?;
152        let end = self.req_token(
153            while_parsing,
154            Token::Brace {
155                start: false,
156                brace,
157            },
158        )?;
159        Ok(Value { value, span: start }.join_with_span(end))
160    }
161
162    /// Parse a list of items that may be delimited by comma, newline or both
163    pub fn parse_delimited<'p, R: LogixType>(
164        &'p mut self,
165        while_parsing: &'static str,
166    ) -> ParseDelimited<'p, 'fs, 'f, FS, R> {
167        ParseDelimited::new(self, while_parsing)
168    }
169
170    pub fn req_token(
171        &mut self,
172        while_parsing: &'static str,
173        want_token: Token<'static>,
174    ) -> Result<SourceSpan> {
175        let (span, got_token) = self.next_token()?;
176
177        if want_token == got_token {
178            Ok(span)
179        } else {
180            Err(ParseError::UnexpectedToken {
181                span,
182                while_parsing,
183                wanted: Wanted::Token(want_token),
184                got_token: got_token.token_type_name(),
185            })
186        }
187    }
188
189    pub fn read_key_value<T: LogixType>(
190        &mut self,
191        while_parsing: &'static str,
192        end_brace: Brace,
193    ) -> Result<Option<(Value<ShortStr>, Value<T>)>> {
194        match self.next_token()? {
195            (span, Token::Ident(key)) => {
196                let key = Value {
197                    value: ShortStr::from(key),
198                    span,
199                };
200
201                self.req_token(while_parsing, Token::Delim(Delim::Colon))?;
202
203                let value = T::logix_parse(self)?;
204
205                self.req_newline(while_parsing)?;
206
207                Ok(Some((key, value)))
208            }
209            (
210                _,
211                Token::Brace {
212                    start: false,
213                    brace,
214                },
215            ) if brace == end_brace => Ok(None),
216            (span, got_token) => Err(ParseError::UnexpectedToken {
217                span,
218                while_parsing,
219                wanted: Wanted::Ident,
220                got_token: got_token.token_type_name(),
221            }),
222        }
223    }
224
225    pub fn req_newline(&mut self, while_parsing: &'static str) -> Result<()> {
226        match self.next_token()? {
227            (_, Token::Newline(..)) => Ok(()),
228            (span, got_token) => Err(ParseError::UnexpectedToken {
229                span,
230                while_parsing,
231                wanted: Wanted::Token(Token::Newline(false)),
232                got_token: got_token.token_type_name(),
233            }),
234        }
235    }
236
237    pub(crate) fn open_file(
238        &mut self,
239        path: impl AsRef<Path>,
240    ) -> Result<CachedFile, logix_vfs::Error> {
241        self.loader.open_file(path)
242    }
243
244    /// Forks the parser and calls the specified function, if the return value
245    /// is `Some(R)`, the parser is replaced by the fork.
246    pub(crate) fn forked<R>(
247        &mut self,
248        f: impl FnOnce(&mut LogixParser<'_, 'f, FS>) -> Result<Option<R>>,
249    ) -> Result<Option<R>> {
250        let mut fork = LogixParser {
251            loader: self.loader,
252            file: self.file,
253            state: self.state.clone(),
254        };
255        if let Some(ret) = f(&mut fork)? {
256            let LogixParser {
257                loader: _,
258                file: _,
259                state,
260            } = fork;
261            self.state = state;
262            Ok(Some(ret))
263        } else {
264            Ok(None)
265        }
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use logix_vfs::RelFs;
272
273    use crate::token::{Brace, Literal, StrLit, StrTag};
274
275    use super::*;
276
277    pub(super) struct Tester<'f> {
278        f: &'f CachedFile,
279    }
280
281    impl<'f> Tester<'f> {
282        fn span(&self, pos: usize, ln: usize, start: usize, len: usize) -> SourceSpan {
283            SourceSpan::new(self.f, pos, ln, start, len)
284        }
285    }
286
287    pub(super) fn run_test<R>(
288        src: &str,
289        test_clb: impl FnOnce(&mut LogixParser<RelFs>, &Tester) -> R,
290    ) -> R {
291        let root = tempfile::tempdir().unwrap();
292        std::fs::write(root.path().join("test.logix"), src).unwrap();
293
294        let mut loader = LogixLoader::new(RelFs::new(root.path()));
295        let f = loader.open_file("test.logix").unwrap();
296
297        test_clb(&mut LogixParser::new(&mut loader, &f), &Tester { f: &f })
298    }
299
300    #[test]
301    fn basics() -> Result<()> {
302        run_test("Hello { world: \"!!!\" }", |p, t| -> Result<()> {
303            assert_eq!(p.next_token()?, (t.span(0, 1, 0, 5), Token::Ident("Hello")));
304            assert_eq!(
305                p.next_token()?,
306                (
307                    t.span(6, 1, 6, 1),
308                    Token::Brace {
309                        start: true,
310                        brace: Brace::Curly
311                    }
312                )
313            );
314            assert_eq!(p.next_token()?, (t.span(8, 1, 8, 5), Token::Ident("world")));
315            assert_eq!(
316                p.next_token()?,
317                (t.span(13, 1, 13, 1), Token::Delim(Delim::Colon))
318            );
319            assert_eq!(
320                p.next_token()?,
321                (
322                    t.span(15, 1, 15, 5),
323                    Token::Literal(Literal::Str(StrLit::new(StrTag::Raw, "!!!")))
324                )
325            );
326            assert_eq!(
327                p.next_token()?,
328                (
329                    t.span(21, 1, 21, 1),
330                    Token::Brace {
331                        start: false,
332                        brace: Brace::Curly
333                    }
334                )
335            );
336
337            assert_eq!(
338                p.next_token()?,
339                (t.span(22, 1, 22, 0), Token::Newline(true))
340            );
341
342            assert_eq!(
343                p.next_token()?,
344                (t.span(22, 1, 22, 0), Token::Newline(true))
345            );
346            // A second time to trigger additional code
347            Ok(())
348        })
349    }
350}