Skip to main content

nickel_lang_parser/
lib.rs

1use crate::{
2    ast::{Ast, AstAlloc, typ::Type},
3    error::{ParseError, ParseErrors},
4    files::FileId,
5    identifier::LocIdent,
6    position::RawSpan,
7};
8
9use lalrpop_util::lalrpop_mod;
10
11lalrpop_mod!(
12    #[allow(clippy::all)]
13    #[allow(unused_parens)]
14    #[allow(unused_imports)]
15    pub grammar, "/grammar.rs");
16
17use grammar::__ToTriple;
18
19pub mod ast;
20pub mod combine;
21pub mod environment;
22pub mod error;
23pub mod files;
24pub mod identifier;
25pub mod lexer;
26pub mod metrics;
27pub mod position;
28pub mod traverse;
29pub mod typ;
30pub(crate) mod uniterm;
31pub mod utils;
32
33#[cfg(test)]
34mod tests;
35
36/// Either an expression or a toplevel let declaration.
37///
38/// Used exclusively in the REPL to allow the defining of variables without having to specify `in`.
39/// For example:
40///
41/// ```text
42/// nickel>let foo = 1
43/// nickel>foo
44/// 1
45/// ```
46pub enum ExtendedTerm<T> {
47    Term(T),
48    ToplevelLet(LocIdent, T),
49}
50
51// The interface of LALRPOP-generated parsers, for each public rule. This trait is used as a facade
52// to implement parser-independent features (such as error tolerance helpers), which don't have to
53// be reimplemented for each and every parser. It's LALRPOP-specific and shouldn't be used outside
54// of this module, if we don't want our implementation to be coupled to LALRPOP details.
55//
56// The type of `parse` was just copy-pasted from the generated code of LALRPOP.
57//TODO: We could avoid having those pesky `'ast` lifetimes at the top-level of every trait using
58//generic associated types, but it's not entirely trivial - to investigate.
59trait LalrpopParser<'ast, T> {
60    fn parse<'input, 'err, 'wcard, __TOKEN, __TOKENS>(
61        &self,
62        alloc: &'ast AstAlloc,
63        src_id: FileId,
64        errors: &'err mut Vec<
65            lalrpop_util::ErrorRecovery<usize, lexer::Token<'input>, self::error::ParseOrLexError>,
66        >,
67        next_wildcard_id: &'wcard mut usize,
68        __tokens0: __TOKENS,
69    ) -> Result<
70        T,
71        lalrpop_util::ParseError<usize, lexer::Token<'input>, self::error::ParseOrLexError>,
72    >
73    where
74        __TOKEN: __ToTriple<'input, 'ast, 'err, 'wcard>,
75        __TOKENS: IntoIterator<Item = __TOKEN>;
76}
77
78/// Generate boiler-plate code to implement the trait [`LalrpopParser`] for a parser generated by
79/// LALRPOP.
80macro_rules! generate_lalrpop_parser_impl {
81    ($parser:ty, $output:ty) => {
82        impl<'ast> LalrpopParser<'ast, $output> for $parser {
83            fn parse<'input, 'err, 'wcard, __TOKEN, __TOKENS>(
84                &self,
85                alloc: &'ast AstAlloc,
86                src_id: FileId,
87                errors: &'err mut Vec<
88                    lalrpop_util::ErrorRecovery<
89                        usize,
90                        lexer::Token<'input>,
91                        self::error::ParseOrLexError,
92                    >,
93                >,
94                next_wildcard_id: &'wcard mut usize,
95                __tokens0: __TOKENS,
96            ) -> Result<
97                $output,
98                lalrpop_util::ParseError<usize, lexer::Token<'input>, self::error::ParseOrLexError>,
99            >
100            where
101                __TOKEN: __ToTriple<'input, 'ast, 'err, 'wcard>,
102                __TOKENS: IntoIterator<Item = __TOKEN>,
103            {
104                Self::parse(self, alloc, src_id, errors, next_wildcard_id, __tokens0)
105            }
106        }
107    };
108}
109
110generate_lalrpop_parser_impl!(grammar::ExtendedTermParser, ExtendedTerm<Ast<'ast>>);
111generate_lalrpop_parser_impl!(grammar::TermParser, Ast<'ast>);
112generate_lalrpop_parser_impl!(grammar::FixedTypeParser, Type<'ast>);
113generate_lalrpop_parser_impl!(grammar::StaticFieldPathParser, Vec<LocIdent>);
114generate_lalrpop_parser_impl!(
115    grammar::CliFieldAssignmentParser,
116    (Vec<LocIdent>, Ast<'ast>, RawSpan)
117);
118
119/// General interface of the various specialized Nickel parsers.
120///
121/// `T` is the product of the parser (a term, a type, etc.).
122pub trait ErrorTolerantParser<'ast, T> {
123    /// Parse a value from a lexer with the given `file_id` in an error-tolerant way. This methods
124    /// can still fail for non-recoverable errors.
125    fn parse_tolerant<'input>(
126        &self,
127        alloc: &'ast AstAlloc,
128        file_id: FileId,
129        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
130    ) -> Result<(T, ParseErrors), ParseError>;
131
132    /// Parse a value from a lexer with the given `file_id`, failing at the first encountered
133    /// error.
134    fn parse_strict<'input>(
135        &self,
136        alloc: &'ast AstAlloc,
137        file_id: FileId,
138        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
139    ) -> Result<T, ParseErrors>;
140}
141
142impl<'ast, T, P> ErrorTolerantParser<'ast, T> for P
143where
144    P: LalrpopParser<'ast, T>,
145{
146    fn parse_tolerant<'input>(
147        &self,
148        alloc: &'ast AstAlloc,
149        file_id: FileId,
150        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
151    ) -> Result<(T, ParseErrors), ParseError> {
152        let mut parse_errors = Vec::new();
153        let mut next_wildcard_id = 0;
154        let result = self
155            .parse(
156                alloc,
157                file_id,
158                &mut parse_errors,
159                &mut next_wildcard_id,
160                lexer.map(|x| x.map_err(error::ParseOrLexError::from)),
161            )
162            .map_err(|err| ParseError::from_lalrpop(err, file_id));
163
164        let parse_errors = ParseErrors::from_recoverable(parse_errors, file_id);
165        match result {
166            Ok(t) => Ok((t, parse_errors)),
167            Err(e) => Err(e),
168        }
169    }
170
171    fn parse_strict<'input>(
172        &self,
173        alloc: &'ast AstAlloc,
174        file_id: FileId,
175        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
176    ) -> Result<T, ParseErrors> {
177        match self.parse_tolerant(alloc, file_id, lexer) {
178            Ok((t, e)) if e.no_errors() => Ok(t),
179            Ok((_, e)) => Err(e),
180            Err(e) => Err(e.into()),
181        }
182    }
183}
184
185/// Additional capabilities for parsers that return `Asts`, offering an error-tolerant interface
186/// that is actually infallible.
187///
188/// The interface of error tolerant parsers is a bit strange: albeit dubbed as error-tolerant,
189/// [ErrorTolerantParser::parse_tolerant] is still fallible with the same error type that is
190/// returned in the `Ok` case. There are thus some parse errors that are fatal: the issue is that
191/// LALRPOP can't generate a proper AST when it can't even get to complete one parsing rule, in
192/// which case it bails out. But this is artificial, because we can still produce an AST with one
193/// node spanning the full file, this node being the fatal error.
194///
195/// This is precisely what does [FullyErrorTolerantParser], which wraps [ErrorTolerantParser] when
196/// `T` is `Ast<'ast>`.
197pub trait FullyErrorTolerantParser<'ast, T> {
198    /// Parse a value from a lexer with the given `file_id` in an error-tolerant way.
199    ///
200    /// When the parser fails without being able to produce a proper AST, we need to construct the
201    /// root as the error node. Since this isn't easy to reverse-engineer the whole span of the
202    /// original file from the lexer, we take it as an explicit argument.
203    fn parse_fully_tolerant<'input>(
204        &self,
205        alloc: &'ast AstAlloc,
206        file_id: FileId,
207        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
208        full_span: RawSpan,
209    ) -> (T, ParseErrors);
210}
211
212impl<'ast, P> FullyErrorTolerantParser<'ast, Ast<'ast>> for P
213where
214    P: ErrorTolerantParser<'ast, Ast<'ast>>,
215{
216    fn parse_fully_tolerant<'input>(
217        &self,
218        alloc: &'ast AstAlloc,
219        file_id: FileId,
220        lexer: impl Iterator<Item = Result<lexer::SpannedToken<'input>, error::LexicalError>>,
221        full_span: RawSpan,
222    ) -> (Ast<'ast>, ParseErrors) {
223        match self.parse_tolerant(alloc, file_id, lexer) {
224            Ok((ast, e)) => (ast, e),
225            Err(e) => {
226                let ast = alloc.parse_error(e.clone()).spanned(full_span.into());
227                (ast, e.into())
228            }
229        }
230    }
231}