lwb_parser/parser/peg/
parser_core_file.rs

1use crate::parser::peg::parse_error::{Expect, PEGParseError};
2use crate::parser::peg::parse_result::ParseResult;
3use crate::parser::peg::parser_core::{ParserContext, ParserState};
4use crate::parser::peg::parser_core_ast::{CoreAst, ParsePairRaw};
5use crate::parser::peg::parser_core_expression::{
6    parse_expression_name, skip_single_layout, ExpressionContext,
7};
8use crate::sources::source_file::{SourceFile, SourceFileIterator};
9use crate::sources::span::Span;
10use std::collections::{HashMap, VecDeque};
11
12/// Parses a file, given the syntax to parse it with, and the file.
13/// When successful, it returns a `ParsePairSort`.
14/// When unsuccessful, it returns a `ParseError`.
15#[allow(clippy::unnecessary_unwrap)] //Clippy gives a suggestion which makes code ugly
16pub fn parse_file<'src>(
17    ast: &'src CoreAst<'src>,
18    file: &'src SourceFile,
19) -> (ParsePairRaw, Vec<PEGParseError>) {
20    //Create a new parser state
21    let mut state = ParserContext {
22        file,
23        ast,
24        errors: HashMap::new(),
25    };
26
27    //Parse the starting sort
28    let mut errors = vec![];
29
30    let mut last_err_pos: Option<usize> = None;
31    let mut last_err_offset = 0usize;
32    loop {
33        let (res, err) = parse_file_sub(&state, state.ast.starting_sort, file.iter());
34        if !res.ok {
35            let err = err.expect("Not ok means an error happened.");
36
37            //If this is the first time we encounter this, error, log it and retry
38            if last_err_pos.is_none()
39                || last_err_pos.unwrap() + last_err_offset < res.pos_err.position()
40            {
41                errors.push(err);
42                last_err_pos = Some(res.pos_err.position());
43                last_err_offset = 0;
44                state.errors.insert(last_err_pos.unwrap(), last_err_offset);
45
46                continue;
47            } else {
48                //If the error now spans rest of file, we could not recover
49                let len_left = res.pos_err.clone().count();
50                if last_err_offset >= len_left {
51                    return (res.result, errors);
52                }
53
54                //Increase offset by 1 and repeat
55                last_err_offset += 1;
56                state.errors.insert(last_err_pos.unwrap(), last_err_offset);
57            }
58        } else {
59            return (res.result, errors);
60        }
61    }
62}
63
64pub fn parse_file_sub<'src>(
65    state: &ParserContext<'src>,
66    sort: &'src str,
67    pos: SourceFileIterator<'src>,
68) -> (ParseResult<'src, ParsePairRaw>, Option<PEGParseError>) {
69    let mut cache = ParserState {
70        cache: HashMap::new(),
71        cache_stack: VecDeque::new(),
72        best_error: None,
73        no_layout_nest_count: 0usize,
74        no_errors_nest_count: 0usize,
75        allow_layout: true,
76    };
77
78    let mut res = parse_expression_name(state, &mut cache, sort, pos);
79    if !res.ok {
80        return (res, Some(cache.best_error.unwrap()));
81    }
82
83    //If there is no input left, return Ok. Skip layout first
84    loop {
85        let (ok, after_layout_pos) = skip_single_layout(
86            state,
87            &mut cache,
88            res.pos.clone(),
89            &ExpressionContext::empty(),
90        );
91        if !ok {
92            break;
93        };
94        res.pos = after_layout_pos;
95    }
96
97    if res.pos.peek().is_none() {
98        (res, None)
99    } else {
100        //If any occurred during the parsing, return it. Otherwise, return a generic NotEntireInput error.
101        //I'm not entirely sure this logic always returns relevant errors. Maybe we should inform the user the parse was actually fine, but didn't parse enough?
102        // TODO: ^
103        res.ok = false;
104        match cache.best_error {
105            Some(err) => (res, Some(err)),
106            None => {
107                let curpos = res.pos.position();
108                while res.pos.next().is_some() {}
109                let endpos = res.pos.position();
110                (
111                    res,
112                    Some(PEGParseError::expect(
113                        Span::from_end(state.file, curpos, endpos),
114                        Expect::NotEntireInput(),
115                        &ExpressionContext::empty(),
116                    )),
117                )
118            }
119        }
120    }
121}