diff --git a/.gitignore b/.gitignore index 2b86ce0b77a4..a351d866fd02 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,8 @@ Cargo.lock .DS_Store -node_modules/ \ No newline at end of file +node_modules/ + +# Flamegraph +*.html +*.svg \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 54bd61019d15..9b24451163ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ sourcemap = "2.2" name = "usage" [profile.bench] +codegen-units = 1 lto = true [profile.release] diff --git a/ecmascript/parser/src/lexer/jsx.rs b/ecmascript/parser/src/lexer/jsx.rs index 569a84173edc..1a9f8d2d024e 100644 --- a/ecmascript/parser/src/lexer/jsx.rs +++ b/ecmascript/parser/src/lexer/jsx.rs @@ -199,8 +199,8 @@ impl<'a, I: Input> Lexer<'a, I> { /// by isIdentifierStart in readToken. pub(super) fn read_jsx_word(&mut self) -> LexResult { debug_assert!(self.syntax.jsx()); - assert!(self.input.cur().is_some()); - assert!(self.input.cur().unwrap().is_ident_start()); + debug_assert!(self.input.cur().is_some()); + debug_assert!(self.input.cur().unwrap().is_ident_start()); let cur_pos = self.input.cur_pos(); let mut first = true; diff --git a/ecmascript/parser/src/lexer/mod.rs b/ecmascript/parser/src/lexer/mod.rs index 5848289988a6..796c6600cfff 100644 --- a/ecmascript/parser/src/lexer/mod.rs +++ b/ecmascript/parser/src/lexer/mod.rs @@ -360,7 +360,7 @@ impl<'a, I: Input> Lexer<'a, I> { /// `#` fn read_token_number_sign(&mut self) -> LexResult> { - assert!(self.cur().is_some()); + debug_assert!(self.cur().is_some()); let start = self.input.cur_pos(); @@ -538,7 +538,7 @@ impl<'a, I: Input> Lexer<'a, I> { } fn read_token_lt_gt(&mut self) -> LexResult> { - assert!(self.cur() == Some('<') || self.cur() == Some('>')); + debug_assert!(self.cur() == Some('<') || self.cur() == Some('>')); let start = self.cur_pos(); let c = self.cur().unwrap(); @@ -586,7 +586,7 @@ impl<'a, I: Input> Lexer<'a, I> { /// See https://tc39.github.io/ecma262/#sec-names-and-keywords fn read_ident_or_keyword(&mut self) -> LexResult { - assert!(self.cur().is_some()); + debug_assert!(self.cur().is_some()); let start = self.cur_pos(); let (word, has_escape) = self.read_word_as_str()?; @@ -611,7 +611,7 @@ impl<'a, I: Input> Lexer<'a, I> { /// returns (word, has_escape) fn read_word_as_str(&mut self) -> LexResult<(JsWord, bool)> { - assert!(self.cur().is_some()); + debug_assert!(self.cur().is_some()); let mut has_escape = false; let mut word = String::new(); @@ -715,7 +715,7 @@ impl<'a, I: Input> Lexer<'a, I> { /// See https://tc39.github.io/ecma262/#sec-literals-string-literals fn read_str_lit(&mut self) -> LexResult { - assert!(self.cur() == Some('\'') || self.cur() == Some('"')); + debug_assert!(self.cur() == Some('\'') || self.cur() == Some('"')); let start = self.cur_pos(); let quote = self.cur().unwrap(); self.bump(); // '"' @@ -765,7 +765,6 @@ impl<'a, I: Input> Lexer<'a, I> { self.bump(); let (mut escaped, mut in_class) = (false, false); - // TODO: Optimize (chunk, cow) let mut content = String::new(); let content_start = self.cur_pos(); diff --git a/ecmascript/parser/src/lexer/number.rs b/ecmascript/parser/src/lexer/number.rs index ad4d7b8116d0..d7a516ed0ea1 100644 --- a/ecmascript/parser/src/lexer/number.rs +++ b/ecmascript/parser/src/lexer/number.rs @@ -2,15 +2,14 @@ //! //! //! See https://tc39.github.io/ecma262/#sec-literals-numeric-literals - use super::*; use crate::error::SyntaxError; -use std::fmt::Display; +use std::{fmt::Write, iter::FusedIterator}; impl<'a, I: Input> Lexer<'a, I> { /// Reads an integer, octal integer, or floating-point number pub(super) fn read_number(&mut self, starts_with_dot: bool) -> LexResult { - assert!(self.cur().is_some()); + debug_assert!(self.cur().is_some()); if starts_with_dot { debug_assert_eq!( self.cur(), @@ -48,21 +47,24 @@ impl<'a, I: Input> Lexer<'a, I> { // strict mode hates non-zero decimals starting with zero. // e.g. 08.1 is strict mode violation but 0.1 is valid float. - let s = format!("{}", val); // TODO: Remove allocation. - - // if it contains '8' or '9', it's decimal. - if s.contains('8') || s.contains('9') { - if self.ctx.strict { - self.error(start, SyntaxError::LegacyDecimal)? + if val.fract() < 1e-10 { + let d = digits(val.round() as u64, 10); + + // if it contains '8' or '9', it's decimal. + if d.clone().any(|v| v == 8 || v == 9) { + if self.ctx.strict { + self.error(start, SyntaxError::LegacyDecimal)? + } + } else { + // It's Legacy octal, and we should reinterpret value. + let val = u64::from_str_radix(&val.to_string(), 8) + .expect("Does this can really happen?"); + let val = val + .to_string() + .parse() + .expect("failed to parse numeric value as f64"); + return self.make_legacy_octal(start, val); } - } else { - // It's Legacy octal, and we should reinterpret value. - let val = u64::from_str_radix(&format!("{}", val), 8) - .expect("Does this can really happen?"); - let val = format!("{}", val) - .parse() - .expect("failed to parse numeric value as f64"); - return self.make_legacy_octal(start, val); } } } @@ -80,23 +82,20 @@ impl<'a, I: Input> Lexer<'a, I> { if self.cur() == Some('.') { self.bump(); if starts_with_dot { - assert!(self.cur().is_some()); - assert!(self.cur().unwrap().is_digit(10)); + debug_assert!(self.cur().is_some()); + debug_assert!(self.cur().unwrap().is_digit(10)); } // Read numbers after dot let dec_val = self.read_int(10, 0, &mut Raw(None))?; + let mut s = String::new(); + write!(s, "{}.", val).unwrap(); - let dec: &dyn Display = match dec_val { - Some(ref n) => n, - // "0.", "0.e1" is valid - None => &"", - }; + if let Some(ref n) = dec_val { + write!(s, "{}", n).unwrap(); + } - // TODO - val = format!("{}.{}", val, dec) - .parse() - .expect("failed to parse float using rust's impl"); + val = s.parse().expect("failed to parse float using rust's impl"); } // Handle 'e' and 'E' @@ -307,6 +306,46 @@ impl<'a, I: Input> Lexer<'a, I> { } } +fn digits(value: u64, radix: u64) -> impl Iterator + Clone + 'static { + debug_assert!(radix > 0); + + #[derive(Clone, Copy)] + struct Digits { + n: u64, + divisor: u64, + } + + impl Digits { + fn new(n: u64, radix: u64) -> Self { + let mut divisor = 1; + while n >= divisor * radix { + divisor *= radix; + } + + Digits { n, divisor } + } + } + + impl Iterator for Digits { + type Item = u64; + + fn next(&mut self) -> Option { + if self.divisor == 0 { + None + } else { + let v = Some(self.n / self.divisor); + self.n %= self.divisor; + self.divisor /= 10; + v + } + } + } + + impl FusedIterator for Digits {} + + Digits::new(value, radix) +} + #[cfg(test)] mod tests { use super::{input::SourceFileInput, *}; @@ -339,19 +378,19 @@ mod tests { 0000000000000000000000000000000000000000000000000000"; #[test] fn num_inf() { - assert_eq!(num(LONG), INFINITY); + debug_assert_eq!(num(LONG), INFINITY); } /// Number >= 2^53 #[test] fn num_big_exp() { - assert_eq!(1e30, num("1e30")); + debug_assert_eq!(1e30, num("1e30")); } #[test] #[ignore] fn num_big_many_zero() { - assert_eq!( + debug_assert_eq!( 1_000_000_000_000_000_000_000_000_000_000f64, num("1000000000000000000000000000000") ) @@ -359,23 +398,23 @@ mod tests { #[test] fn num_legacy_octal() { - assert_eq!(0o12 as f64, num("0012")); + debug_assert_eq!(0o12 as f64, num("0012")); } #[test] fn read_int_1() { - assert_eq!(60, int(10, "60")); - assert_eq!(0o73, int(8, "73")); + debug_assert_eq!(60, int(10, "60")); + debug_assert_eq!(0o73, int(8, "73")); } #[test] fn read_int_short() { - assert_eq!(7, int(10, "7")); + debug_assert_eq!(7, int(10, "7")); } #[test] fn read_radix_number() { - assert_eq!( + debug_assert_eq!( 0o73 as f64, lex("0o73", |l| l.read_radix_number(8).unwrap()) ); @@ -418,11 +457,11 @@ mod tests { Ok(vec) => vec, Err(err) => panic::resume_unwind(err), }; - assert_eq!(vec.len(), 1); + debug_assert_eq!(vec.len(), 1); let token = vec.into_iter().next().unwrap(); - assert_eq!(Num(expected), token); + debug_assert_eq!(Num(expected), token); } else if let Ok(vec) = vec { - assert_ne!(vec![Num(expected)], vec) + debug_assert_ne!(vec![Num(expected)], vec) } } } diff --git a/ecmascript/parser/src/lexer/state.rs b/ecmascript/parser/src/lexer/state.rs index 07ac7d1efafd..4d6dad9297ab 100644 --- a/ecmascript/parser/src/lexer/state.rs +++ b/ecmascript/parser/src/lexer/state.rs @@ -236,11 +236,11 @@ impl<'a, I: Input> Iterator for Lexer<'a, I> { ); } self.state.update(start, &token); - self.state.prev_hi = span.hi(); + self.state.prev_hi = self.last_pos(); } token.map(|token| { - // Attatch span to token. + // Attach span to token. TokenAndSpan { token, had_line_break: self.had_line_break_before_last(), @@ -600,7 +600,7 @@ where let res = f(&mut l); let c: SmallVec<[TokenContext; 32]> = smallvec![TokenContext::BraceStmt]; - assert_eq!(l.state.context.0, c); + debug_assert_eq!(l.state.context.0, c); res }) diff --git a/ecmascript/parser/src/lexer/tests.rs b/ecmascript/parser/src/lexer/tests.rs index 9987fb5e3284..d13c9421528c 100644 --- a/ecmascript/parser/src/lexer/tests.rs +++ b/ecmascript/parser/src/lexer/tests.rs @@ -102,7 +102,7 @@ impl WithSpan for AssignOpToken { //#[test] //fn module_legacy_octal() { -// assert_eq!( +// debug_assert_eq!( // lex_module(Syntax::default(), "01"), // vec![Token::Error(Error { // span: sp(0..2), @@ -115,7 +115,7 @@ impl WithSpan for AssignOpToken { #[test] fn module_legacy_decimal() { - assert_eq!( + debug_assert_eq!( lex_module(Syntax::default(), "08"), vec![Token::Error(Error { span: sp(0..2), @@ -128,7 +128,7 @@ fn module_legacy_decimal() { #[test] fn module_legacy_comment_1() { - assert_eq!( + debug_assert_eq!( lex_module(Syntax::default(), ""), vec![Token::Error(Error { span: sp(0..3), @@ -154,7 +154,7 @@ fn module_legacy_comment_2() { #[test] fn test262_lexer_error_0001() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "123..a(1)"), vec![ 123f64.span(0..4).lb(), @@ -169,7 +169,7 @@ fn test262_lexer_error_0001() { #[test] fn test262_lexer_error_0002() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"'use\x20strict';"#), vec![ Token::Str { @@ -185,7 +185,7 @@ fn test262_lexer_error_0002() { #[test] fn test262_lexer_error_0003() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"\u0061"#), vec!["a".span(0..6).lb()] ); @@ -193,7 +193,7 @@ fn test262_lexer_error_0003() { #[test] fn test262_lexer_error_0004() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), "+{} / 1"), vec![tok!('+'), tok!('{'), tok!('}'), tok!('/'), 1.into_token()] ); @@ -201,7 +201,7 @@ fn test262_lexer_error_0004() { #[test] fn ident_escape_unicode() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"a\u0061"#), vec!["aa".span(0..7).lb()] ); @@ -209,9 +209,9 @@ fn ident_escape_unicode() { #[test] fn ident_escape_unicode_2() { - assert_eq!(lex(Syntax::default(), "℘℘"), vec!["℘℘".span(0..6).lb()]); + debug_assert_eq!(lex(Syntax::default(), "℘℘"), vec!["℘℘".span(0..6).lb()]); - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"℘\u2118"#), vec!["℘℘".span(0..9).lb()] ); @@ -219,7 +219,7 @@ fn ident_escape_unicode_2() { #[test] fn tpl_multiline() { - assert_eq!( + debug_assert_eq!( lex_tokens( Syntax::default(), "`this @@ -240,7 +240,7 @@ multiline`" #[test] fn tpl_raw_unicode_escape() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), r"`\u{0010}`"), vec![ tok!('`'), @@ -256,7 +256,7 @@ fn tpl_raw_unicode_escape() { #[test] fn str_escape() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), r#"'\n'"#), vec![Token::Str { value: "\n".into(), @@ -267,7 +267,7 @@ fn str_escape() { #[test] fn str_escape_2() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), r#"'\\n'"#), vec![Token::Str { value: "\\n".into(), @@ -278,7 +278,7 @@ fn str_escape_2() { #[test] fn str_escape_hex() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"'\x61'"#), vec![Token::Str { value: "a".into(), @@ -291,7 +291,7 @@ fn str_escape_hex() { #[test] fn str_escape_octal() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"'Hello\012World'"#), vec![Token::Str { value: "Hello\nWorld".into(), @@ -304,7 +304,7 @@ fn str_escape_octal() { #[test] fn str_escape_unicode_long() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), r#"'\u{00000000034}'"#), vec![Token::Str { value: "4".into(), @@ -317,7 +317,7 @@ fn str_escape_unicode_long() { #[test] fn regexp_unary_void() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "void /test/"), vec![ Void.span(0..4).lb(), @@ -332,7 +332,7 @@ fn regexp_unary_void() { .span(5..11), ] ); - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "void (/test/)"), vec![ Void.span(0..4).lb(), @@ -353,7 +353,7 @@ fn regexp_unary_void() { #[test] fn non_regexp_unary_plus() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "+{} / 1"), vec![ tok!('+').span(0..1).lb(), @@ -369,7 +369,7 @@ fn non_regexp_unary_plus() { #[test] fn paren_semi() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "();"), vec![LParen.span(0).lb(), RParen.span(1), Semi.span(2)] ); @@ -377,7 +377,7 @@ fn paren_semi() { #[test] fn ident_paren() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "a(bc);"), vec![ "a".span(0).lb(), @@ -391,7 +391,7 @@ fn ident_paren() { #[test] fn read_word() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "a b c"), vec!["a".span(0).lb(), "b".span(2), "c".span(4)] ) @@ -399,7 +399,7 @@ fn read_word() { #[test] fn simple_regex() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "x = /42/i"), vec![ "x".span(0).lb(), @@ -420,7 +420,7 @@ fn simple_regex() { ], ); - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "/42/"), vec![Regex( Str { @@ -468,7 +468,7 @@ fn complex_regex() { #[test] fn simple_div() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "a / b"), vec!["a".span(0).lb(), Div.span(2), "b".span(4)], ); @@ -476,7 +476,7 @@ fn simple_div() { #[test] fn complex_divide() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), "x = function foo() {} /a/i"), vec![ Word(Word::Ident("x".into())), @@ -521,7 +521,7 @@ fn spec_001() { Semi, ]; - assert_eq!( + debug_assert_eq!( lex_tokens( Syntax::default(), "a = b @@ -529,7 +529,7 @@ fn spec_001() { ), expected ); - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), "a = b / hi / g.exec(c).map(d);"), expected ); @@ -539,7 +539,7 @@ fn spec_001() { #[test] fn after_if() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "if(x){} /y/.test(z)"), vec![ Keyword::If.span(0..2).lb(), @@ -569,7 +569,7 @@ fn after_if() { // #[test] // #[ignore] // fn leading_comment() { -// assert_eq!( +// debug_assert_eq!( // vec![ // BlockComment(" hello world ".into()).span(0..17), // Regex("42".into(), "".into()).span(17..21), @@ -581,7 +581,7 @@ fn after_if() { // #[test] // #[ignore] // fn line_comment() { -// assert_eq!( +// debug_assert_eq!( // vec![ // Keyword::Var.span(0..3), // "answer".span(4..10), @@ -595,7 +595,7 @@ fn after_if() { #[test] fn migrated_0002() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "tokenize(/42/)"), vec![ "tokenize".span(0..8).lb(), @@ -616,7 +616,7 @@ fn migrated_0002() { #[test] fn migrated_0003() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "(false) /42/"), vec![ LParen.span(0).lb(), @@ -631,7 +631,7 @@ fn migrated_0003() { #[test] fn migrated_0004() { - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "function f(){} /42/"), vec![ Function.span(0..8).lb(), @@ -657,7 +657,7 @@ fn migrated_0004() { // // #[test] // fn migrated_0005() { -// assert_eq!( +// debug_assert_eq!( // vec![ // Function.span(0..8), // LParen.span(9), @@ -674,12 +674,12 @@ fn migrated_0004() { #[test] fn migrated_0006() { // This test seems wrong. - // assert_eq!( + // debug_assert_eq!( // vec![LBrace.span(0).lb(), RBrace.span(1), Div.span(3), 42.span(4..6)], // lex(Syntax::default(), "{} /42") // ) - assert_eq!( + debug_assert_eq!( lex(Syntax::default(), "{} /42/"), vec![ LBrace.span(0).lb(), @@ -699,7 +699,7 @@ fn migrated_0006() { #[test] fn str_lit() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), "'abcde'"), vec![Token::Str { value: "abcde".into(), @@ -717,7 +717,7 @@ fn str_lit() { #[test] fn tpl_empty() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), r#"``"#), vec![ tok!('`'), @@ -733,7 +733,7 @@ fn tpl_empty() { #[test] fn tpl() { - assert_eq!( + debug_assert_eq!( lex_tokens(Syntax::default(), r#"`${a}`"#), vec![ tok!('`'), @@ -757,7 +757,7 @@ fn tpl() { #[test] fn comment() { - assert_eq!( + debug_assert_eq!( lex( Syntax::default(), "// foo @@ -774,7 +774,7 @@ a" #[test] fn comment_2() { - assert_eq!( + debug_assert_eq!( lex( Syntax::default(), "// foo @@ -792,7 +792,7 @@ a" #[test] fn jsx_01() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -811,7 +811,7 @@ fn jsx_01() { #[test] fn jsx_02() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -834,7 +834,7 @@ fn jsx_02() { #[test] fn jsx_03() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -862,7 +862,7 @@ fn jsx_03() { #[test] fn jsx_04() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -891,7 +891,7 @@ fn max_integer() { #[test] fn shebang() { - assert_eq!( + debug_assert_eq!( lex_tokens(crate::Syntax::default(), "#!/usr/bin/env node",), vec![Token::Shebang("/usr/bin/env node".into())] ); @@ -899,12 +899,12 @@ fn shebang() { #[test] fn empty() { - assert_eq!(lex_tokens(crate::Syntax::default(), "",), vec![]); + debug_assert_eq!(lex_tokens(crate::Syntax::default(), "",), vec![]); } #[test] fn issue_191() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -934,7 +934,7 @@ fn issue_191() { #[test] fn jsx_05() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -960,7 +960,7 @@ fn jsx_05() { #[test] fn issue_299_01() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -994,7 +994,7 @@ fn issue_299_01() { #[test] fn issue_299_02() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -1028,7 +1028,7 @@ fn issue_299_02() { #[test] fn issue_299_03() { - assert_eq!( + debug_assert_eq!( lex_tokens( crate::Syntax::Es(crate::EsConfig { jsx: true, @@ -1062,7 +1062,7 @@ fn issue_299_03() { #[test] fn issue_316() { - assert_eq!( + debug_assert_eq!( lex_tokens(Default::default(), "'Hi\\r\\n..'"), vec![Token::Str { value: "Hi\r\n..".into(), @@ -1073,7 +1073,7 @@ fn issue_316() { #[test] fn issue_401() { - assert_eq!( + debug_assert_eq!( lex_tokens(Default::default(), "'17' as const"), vec![ Token::Str { @@ -1101,3 +1101,19 @@ fn lex_colors_js(b: &mut Bencher) { ); }); } + +#[bench] +fn lex_colors_ts(b: &mut Bencher) { + b.bytes = include_str!("../../colors.js").len() as _; + + b.iter(|| { + let _ = with_lexer( + Syntax::Typescript(Default::default()), + include_str!("../../colors.js"), + |lexer| { + for _ in lexer {} + Ok(()) + }, + ); + }); +} diff --git a/ecmascript/parser/src/lexer/util.rs b/ecmascript/parser/src/lexer/util.rs index 17d440bf34a6..740b65bd6ea2 100644 --- a/ecmascript/parser/src/lexer/util.rs +++ b/ecmascript/parser/src/lexer/util.rs @@ -222,7 +222,7 @@ impl<'a, I: Input> Lexer<'a, I> { while let Some(c) = self.cur() { if was_star && c == '/' { - assert_eq!(self.cur(), Some('/')); + debug_assert_eq!(self.cur(), Some('/')); self.bump(); // '/' let pos = self.cur_pos(); diff --git a/ecmascript/parser/src/parser/class_and_fn.rs b/ecmascript/parser/src/parser/class_and_fn.rs index b2dcc7522ece..68bc95c7b30c 100644 --- a/ecmascript/parser/src/parser/class_and_fn.rs +++ b/ecmascript/parser/src/parser/class_and_fn.rs @@ -17,34 +17,34 @@ impl<'a, I: Tokens> Parser<'a, I> { self.parse_fn(None, vec![]) } - pub(super) fn parse_async_fn_decl(&mut self, decoraters: Vec) -> PResult<'a, Decl> { + pub(super) fn parse_async_fn_decl(&mut self, decorators: Vec) -> PResult<'a, Decl> { let start = cur_pos!(); expect!("async"); - self.parse_fn(Some(start), decoraters) + self.parse_fn(Some(start), decorators) } - pub(super) fn parse_fn_decl(&mut self, decoraters: Vec) -> PResult<'a, Decl> { - self.parse_fn(None, decoraters) + pub(super) fn parse_fn_decl(&mut self, decorators: Vec) -> PResult<'a, Decl> { + self.parse_fn(None, decorators) } pub(super) fn parse_default_async_fn( &mut self, - decoraters: Vec, + decorators: Vec, ) -> PResult<'a, ExportDefaultDecl> { let start = cur_pos!(); expect!("async"); - self.parse_fn(Some(start), decoraters) + self.parse_fn(Some(start), decorators) } pub(super) fn parse_default_fn( &mut self, - decoraters: Vec, + decorators: Vec, ) -> PResult<'a, ExportDefaultDecl> { - self.parse_fn(None, decoraters) + self.parse_fn(None, decorators) } - pub(super) fn parse_class_decl(&mut self, decoraters: Vec) -> PResult<'a, Decl> { - self.parse_class(decoraters) + pub(super) fn parse_class_decl(&mut self, decorators: Vec) -> PResult<'a, Decl> { + self.parse_class(decorators) } pub(super) fn parse_class_expr( @@ -56,9 +56,9 @@ impl<'a, I: Tokens> Parser<'a, I> { pub(super) fn parse_default_class( &mut self, - decoraters: Vec, + decorators: Vec, ) -> PResult<'a, ExportDefaultDecl> { - self.parse_class(decoraters) + self.parse_class(decorators) } fn parse_class(&mut self, decorators: Vec) -> PResult<'a, T> diff --git a/ecmascript/parser/src/parser/expr/mod.rs b/ecmascript/parser/src/parser/expr/mod.rs index 28fb53a4ebe6..3315285b547b 100644 --- a/ecmascript/parser/src/parser/expr/mod.rs +++ b/ecmascript/parser/src/parser/expr/mod.rs @@ -37,9 +37,9 @@ impl<'a, I: Tokens> Parser<'a, I> { if is!(JSXTagStart) { let cur_context = self.input.token_context().current(); - assert_eq!(cur_context, Some(TokenContext::JSXOpeningTag)); + debug_assert_eq!(cur_context, Some(TokenContext::JSXOpeningTag)); // Only time j_oTag is pushed is right after j_expr. - assert_eq!( + debug_assert_eq!( self.input.token_context().0[self.input.token_context().len() - 2], TokenContext::JSXExpr ); @@ -48,12 +48,12 @@ impl<'a, I: Tokens> Parser<'a, I> { if let Some(res) = res { return Ok(res); } else { - assert_eq!( + debug_assert_eq!( self.input.token_context().current(), Some(TokenContext::JSXOpeningTag) ); self.input.token_context_mut().pop(); - assert_eq!( + debug_assert_eq!( self.input.token_context().current(), Some(TokenContext::JSXExpr) ); @@ -62,22 +62,27 @@ impl<'a, I: Tokens> Parser<'a, I> { } } - let res = self.try_parse_ts(|p| { - let type_parameters = p.parse_ts_type_params()?; - let mut arrow = p.parse_assignment_expr_base()?; - match *arrow { - Expr::Arrow(ArrowExpr { - ref mut type_params, - .. - }) => { - *type_params = Some(type_parameters); + if self.input.syntax().typescript() + && (is_one_of!('<', JSXTagStart)) + && peeked_is!(IdentName) + { + let res = self.try_parse_ts(|p| { + let type_parameters = p.parse_ts_type_params()?; + let mut arrow = p.parse_assignment_expr_base()?; + match *arrow { + Expr::Arrow(ArrowExpr { + ref mut type_params, + .. + }) => { + *type_params = Some(type_parameters); + } + _ => unexpected!(), } - _ => unexpected!(), + Ok(Some(arrow)) + }); + if let Some(res) = res { + return Ok(res); } - Ok(Some(arrow)) - }); - if let Some(res) = res { - return Ok(res); } self.parse_assignment_expr_base() @@ -287,7 +292,7 @@ impl<'a, I: Tokens> Parser<'a, I> { match id.sym { // js_word!("eval") | js_word!("arguments") => { // self.emit_err(id.span, - // SyntaxError::EvalAndArgumentsInStrict) + // SyntaxError::EvalAndArgumentsInStrict) // } js_word!("yield") | js_word!("static") @@ -760,8 +765,6 @@ impl<'a, I: Tokens> Parser<'a, I> { )); } - // TODO(kdy1): Remove this. - let obj = obj.clone(); if { match obj { ExprOrSuper::Expr(..) => true, @@ -770,13 +773,14 @@ impl<'a, I: Tokens> Parser<'a, I> { } } && is!('<') { + let obj_ref = &obj; // tsTryParseAndCatch is expensive, so avoid if not necessary. // There are number of things we are going to "maybe" parse, like type arguments // on tagged template expressions. If any of them fail, walk it back and // continue. let result = self.try_parse_ts(|p| { if !no_call - && p.at_possible_async(match obj { + && p.at_possible_async(match obj_ref { ExprOrSuper::Expr(ref expr) => &*expr, _ => unreachable!(), })? @@ -799,7 +803,7 @@ impl<'a, I: Tokens> Parser<'a, I> { Ok(Some(( Box::new(Expr::Call(CallExpr { span: span!(start), - callee: obj, + callee: obj_ref.clone(), type_args: Some(type_args), args, })), @@ -807,8 +811,8 @@ impl<'a, I: Tokens> Parser<'a, I> { ))) } else if is!('`') { p.parse_tagged_tpl( - match obj { - ExprOrSuper::Expr(obj) => obj, + match *obj_ref { + ExprOrSuper::Expr(ref obj) => obj.clone(), _ => unreachable!(), }, Some(type_args), @@ -985,7 +989,7 @@ impl<'a, I: Tokens> Parser<'a, I> { // This fails with `expected (` expect!('('); } - assert_ne!( + debug_assert_ne!( cur!(false).ok(), Some(&tok!('(')), "parse_new_expr() should eat paren if it exists" diff --git a/ecmascript/parser/src/parser/expr/tests.rs b/ecmascript/parser/src/parser/expr/tests.rs index 52099f1ec781..a6b62b7d5fdc 100644 --- a/ecmascript/parser/src/parser/expr/tests.rs +++ b/ecmascript/parser/src/parser/expr/tests.rs @@ -1,6 +1,8 @@ use super::*; use crate::EsConfig; +use std::hint::black_box; use swc_common::DUMMY_SP as span; +use test::Bencher; fn syntax() -> Syntax { Syntax::Es(EsConfig { @@ -407,3 +409,45 @@ fn issue_380() { }", ); } + +#[bench] +fn bench_new_expr_ts(b: &mut Bencher) { + bench_parser( + b, + "new Foo()", + Syntax::Typescript(Default::default()), + |p| { + black_box(p.parse_expr()?); + Ok(()) + }, + ); +} + +#[bench] +fn bench_new_expr_es(b: &mut Bencher) { + bench_parser(b, "new Foo()", Syntax::Es(Default::default()), |p| { + black_box(p.parse_expr()?); + Ok(()) + }); +} + +#[bench] +fn bench_member_expr_ts(b: &mut Bencher) { + bench_parser( + b, + "a.b.c.d.e.f", + Syntax::Typescript(Default::default()), + |p| { + black_box(p.parse_expr()?); + Ok(()) + }, + ); +} + +#[bench] +fn bench_member_expr_es(b: &mut Bencher) { + bench_parser(b, "a.b.c.d.e.f", Syntax::Es(Default::default()), |p| { + black_box(p.parse_expr()?); + Ok(()) + }); +} diff --git a/ecmascript/parser/src/parser/input/mod.rs b/ecmascript/parser/src/parser/input/mod.rs index da33dbc85932..350c3ab58d31 100644 --- a/ecmascript/parser/src/parser/input/mod.rs +++ b/ecmascript/parser/src/parser/input/mod.rs @@ -172,8 +172,8 @@ impl Buffer { } pub fn store(&mut self, token: Token) { - assert!(self.next.is_none()); - assert!(self.cur.is_none()); + debug_assert!(self.next.is_none()); + debug_assert!(self.cur.is_none()); let span = self.prev_span; self.cur = Some(TokenAndSpan { diff --git a/ecmascript/parser/src/parser/jsx/mod.rs b/ecmascript/parser/src/parser/jsx/mod.rs index fbd47b39a991..c9a79a27d6c9 100644 --- a/ecmascript/parser/src/parser/jsx/mod.rs +++ b/ecmascript/parser/src/parser/jsx/mod.rs @@ -184,7 +184,7 @@ impl<'a, I: Tokens> Parser<'a, I> { debug_assert!(self.input.syntax().jsx()); let start = name.span().lo(); - let type_args = if self.input.syntax().typescript() { + let type_args = if self.input.syntax().typescript() && is!('<') { self.try_parse_ts(|p| p.parse_ts_type_args().map(Some)) } else { None @@ -356,7 +356,7 @@ impl<'a, I: Tokens> Parser<'a, I> { /// babel: `jsxParseElement` pub(super) fn parse_jsx_element(&mut self) -> PResult<'a, Either> { debug_assert!(self.input.syntax().jsx()); - assert!({ + debug_assert!({ match *cur!(true)? { Token::JSXTagStart | tok!('<') => true, _ => false, @@ -370,7 +370,7 @@ impl<'a, I: Tokens> Parser<'a, I> { pub(super) fn parse_jsx_text(&mut self) -> PResult<'a, JSXText> { debug_assert!(self.input.syntax().jsx()); - assert!({ + debug_assert!({ match cur!(false) { Ok(&Token::JSXText { .. }) => true, _ => false, diff --git a/ecmascript/parser/src/parser/macros.rs b/ecmascript/parser/src/parser/macros.rs index bfa442e304b8..413a3f2dde0c 100644 --- a/ecmascript/parser/src/parser/macros.rs +++ b/ecmascript/parser/src/parser/macros.rs @@ -249,7 +249,7 @@ Current token is {:?}", macro_rules! bump { ($p:expr) => {{ - assert!( + debug_assert!( $p.input.knows_cur(), "parser should not call bump() without knowing current token" ); diff --git a/ecmascript/parser/src/parser/mod.rs b/ecmascript/parser/src/parser/mod.rs index 71b87bb6c42a..4560386b7d39 100644 --- a/ecmascript/parser/src/parser/mod.rs +++ b/ecmascript/parser/src/parser/mod.rs @@ -13,6 +13,8 @@ use ast::*; use std::ops::{Deref, DerefMut}; use swc_atoms::JsWord; use swc_common::{comments::Comments, errors::DiagnosticBuilder, input::Input, BytePos, Span}; +#[cfg(test)] +use test::Bencher; #[macro_use] mod macros; @@ -96,7 +98,7 @@ impl<'a, I: Tokens> Parser<'a, I> { } pub fn parse_typescript_module(&mut self) -> PResult<'a, Module> { - assert!(self.syntax().typescript()); + debug_assert!(self.syntax().typescript()); //TODO: parse() -> PResult<'a, Program> let ctx = Context { @@ -171,3 +173,21 @@ where }) .unwrap_or_else(|output| panic!("test_parser(): failed to parse \n{}\n{}", s, output)) } + +#[cfg(test)] +pub fn bench_parser(b: &mut Bencher, s: &'static str, syntax: Syntax, mut f: F) +where + F: for<'a> FnMut(&'a mut Parser<'a, Lexer<'a, crate::SourceFileInput<'_>>>) -> PResult<'a, ()>, +{ + b.bytes = s.len() as u64; + + let _ = crate::with_test_sess(s, |sess, input| { + b.iter(|| { + let _ = f(&mut Parser::new(sess, syntax, input.clone(), None)).map_err(|mut err| { + err.emit(); + }); + }); + + Ok(()) + }); +} diff --git a/ecmascript/parser/src/parser/stmt/mod.rs b/ecmascript/parser/src/parser/stmt/mod.rs index 3183ff0276cd..4994e62a4e73 100644 --- a/ecmascript/parser/src/parser/stmt/mod.rs +++ b/ecmascript/parser/src/parser/stmt/mod.rs @@ -550,17 +550,21 @@ impl<'a, I: Tokens> Parser<'a, I> { let should_include_in = kind != VarDeclKind::Var || !for_loop; if self.syntax().typescript() && for_loop { - let res = self.ts_look_ahead(|p| { - // - if !eat!("of") && !eat!("in") { - return Ok(false); - } + let res = if is_one_of!("in", "of") { + self.ts_look_ahead(|p| { + // + if !eat!("of") && !eat!("in") { + return Ok(false); + } - p.parse_assignment_expr()?; - expect!(')'); + p.parse_assignment_expr()?; + expect!(')'); - Ok(true) - }); + Ok(true) + }) + } else { + Ok(false) + }; match res { Ok(true) => { diff --git a/ecmascript/parser/src/parser/typescript.rs b/ecmascript/parser/src/parser/typescript.rs index 54b1aca72149..1a8f310ed676 100644 --- a/ecmascript/parser/src/parser/typescript.rs +++ b/ecmascript/parser/src/parser/typescript.rs @@ -358,7 +358,7 @@ impl<'a, I: Tokens> Parser<'a, I> { syntax_error!(span, SyntaxError::Expected(return_token, cur)) } - let type_pred_var = if is!(IdentRef) { + let type_pred_var = if is!(IdentRef) && peeked_is!("is") { p.try_parse_ts(|p| p.parse_ts_type_predicate_prefix()) } else { None @@ -1843,7 +1843,7 @@ impl<'a, I: Tokens> Parser<'a, I> { start: BytePos, decorators: Vec, ) -> PResult<'a, Option> { - assert!( + debug_assert!( !is!("declare"), "try_parse_ts_declare should be called after eating `declare`" ); @@ -2023,17 +2023,21 @@ impl<'a, I: Tokens> Parser<'a, I> { &mut self, start: BytePos, ) -> PResult<'a, Option> { - let res = self.try_parse_ts(|p| { - let type_params = p.parse_ts_type_params()?; - // Don't use overloaded parseFunctionParams which would look for "<" again. - expect!('('); - let params = p.parse_formal_params()?; - expect!(')'); - let return_type = p.try_parse_ts_type_or_type_predicate_ann()?; - expect!("=>"); - - Ok(Some((type_params, params, return_type))) - }); + let res = if is_one_of!('<', JSXTagStart) { + self.try_parse_ts(|p| { + let type_params = p.parse_ts_type_params()?; + // Don't use overloaded parseFunctionParams which would look for "<" again. + expect!('('); + let params = p.parse_formal_params()?; + expect!(')'); + let return_type = p.try_parse_ts_type_or_type_predicate_ann()?; + expect!("=>"); + + Ok(Some((type_params, params, return_type))) + }) + } else { + None + }; let (type_params, params, return_type) = match res { Some(v) => v,