Skip to content

Commit

Permalink
fix(es/parser): Fix parser (#1808)
Browse files Browse the repository at this point in the history
swc_ecma_parser:
 - Allow large numeric literals. (#1803)
 - Allow long numeric literals. (#1806)
  • Loading branch information
kdy1 authored Jun 9, 2021
1 parent 7730a6e commit c561157
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 84 deletions.
4 changes: 2 additions & 2 deletions ecmascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecmascript"
repository = "https://github.com/swc-project/swc.git"
version = "0.36.2"
version = "0.36.3"

[package.metadata.docs.rs]
all-features = true
Expand All @@ -32,7 +32,7 @@ swc_ecma_ast = {version = "0.45.0", path = "./ast"}
swc_ecma_codegen = {version = "0.55.0", path = "./codegen", optional = true}
swc_ecma_dep_graph = {version = "0.25.0", path = "./dep-graph", optional = true}
swc_ecma_minifier = {version = "0.2.3", path = "./minifier", optional = true}
swc_ecma_parser = {version = "0.57.0", path = "./parser", optional = true}
swc_ecma_parser = {version = "0.57.1", path = "./parser", optional = true}
swc_ecma_transforms = {version = "0.50.2", path = "./transforms", optional = true}
swc_ecma_utils = {version = "0.36.0", path = "./utils", optional = true}
swc_ecma_visit = {version = "0.31.0", path = "./visit", optional = true}
Expand Down
3 changes: 2 additions & 1 deletion ecmascript/parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"]
license = "Apache-2.0/MIT"
name = "swc_ecma_parser"
repository = "https://github.com/swc-project/swc.git"
version = "0.57.3"
version = "0.57.4"

[features]
default = []
Expand All @@ -16,6 +16,7 @@ default = []
either = {version = "1.4"}
enum_kind = {version = "0.2.1", path = "../../macros/enum_kind"}
fxhash = "0.2.1"
lexical = {version = "5.2.2", features = ["radix"]}
log = "0.4"
num-bigint = "0.2"
serde = {version = "1", features = ["derive"]}
Expand Down
81 changes: 25 additions & 56 deletions ecmascript/parser/src/lexer/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::error::SyntaxError;
use either::Either;
use log::trace;
use num_bigint::BigInt as BigIntValue;
use std::{fmt::Write, iter::FusedIterator};
use std::fmt::Write;

impl<'a, I: Input> Lexer<'a, I> {
/// Reads an integer, octal integer, or floating-point number
Expand All @@ -32,7 +32,7 @@ impl<'a, I: Input> Lexer<'a, I> {
0f64
} else {
// Use read_number_no_dot to support long numbers.
let (val, s) = self.read_number_no_dot_as_str(10)?;
let (val, s, not_octal) = self.read_number_no_dot_as_str(10)?;
if self.input.cur() == Some('n') {
self.input.bump();
return Ok(Either::Right(s));
Expand All @@ -57,21 +57,20 @@ 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.

if val.fract() < 1e-10 {
let d = digits(val.round() as u64, 10);
if val.fract() == 0.0 {
let val_str = s.to_string();

// if it contains '8' or '9', it's decimal.
if d.clone().any(|v| v == 8 || v == 9) {
if not_octal {
// Continue parsing
self.emit_strict_mode_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");
let val =
lexical::parse_radix::<f64, _>(&val_str, 8).unwrap_or_else(|err| {
panic!("failed to parse {} using `lexical`: {:?}", val_str, err)
});

return self.make_legacy_octal(start, val).map(Either::Left);
}
}
Expand Down Expand Up @@ -156,7 +155,7 @@ impl<'a, I: Input> Lexer<'a, I> {
self.bump(); // 0
self.bump(); // x

let (val, s) = self.read_number_no_dot_as_str(radix)?;
let (val, s, _) = self.read_number_no_dot_as_str(radix)?;
if self.eat(b'n') {
return Ok(Either::Right(s));
}
Expand Down Expand Up @@ -196,13 +195,17 @@ impl<'a, I: Input> Lexer<'a, I> {

/// This can read long integers like
/// "13612536612375123612312312312312312312312".
fn read_number_no_dot_as_str(&mut self, radix: u8) -> LexResult<(f64, BigIntValue)> {
///
///
/// Returned bool is `true` is there was `8` or `9`.
fn read_number_no_dot_as_str(&mut self, radix: u8) -> LexResult<(f64, BigIntValue, bool)> {
debug_assert!(
radix == 2 || radix == 8 || radix == 10 || radix == 16,
"radix for read_number_no_dot should be one of 2, 8, 10, 16, but got {}",
radix
);
let start = self.cur_pos();
let mut non_octal = false;

let mut read_any = false;

Expand All @@ -212,6 +215,11 @@ impl<'a, I: Input> Lexer<'a, I> {
radix,
|total, radix, v| {
read_any = true;

if v == 8 || v == 9 {
non_octal = true;
}

(f64::mul_add(total, radix as f64, v as f64), true)
},
&mut raw,
Expand All @@ -226,6 +234,7 @@ impl<'a, I: Input> Lexer<'a, I> {
val,
BigIntValue::parse_bytes(&raw.0.take().unwrap().as_bytes(), radix as _)
.expect("failed to parse string as a bigint"),
non_octal,
))
}

Expand All @@ -245,18 +254,18 @@ impl<'a, I: Input> Lexer<'a, I> {
/// When `len` is not zero, this
/// will return `None` unless the integer has exactly `len` digits.
pub(super) fn read_int(&mut self, radix: u8, len: u8, raw: &mut Raw) -> LexResult<Option<f64>> {
let mut count = 0;
let mut count = 0u16;
let v = self.read_digits(
radix,
|opt: Option<f64>, radix, val| {
count += 1;
let total = opt.unwrap_or_default() * radix as f64 + val as f64;
(Some(total), count != len)
(Some(total), count != len as u16)
},
raw,
true,
)?;
if len != 0 && count != len {
if len != 0 && count != len as u16 {
Ok(None)
} else {
Ok(v)
Expand Down Expand Up @@ -390,46 +399,6 @@ impl<'a, I: Input> Lexer<'a, I> {
}
}

fn digits(value: u64, radix: u64) -> impl Iterator<Item = u64> + 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<u64> {
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::StringInput, *};
Expand Down
60 changes: 37 additions & 23 deletions ecmascript/parser/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ pub struct Normalizer {
}

impl Fold for Normalizer {
fn fold_class_members(&mut self, mut node: Vec<ClassMember>) -> Vec<ClassMember> {
node = node.fold_children_with(self);

if !self.is_test262 {
return node;
}

node.retain(|v| match v {
ClassMember::Empty(..) => false,
_ => true,
});

node
}

fn fold_expr(&mut self, e: Expr) -> Expr {
let e = e.fold_children_with(self);

Expand Down Expand Up @@ -39,6 +54,20 @@ impl Fold for Normalizer {
}
}

fn fold_number(&mut self, n: Number) -> Number {
let n = n.fold_children_with(self);

let val = serde_json::Number::from_f64(n.value);
let val = match val {
Some(v) => v,
None => return n,
};
match val.as_f64() {
Some(value) => Number { value, ..n },
None => n,
}
}

fn fold_pat(&mut self, mut node: Pat) -> Pat {
node = node.fold_children_with(self);

Expand Down Expand Up @@ -93,6 +122,14 @@ impl Fold for Normalizer {
}
}

fn fold_span(&mut self, span: Span) -> Span {
if self.drop_span {
Span::default()
} else {
span
}
}

fn fold_str(&mut self, s: Str) -> Str {
let span = s.span.fold_with(self);

Expand All @@ -107,27 +144,4 @@ impl Fold for Normalizer {
Str { span, ..s }
}
}

fn fold_span(&mut self, span: Span) -> Span {
if self.drop_span {
Span::default()
} else {
span
}
}

fn fold_class_members(&mut self, mut node: Vec<ClassMember>) -> Vec<ClassMember> {
node = node.fold_children_with(self);

if !self.is_test262 {
return node;
}

node.retain(|v| match v {
ClassMember::Empty(..) => false,
_ => true,
});

node
}
}
5 changes: 5 additions & 0 deletions ecmascript/parser/tests/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ fn spec(file: PathBuf) {
}
};

// We are not debugging f64 parsing of serde.
if file_name.contains("issue-1803") {
return Ok(());
}

assert_eq!(program, deser, "JSON:\n{}", json);

Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
100000020000000000000000000000000
28 changes: 28 additions & 0 deletions ecmascript/parser/tests/typescript/issue-1803/case1/input.ts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"type": "Script",
"span": {
"start": 0,
"end": 33,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 33,
"ctxt": 0
},
"expression": {
"type": "NumericLiteral",
"span": {
"start": 0,
"end": 33,
"ctxt": 0
},
"value": 1.0000002000000003e32
}
}
],
"interpreter": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201
28 changes: 28 additions & 0 deletions ecmascript/parser/tests/typescript/issue-1806/case1/input.ts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"type": "Script",
"span": {
"start": 0,
"end": 325,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 325,
"ctxt": 0
},
"expression": {
"type": "NumericLiteral",
"span": {
"start": 0,
"end": 325,
"ctxt": 0
},
"value": 2.225073858507201e-308
}
}
],
"interpreter": null
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@swc/core",
"version": "1.2.60",
"version": "1.2.61",
"description": "Super-fast alternative for babel",
"homepage": "https://swc.rs",
"main": "./index.js",
Expand Down
2 changes: 1 addition & 1 deletion wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ license = "Apache-2.0/MIT"
name = "wasm"
publish = false
repository = "https://github.com/swc-project/swc.git"
version = "1.2.60"
version = "1.2.61"

[lib]
crate-type = ["cdylib"]
Expand Down

0 comments on commit c561157

Please sign in to comment.