Skip to content

Commit

Permalink
Add decimal parsing support (#11640)
Browse files Browse the repository at this point in the history
* docs: add DecimalLiteral to AST spec

* add decimal support

* fix: throw invalid decimal on start

* add DecimalLiteral type definitions

* update parser typings

* add generator support

* add syntax-decimal plugin

* Add syntax-decimal to babel-standalone

* add syntax-decimal to missing plugin helpers

* fix incorrect test macro
  • Loading branch information
JLHwung committed Jul 29, 2020
1 parent 9daa50e commit 059e912
Show file tree
Hide file tree
Showing 56 changed files with 655 additions and 4 deletions.
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() {} });

0 comments on commit 059e912

Please sign in to comment.