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
27pub 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 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 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 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 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 Ok(())
348 })
349 }
350}