Skip to main content

boa_parser/parser/
mod.rs

1//! Boa parser implementation.
2
3mod cursor;
4mod expression;
5mod statement;
6
7pub(crate) mod function;
8
9#[cfg(test)]
10mod tests;
11
12use crate::{
13    Error, Source,
14    error::ParseResult,
15    lexer::{Error as LexError, InputElement},
16    parser::{
17        cursor::Cursor,
18        function::{FormalParameters, FunctionStatementList},
19    },
20    source::ReadChar,
21};
22use boa_ast::{
23    Position, StatementList,
24    function::{FormalParameterList, FunctionBody},
25    operations::{
26        ContainsSymbol, all_private_identifiers_valid, check_labels, contains,
27        contains_invalid_object_literal, lexically_declared_names, var_declared_names,
28    },
29    scope::Scope,
30};
31use boa_interner::{Interner, Sym};
32use rustc_hash::FxHashSet;
33use std::path::Path;
34
35use self::statement::ModuleItemList;
36
37type ScriptParseOutput = (boa_ast::Script, boa_ast::SourceText);
38type ModuleParseOutput = (boa_ast::Module, boa_ast::SourceText);
39
40/// Trait implemented by parsers.
41///
42/// This makes it possible to abstract over the underlying implementation of a parser.
43trait TokenParser<R>: Sized
44where
45    R: ReadChar,
46{
47    /// Output type for the parser.
48    type Output; // = Node; waiting for https://github.com/rust-lang/rust/issues/29661
49
50    /// Parses the token stream using the current parser.
51    ///
52    /// This method needs to be provided by the implementor type.
53    ///
54    /// # Errors
55    ///
56    /// It will fail if the cursor is not placed at the beginning of the expected non-terminal.
57    fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output>;
58}
59
60/// Boolean representing if the parser should allow a `yield` keyword.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62struct AllowYield(bool);
63
64impl From<bool> for AllowYield {
65    fn from(allow: bool) -> Self {
66        Self(allow)
67    }
68}
69
70/// Boolean representing if the parser should allow a `await` keyword.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72struct AllowAwait(bool);
73
74impl From<bool> for AllowAwait {
75    fn from(allow: bool) -> Self {
76        Self(allow)
77    }
78}
79
80/// Boolean representing if the parser should allow a `in` keyword.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82struct AllowIn(bool);
83
84impl From<bool> for AllowIn {
85    fn from(allow: bool) -> Self {
86        Self(allow)
87    }
88}
89
90/// Boolean representing if the parser should allow a `return` keyword.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92struct AllowReturn(bool);
93
94impl From<bool> for AllowReturn {
95    fn from(allow: bool) -> Self {
96        Self(allow)
97    }
98}
99
100/// Boolean representing if the parser should allow a `default` keyword.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102struct AllowDefault(bool);
103
104impl From<bool> for AllowDefault {
105    fn from(allow: bool) -> Self {
106        Self(allow)
107    }
108}
109
110/// Parser for the ECMAScript language.
111///
112/// This parser implementation tries to be conformant to the most recent
113/// [ECMAScript language specification], and it also implements some legacy features like
114/// [labelled functions][label] or [duplicated block-level function definitions][block].
115///
116/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-source-code
117/// [label]: https://tc39.es/ecma262/#sec-labelled-function-declarations
118/// [block]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics
119#[derive(Debug)]
120pub struct Parser<'a, R> {
121    /// Path to the source being parsed.
122    #[allow(unused)] // Good to have for future improvements.
123    path: Option<&'a Path>,
124    /// Cursor of the parser, pointing to the lexer and used to get tokens for the parser.
125    cursor: Cursor<R>,
126}
127
128impl<'a, R: ReadChar> Parser<'a, R> {
129    /// Create a new `Parser` with a `Source` as the input to parse.
130    pub fn new(source: Source<'a, R>) -> Self {
131        Self {
132            path: source.path,
133            cursor: Cursor::new(source.reader),
134        }
135    }
136
137    /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation without source text.
138    /// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm.
139    ///
140    /// # Errors
141    ///
142    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
143    ///
144    /// [spec]: https://tc39.es/ecma262/#prod-Script
145    pub fn parse_script(
146        &mut self,
147        scope: &Scope,
148        interner: &mut Interner,
149    ) -> ParseResult<boa_ast::Script> {
150        self.parse_script_with_source(scope, interner).map(|x| x.0)
151    }
152
153    /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation with source text.
154    /// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm.
155    ///
156    /// # Errors
157    ///
158    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
159    ///
160    /// [spec]: https://tc39.es/ecma262/#prod-Script
161    pub fn parse_script_with_source(
162        &mut self,
163        scope: &Scope,
164        interner: &mut Interner,
165    ) -> ParseResult<ScriptParseOutput> {
166        self.cursor.set_goal(InputElement::HashbangOrRegExp);
167        let (mut ast, source) = ScriptParser::new(false).parse(&mut self.cursor, interner)?;
168        if let Err(reason) = ast.analyze_scope(scope, interner) {
169            return Err(Error::general(
170                format!("invalid scope analysis: {reason}"),
171                Position::new(1, 1),
172            ));
173        }
174        Ok((ast, source))
175    }
176
177    /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation without source text.
178    /// The resulting `ModuleItemList` can be compiled into boa bytecode and executed in the boa vm.
179    ///
180    /// # Errors
181    ///
182    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
183    ///
184    /// [spec]: https://tc39.es/ecma262/#prod-Module
185    pub fn parse_module(
186        &mut self,
187        scope: &Scope,
188        interner: &mut Interner,
189    ) -> ParseResult<boa_ast::Module>
190    where
191        R: ReadChar,
192    {
193        self.parse_module_with_source(scope, interner).map(|x| x.0)
194    }
195
196    /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation with source text.
197    /// The resulting `ModuleItemList` can be compiled into boa bytecode and executed in the boa vm.
198    ///
199    /// # Errors
200    ///
201    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
202    ///
203    /// [spec]: https://tc39.es/ecma262/#prod-Module
204    pub fn parse_module_with_source(
205        &mut self,
206        scope: &Scope,
207        interner: &mut Interner,
208    ) -> ParseResult<ModuleParseOutput>
209    where
210        R: ReadChar,
211    {
212        self.cursor.set_goal(InputElement::HashbangOrRegExp);
213        let (mut module, source) = ModuleParser.parse(&mut self.cursor, interner)?;
214        if let Err(reason) = module.analyze_scope(scope, interner) {
215            return Err(Error::general(
216                format!("invalid scope analysis: {reason}"),
217                Position::new(1, 1),
218            ));
219        }
220        Ok((module, source))
221    }
222
223    /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec]
224    ///
225    /// Parses the source text input of an `eval` call.
226    ///
227    /// # Errors
228    ///
229    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
230    ///
231    /// [spec]: https://tc39.es/ecma262/#sec-performeval
232    pub fn parse_eval(
233        &mut self,
234        direct: bool,
235        interner: &mut Interner,
236    ) -> ParseResult<ScriptParseOutput> {
237        self.cursor.set_goal(InputElement::HashbangOrRegExp);
238        ScriptParser::new(direct).parse(&mut self.cursor, interner)
239    }
240
241    /// Parses the full input as an [ECMAScript `FunctionBody`][spec] into the boa AST representation.
242    ///
243    /// # Errors
244    ///
245    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
246    ///
247    /// [spec]: https://tc39.es/ecma262/#prod-FunctionBody
248    pub fn parse_function_body(
249        &mut self,
250        interner: &mut Interner,
251        allow_yield: bool,
252        allow_await: bool,
253    ) -> ParseResult<FunctionBody> {
254        let mut parser = FunctionStatementList::new(allow_yield, allow_await, "function body");
255        parser.parse_full_input(true);
256        parser.parse(&mut self.cursor, interner)
257    }
258
259    /// Parses the full input as an [ECMAScript `FormalParameterList`][spec] into the boa AST representation.
260    ///
261    /// # Errors
262    ///
263    /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
264    ///
265    /// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
266    pub fn parse_formal_parameters(
267        &mut self,
268        interner: &mut Interner,
269        allow_yield: bool,
270        allow_await: bool,
271    ) -> ParseResult<FormalParameterList> {
272        FormalParameters::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
273    }
274}
275
276impl<R> Parser<'_, R> {
277    /// Set the parser strict mode to true.
278    pub fn set_strict(&mut self)
279    where
280        R: ReadChar,
281    {
282        self.cursor.set_strict(true);
283    }
284
285    /// Set the parser JSON mode to true.
286    pub fn set_json_parse(&mut self)
287    where
288        R: ReadChar,
289    {
290        self.cursor.set_json_parse(true);
291    }
292
293    /// Set the unique identifier for the parser.
294    pub fn set_identifier(&mut self, identifier: u32)
295    where
296        R: ReadChar,
297    {
298        self.cursor.set_identifier(identifier);
299    }
300}
301
302/// Parses a full script.
303///
304/// More information:
305///  - [ECMAScript specification][spec]
306///
307/// [spec]: https://tc39.es/ecma262/#prod-Script
308#[derive(Debug, Clone, Copy)]
309pub struct ScriptParser {
310    direct_eval: bool,
311}
312
313impl ScriptParser {
314    /// Create a new `Script` parser.
315    #[inline]
316    const fn new(direct_eval: bool) -> Self {
317        Self { direct_eval }
318    }
319}
320
321impl<R> TokenParser<R> for ScriptParser
322where
323    R: ReadChar,
324{
325    type Output = ScriptParseOutput;
326
327    fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
328        let stmts =
329            ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?;
330        let script = boa_ast::Script::new(stmts);
331
332        // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
333        let mut lexical_names = FxHashSet::default();
334        for name in lexically_declared_names(&script) {
335            if !lexical_names.insert(name) {
336                return Err(Error::general(
337                    "lexical name declared multiple times",
338                    Position::new(1, 1),
339                ));
340            }
341        }
342
343        // It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
344        for name in var_declared_names(&script) {
345            if lexical_names.contains(&name) {
346                return Err(Error::general(
347                    "lexical name declared multiple times",
348                    Position::new(1, 1),
349                ));
350            }
351        }
352
353        let source = cursor.take_source();
354        Ok((script, source))
355    }
356}
357
358/// Parses a script body.
359///
360/// More information:
361///  - [ECMAScript specification][spec]
362///
363/// [spec]: https://tc39.es/ecma262/#prod-ScriptBody
364#[derive(Debug, Clone, Copy)]
365pub struct ScriptBody {
366    directive_prologues: bool,
367    strict: bool,
368    direct_eval: bool,
369}
370
371impl ScriptBody {
372    /// Create a new `ScriptBody` parser.
373    #[inline]
374    const fn new(directive_prologues: bool, strict: bool, direct_eval: bool) -> Self {
375        Self {
376            directive_prologues,
377            strict,
378            direct_eval,
379        }
380    }
381}
382
383impl<R> TokenParser<R> for ScriptBody
384where
385    R: ReadChar,
386{
387    type Output = StatementList;
388
389    fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
390        let (body, _end) = statement::StatementList::new(
391            false,
392            false,
393            false,
394            &[],
395            self.directive_prologues,
396            self.strict,
397        )
398        .parse(cursor, interner)?;
399
400        if !self.direct_eval {
401            // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval
402            // code that is being processed by a direct eval.
403            // Additional early error rules for super within direct eval are defined in 19.2.1.1.
404            if contains(&body, ContainsSymbol::Super) {
405                return Err(Error::general("invalid super usage", Position::new(1, 1)));
406            }
407            // It is a Syntax Error if StatementList Contains NewTarget unless the source text containing NewTarget
408            // is eval code that is being processed by a direct eval.
409            // Additional early error rules for NewTarget in direct eval are defined in 19.2.1.1.
410            if contains(&body, ContainsSymbol::NewTarget) {
411                return Err(Error::general(
412                    "invalid new.target usage",
413                    Position::new(1, 1),
414                ));
415            }
416
417            // It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with
418            // argument « » is false unless the source text containing ScriptBody is
419            // eval code that is being processed by a direct eval.
420            if !all_private_identifiers_valid(&body, Vec::new()) {
421                return Err(Error::general(
422                    "invalid private identifier usage",
423                    Position::new(1, 1),
424                ));
425            }
426        }
427
428        if let Err(error) = check_labels(&body) {
429            return Err(Error::lex(LexError::Syntax(
430                error.message(interner).into(),
431                Position::new(1, 1),
432            )));
433        }
434
435        if contains_invalid_object_literal(&body) {
436            return Err(Error::lex(LexError::Syntax(
437                "invalid object literal in script statement list".into(),
438                Position::new(1, 1),
439            )));
440        }
441
442        Ok(body)
443    }
444}
445
446/// Parses a full module.
447///
448/// More information:
449///  - [ECMAScript specification][spec]
450///
451/// [spec]: https://tc39.es/ecma262/#prod-Module
452#[derive(Debug, Clone, Copy)]
453struct ModuleParser;
454
455impl<R> TokenParser<R> for ModuleParser
456where
457    R: ReadChar,
458{
459    type Output = ModuleParseOutput;
460
461    fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
462        cursor.set_module();
463
464        let module = boa_ast::Module::new(ModuleItemList.parse(cursor, interner)?);
465
466        // It is a Syntax Error if the LexicallyDeclaredNames of ModuleItemList contains any duplicate entries.
467        let mut bindings = FxHashSet::default();
468        for name in lexically_declared_names(&module) {
469            if !bindings.insert(name) {
470                return Err(Error::general(
471                    format!(
472                        "lexical name `{}` declared multiple times",
473                        interner.resolve_expect(name)
474                    ),
475                    Position::new(1, 1),
476                ));
477            }
478        }
479
480        // It is a Syntax Error if any element of the LexicallyDeclaredNames of ModuleItemList also occurs in the
481        // VarDeclaredNames of ModuleItemList.
482        for name in var_declared_names(&module) {
483            if !bindings.insert(name) {
484                return Err(Error::general(
485                    format!(
486                        "lexical name `{}` declared multiple times",
487                        interner.resolve_expect(name)
488                    ),
489                    Position::new(1, 1),
490                ));
491            }
492        }
493
494        // It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
495        {
496            let mut exported_names = FxHashSet::default();
497            for name in module.items().exported_names() {
498                if !exported_names.insert(name) {
499                    return Err(Error::general(
500                        format!(
501                            "exported name `{}` declared multiple times",
502                            interner.resolve_expect(name)
503                        ),
504                        Position::new(1, 1),
505                    ));
506                }
507            }
508        }
509
510        // It is a Syntax Error if any element of the ExportedBindings of ModuleItemList does not also occur in either
511        // the VarDeclaredNames of ModuleItemList, or the LexicallyDeclaredNames of ModuleItemList.
512        for name in module.items().exported_bindings() {
513            if !bindings.contains(&name) {
514                return Err(Error::general(
515                    format!(
516                        "could not find the exported binding `{}` in the declared names of the module",
517                        interner.resolve_expect(name)
518                    ),
519                    Position::new(1, 1),
520                ));
521            }
522        }
523
524        // It is a Syntax Error if ModuleItemList Contains super.
525        if contains(&module, ContainsSymbol::Super) {
526            return Err(Error::general(
527                "module cannot contain `super` on the top-level",
528                Position::new(1, 1),
529            ));
530        }
531
532        // It is a Syntax Error if ModuleItemList Contains NewTarget.
533        if contains(&module, ContainsSymbol::NewTarget) {
534            return Err(Error::general(
535                "module cannot contain `new.target` on the top-level",
536                Position::new(1, 1),
537            ));
538        }
539
540        // It is a Syntax Error if ContainsDuplicateLabels of ModuleItemList with argument « » is true.
541        // It is a Syntax Error if ContainsUndefinedBreakTarget of ModuleItemList with argument « » is true.
542        // It is a Syntax Error if ContainsUndefinedContinueTarget of ModuleItemList with arguments « » and « » is true.
543        check_labels(&module).map_err(|error| {
544            Error::lex(LexError::Syntax(
545                error.message(interner).into(),
546                Position::new(1, 1),
547            ))
548        })?;
549
550        // It is a Syntax Error if AllPrivateIdentifiersValid of ModuleItemList with argument « » is false.
551        if !all_private_identifiers_valid(&module, Vec::new()) {
552            return Err(Error::general(
553                "invalid private identifier usage",
554                Position::new(1, 1),
555            ));
556        }
557
558        let source = cursor.take_source();
559        Ok((module, source))
560    }
561}
562
563/// Helper to check if any parameter names are declared in the given list.
564fn name_in_lexically_declared_names(
565    bound_names: &[Sym],
566    lexical_names: &[Sym],
567    position: Position,
568    interner: &Interner,
569) -> ParseResult<()> {
570    for name in bound_names {
571        if lexical_names.contains(name) {
572            return Err(Error::general(
573                format!(
574                    "formal parameter `{}` declared in lexically declared names",
575                    interner.resolve_expect(*name)
576                ),
577                position,
578            ));
579        }
580    }
581    Ok(())
582}
583
584/// Trait to reduce boilerplate in the parser.
585trait OrAbrupt<T> {
586    /// Will convert an `Ok(None)` to an [`Error::AbruptEnd`] or return the inner type if not.
587    fn or_abrupt(self) -> ParseResult<T>;
588}
589
590impl<T> OrAbrupt<T> for ParseResult<Option<T>> {
591    fn or_abrupt(self) -> ParseResult<T> {
592        self?.ok_or(Error::AbruptEnd)
593    }
594}