diff --git a/README.md b/README.md index fb14b5a8ce..f370a0151a 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ require("babylon").parse("code", { - `numericSeparator` ([proposal](https://github.com/samuelgoto/proposal-numeric-separator)) - `optionalChaining` ([proposal](https://github.com/tc39/proposal-optional-chaining)) - `importMeta` ([proposal](https://github.com/tc39/proposal-import-meta)) + - `bigInt` ([proposal](https://github.com/tc39/proposal-bigint)) ### FAQ diff --git a/src/parser/expression.js b/src/parser/expression.js index 588c1b106d..f044c613b4 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -554,6 +554,9 @@ export default class ExpressionParser extends LValParser { case tt.num: return this.parseLiteral(this.state.value, "NumericLiteral"); + case tt.bigint: + return this.parseLiteral(this.state.value, "BigIntLiteral"); + case tt.string: return this.parseLiteral(this.state.value, "StringLiteral"); diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index dca36f2175..d95d79d703 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -631,10 +631,27 @@ export default class Tokenizer extends LocationParser { } readRadixNumber(radix: number): void { + const start = this.state.pos; + let isBigInt = false; + this.state.pos += 2; // 0x const val = this.readInt(radix); if (val == null) this.raise(this.state.start + 2, "Expected number in radix " + radix); + + if (this.hasPlugin("bigInt")) { + if (this.input.charCodeAt(this.state.pos) === 0x6E) { // 'n' + ++this.state.pos; + isBigInt = true; + } + } + if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.state.pos, "Identifier directly after number"); + + if (isBigInt) { + const str = this.input.slice(start, this.state.pos).replace(/[_n]/g, ""); + return this.finishToken(tt.bigint, str); + } + return this.finishToken(tt.num, val); } @@ -642,30 +659,47 @@ export default class Tokenizer extends LocationParser { readNumber(startsWithDot: boolean): void { const start = this.state.pos; - let octal = this.input.charCodeAt(start) === 48; // '0' + let octal = this.input.charCodeAt(start) === 0x30; // '0' let isFloat = false; + let isBigInt = false; if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number"); if (octal && this.state.pos == start + 1) octal = false; // number === 0 let next = this.input.charCodeAt(this.state.pos); - if (next === 46 && !octal) { // '.' + if (next === 0x2E && !octal) { // '.' ++this.state.pos; this.readInt(10); isFloat = true; next = this.input.charCodeAt(this.state.pos); } - if ((next === 69 || next === 101) && !octal) { // 'eE' + if ((next === 0x45 || next === 0x65) && !octal) { // 'Ee' next = this.input.charCodeAt(++this.state.pos); - if (next === 43 || next === 45) ++this.state.pos; // '+-' + if (next === 0x2B || next === 0x2D) ++this.state.pos; // '+-' if (this.readInt(10) === null) this.raise(start, "Invalid number"); isFloat = true; + next = this.input.charCodeAt(this.state.pos); + } + + if (this.hasPlugin("bigInt")) { + if (next === 0x6E) { // 'n' + // disallow floats and legacy octal syntax, new style octal ("0o") is handled in this.readRadixNumber + if (isFloat || octal) this.raise(start, "Invalid BigIntLiteral"); + ++this.state.pos; + isBigInt = true; + } } if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.state.pos, "Identifier directly after number"); - const str = this.input.slice(start, this.state.pos).replace(/_/g, ""); + // remove "_" for numeric literal separator, and "n" for BigInts + const str = this.input.slice(start, this.state.pos).replace(/[_n]/g, ""); + + if (isBigInt) { + return this.finishToken(tt.bigint, str); + } + let val; if (isFloat) { val = parseFloat(str); diff --git a/src/tokenizer/types.js b/src/tokenizer/types.js index 12d7ee1e93..89759681ed 100644 --- a/src/tokenizer/types.js +++ b/src/tokenizer/types.js @@ -82,6 +82,7 @@ export class BinopTokenType extends TokenType { export const types: { [name: string]: TokenType } = { num: new TokenType("num", { startsExpr }), + bigint: new TokenType("bigint", { startsExpr }), regexp: new TokenType("regexp", { startsExpr }), string: new TokenType("string", { startsExpr }), name: new TokenType("name", { startsExpr }), diff --git a/src/types.js b/src/types.js index d4c1041ae8..3a4acfaa08 100644 --- a/src/types.js +++ b/src/types.js @@ -96,6 +96,11 @@ export type NumericLiteral = NodeBase & { value: number; }; +export type BigIntLiteral = NodeBase & { + type: "BigIntLiteral"; + value: number; +} + // Programs export type BlockStatementLike = Program | BlockStatement; diff --git a/test/fixtures/experimental/bigint/invalid-decimal/actual.js b/test/fixtures/experimental/bigint/invalid-decimal/actual.js new file mode 100644 index 0000000000..d87b390c40 --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-decimal/actual.js @@ -0,0 +1 @@ +1.0n diff --git a/test/fixtures/experimental/bigint/invalid-decimal/options.json b/test/fixtures/experimental/bigint/invalid-decimal/options.json new file mode 100644 index 0000000000..a1fd77a334 --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-decimal/options.json @@ -0,0 +1 @@ +{ "throws": "Invalid BigIntLiteral (1:0)" } diff --git a/test/fixtures/experimental/bigint/invalid-e/actual.js b/test/fixtures/experimental/bigint/invalid-e/actual.js new file mode 100644 index 0000000000..3bc83e85c1 --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-e/actual.js @@ -0,0 +1 @@ +2e9n diff --git a/test/fixtures/experimental/bigint/invalid-e/options.json b/test/fixtures/experimental/bigint/invalid-e/options.json new file mode 100644 index 0000000000..a1fd77a334 --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-e/options.json @@ -0,0 +1 @@ +{ "throws": "Invalid BigIntLiteral (1:0)" } diff --git a/test/fixtures/experimental/bigint/invalid-octal-legacy/actual.js b/test/fixtures/experimental/bigint/invalid-octal-legacy/actual.js new file mode 100644 index 0000000000..59dd202a6f --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-octal-legacy/actual.js @@ -0,0 +1 @@ +016432n diff --git a/test/fixtures/experimental/bigint/invalid-octal-legacy/options.json b/test/fixtures/experimental/bigint/invalid-octal-legacy/options.json new file mode 100644 index 0000000000..a1fd77a334 --- /dev/null +++ b/test/fixtures/experimental/bigint/invalid-octal-legacy/options.json @@ -0,0 +1 @@ +{ "throws": "Invalid BigIntLiteral (1:0)" } diff --git a/test/fixtures/experimental/bigint/options.json b/test/fixtures/experimental/bigint/options.json new file mode 100644 index 0000000000..f6a5fb21f1 --- /dev/null +++ b/test/fixtures/experimental/bigint/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["bigInt"] +} diff --git a/test/fixtures/experimental/bigint/valid-binary/actual.js b/test/fixtures/experimental/bigint/valid-binary/actual.js new file mode 100644 index 0000000000..a61408b28a --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-binary/actual.js @@ -0,0 +1 @@ +0b101011101n diff --git a/test/fixtures/experimental/bigint/valid-binary/expected.json b/test/fixtures/experimental/bigint/valid-binary/expected.json new file mode 100644 index 0000000000..263ef53fc6 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-binary/expected.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start": 0, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "expression": { + "type": "BigIntLiteral", + "start": 0, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "extra": { + "rawValue": "0b101011101", + "raw": "0b101011101n" + }, + "value": "0b101011101" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/experimental/bigint/valid-hex/actual.js b/test/fixtures/experimental/bigint/valid-hex/actual.js new file mode 100644 index 0000000000..b5fa6179c8 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-hex/actual.js @@ -0,0 +1 @@ +0xFFF123n diff --git a/test/fixtures/experimental/bigint/valid-hex/expected.json b/test/fixtures/experimental/bigint/valid-hex/expected.json new file mode 100644 index 0000000000..64fe81e542 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-hex/expected.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "expression": { + "type": "BigIntLiteral", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "extra": { + "rawValue": "0xFFF123", + "raw": "0xFFF123n" + }, + "value": "0xFFF123" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/experimental/bigint/valid-large/actual.js b/test/fixtures/experimental/bigint/valid-large/actual.js new file mode 100644 index 0000000000..bff78974b7 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-large/actual.js @@ -0,0 +1 @@ +9223372036854775807n diff --git a/test/fixtures/experimental/bigint/valid-large/expected.json b/test/fixtures/experimental/bigint/valid-large/expected.json new file mode 100644 index 0000000000..1536e3c7ae --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-large/expected.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "expression": { + "type": "BigIntLiteral", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "extra": { + "rawValue": "9223372036854775807", + "raw": "9223372036854775807n" + }, + "value": "9223372036854775807" + } + } + ], + "directives": [] + } +} diff --git a/test/fixtures/experimental/bigint/valid-octal-new/actual.js b/test/fixtures/experimental/bigint/valid-octal-new/actual.js new file mode 100644 index 0000000000..21e50f9aba --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-octal-new/actual.js @@ -0,0 +1 @@ +0o16432n diff --git a/test/fixtures/experimental/bigint/valid-octal-new/expected.json b/test/fixtures/experimental/bigint/valid-octal-new/expected.json new file mode 100644 index 0000000000..93103a5656 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-octal-new/expected.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "expression": { + "type": "BigIntLiteral", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "extra": { + "rawValue": "0o16432", + "raw": "0o16432n" + }, + "value": "0o16432" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/experimental/bigint/valid-small/actual.js b/test/fixtures/experimental/bigint/valid-small/actual.js new file mode 100644 index 0000000000..9ad48c1bff --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-small/actual.js @@ -0,0 +1 @@ +100n diff --git a/test/fixtures/experimental/bigint/valid-small/expected.json b/test/fixtures/experimental/bigint/valid-small/expected.json new file mode 100644 index 0000000000..736974a559 --- /dev/null +++ b/test/fixtures/experimental/bigint/valid-small/expected.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "expression": { + "type": "BigIntLiteral", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "extra": { + "rawValue": "100", + "raw": "100n" + }, + "value": "100" + } + } + ], + "directives": [] + } +}