Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add decimal parsing support #11640

Merged
merged 10 commits into from Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/babel-core/src/parser/util/missing-plugin-helper.js
Expand Up @@ -31,6 +31,12 @@ const pluginNameMap = {
url: "https://git.io/JvpRG",
},
},
decimal: {
syntax: {
name: "@babel/plugin-syntax-decimal",
url: "https://git.io/JfKOH",
},
},
decorators: {
syntax: {
name: "@babel/plugin-syntax-decorators",
Expand Down
9 changes: 9 additions & 0 deletions packages/babel-generator/src/generators/types.js
Expand Up @@ -222,6 +222,15 @@ export function BigIntLiteral(node: Object) {
this.token(node.value + "n");
}

export function DecimalLiteral(node: Object) {
const raw = this.getPossibleRaw(node);
if (!this.format.minified && raw != null) {
this.token(raw);
return;
}
this.token(node.value + "m");
}

export function PipelineTopicExpression(node: Object) {
this.print(node.expression, node);
}
Expand Down
@@ -0,0 +1,5 @@
100m;
9223372036854775807m;
0.m;
3.1415926535897932m;
100.000m;
@@ -0,0 +1,4 @@
{
"plugins": ["decimal"],
"minified": true
}
@@ -0,0 +1 @@
100m;9223372036854775807m;0.m;3.1415926535897932m;100.000m;
@@ -0,0 +1,5 @@
100m;
9223372036854775807m;
0.m;
3.1415926535897932m;
100.000m;
@@ -0,0 +1,3 @@
{
"plugins": ["decimal"]
}
@@ -0,0 +1,5 @@
100m;
9223372036854775807m;
0.m;
3.1415926535897932m;
100.000m;
12 changes: 12 additions & 0 deletions packages/babel-parser/ast/spec.md
Expand Up @@ -11,6 +11,7 @@ These are the core @babel/parser (babylon) AST node types.
- [BooleanLiteral](#booleanliteral)
- [NumericLiteral](#numericliteral)
- [BigIntLiteral](#bigintliteral)
- [DecimalLiteral](#decimalliteral)
- [Programs](#programs)
- [Functions](#functions)
- [Statements](#statements)
Expand Down Expand Up @@ -253,6 +254,17 @@ interface BigIntLiteral <: Literal {

The `value` property is the string representation of the `BigInt` value. It doesn't include the suffix `n`.

## DecimalLiteral

```js
interface DecimalLiteral <: Literal {
type: "DecimalLiteral";
value: string;
}
```

The `value` property is the string representation of the `BigDecimal` value. It doesn't include the suffix `m`.

# Programs

```js
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/parser/error-message.js
Expand Up @@ -61,6 +61,7 @@ export const ErrorMessages = Object.freeze({
ImportOutsideModule: `'import' and 'export' may appear only with 'sourceType: "module"'`,
InvalidBigIntLiteral: "Invalid BigIntLiteral",
InvalidCodePoint: "Code point out of bounds",
InvalidDecimal: "Invalid decimal",
InvalidDigit: "Expected number in radix %0",
InvalidEscapeSequence: "Bad character escape sequence",
InvalidEscapeSequenceTemplate: "Invalid escape sequence in template",
Expand Down
8 changes: 7 additions & 1 deletion packages/babel-parser/src/parser/expression.js
Expand Up @@ -1032,6 +1032,9 @@ export default class ExpressionParser extends LValParser {
case tt.bigint:
return this.parseLiteral(this.state.value, "BigIntLiteral");

case tt.decimal:
return this.parseLiteral(this.state.value, "DecimalLiteral");

case tt.string:
return this.parseLiteral(this.state.value, "StringLiteral");

Expand Down Expand Up @@ -1859,7 +1862,10 @@ export default class ExpressionParser extends LValParser {
this.state.inPropertyName = true;
// We check if it's valid for it to be a private name when we push it.
(prop: $FlowFixMe).key =
this.match(tt.num) || this.match(tt.string) || this.match(tt.bigint)
this.match(tt.num) ||
this.match(tt.string) ||
this.match(tt.bigint) ||
this.match(tt.decimal)
? this.parseExprAtom()
: this.parseMaybePrivateName(isPrivateNameAllowed);

Expand Down
3 changes: 2 additions & 1 deletion packages/babel-parser/src/parser/util.js
Expand Up @@ -273,7 +273,8 @@ export default class UtilParser extends Tokenizer {
!!this.state.type.keyword ||
this.match(tt.string) ||
this.match(tt.num) ||
this.match(tt.bigint)
this.match(tt.bigint) ||
this.match(tt.decimal)
);
}
}
Expand Down
14 changes: 14 additions & 0 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -43,6 +43,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return node;
}

estreeParseDecimalLiteral(value: any): N.Node {
// https://github.com/estree/estree/blob/master/experimental/decimal.md
// $FlowIgnore
// todo: use BigDecimal when node supports it.
const decimal = null;
const node = this.estreeParseLiteral(decimal);
node.decimal = String(node.value || value);

return node;
}

estreeParseLiteral(value: any): N.Node {
return this.parseLiteral(value, "Literal");
}
Expand Down Expand Up @@ -229,6 +240,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
case tt.bigint:
return this.estreeParseBigIntLiteral(this.state.value);

case tt.decimal:
return this.estreeParseDecimalLiteral(this.state.value);

case tt._null:
return this.estreeParseLiteral(null);

Expand Down
23 changes: 21 additions & 2 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -1095,6 +1095,8 @@ export default class Tokenizer extends ParserErrors {
if (next === charCodes.lowercaseN) {
++this.state.pos;
isBigInt = true;
} else if (next === charCodes.lowercaseM) {
throw this.raise(start, Errors.InvalidDecimal);
}

if (isIdentifierStart(this.input.codePointAt(this.state.pos))) {
Expand All @@ -1116,6 +1118,8 @@ export default class Tokenizer extends ParserErrors {
const start = this.state.pos;
let isFloat = false;
let isBigInt = false;
let isDecimal = false;
let hasExponent = false;
let isOctal = false;

if (!startsWithDot && this.readInt(10) === null) {
Expand Down Expand Up @@ -1157,6 +1161,7 @@ export default class Tokenizer extends ParserErrors {
}
if (this.readInt(10) === null) this.raise(start, Errors.InvalidNumber);
isFloat = true;
hasExponent = true;
next = this.input.charCodeAt(this.state.pos);
}

Expand All @@ -1174,18 +1179,32 @@ export default class Tokenizer extends ParserErrors {
isBigInt = true;
}

if (next === charCodes.lowercaseM) {
this.expectPlugin("decimal", this.state.pos);
if (hasExponent || hasLeadingZero) {
this.raise(start, Errors.InvalidDecimal);
}
++this.state.pos;
isDecimal = true;
}

if (isIdentifierStart(this.input.codePointAt(this.state.pos))) {
throw this.raise(this.state.pos, Errors.NumberIdentifier);
}

// remove "_" for numeric literal separator, and "n" for BigInts
const str = this.input.slice(start, this.state.pos).replace(/[_n]/g, "");
// remove "_" for numeric literal separator, and trailing `m` or `n`
const str = this.input.slice(start, this.state.pos).replace(/[_mn]/g, "");

if (isBigInt) {
this.finishToken(tt.bigint, str);
return;
}

if (isDecimal) {
this.finishToken(tt.decimal, str);
return;
}

const val = isOctal ? parseInt(str, 8) : parseFloat(str);
this.finishToken(tt.num, val);
}
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/tokenizer/types.js
Expand Up @@ -86,6 +86,7 @@ function createBinop(name: string, binop: number) {
export const types: { [name: string]: TokenType } = {
num: new TokenType("num", { startsExpr }),
bigint: new TokenType("bigint", { startsExpr }),
decimal: new TokenType("decimal", { startsExpr }),
regexp: new TokenType("regexp", { startsExpr }),
string: new TokenType("string", { startsExpr }),
name: new TokenType("name", { startsExpr }),
Expand Down
@@ -0,0 +1 @@
.1m;
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: 'decimal' (1:2)"
}
@@ -0,0 +1 @@
({ 0m: 0, .1m() {}, get 0.2m(){}, set 3m(_){}, async 4m() {}, *.5m() {} });