Skip to content

Commit f528e5e

Browse files
helixbassGeoffreyBoothTrottrdeforestInve1951
committed
AST: numeric separators, BigInt (#5272)
* Revert to more complicated lexing of numbers, as the Number constructor can't handle BigInts or numbers with numeric separators * Add debugging information to error message test (#5239) One of the test cases in test/error_messages.coffee fails intermittently in the Node.js ecosystem-testing tool CITGM. In an effort to help debug what's going on when this occurs, this adds more information to the AssertionError message in question. * Fix #5103: Add support for BigInt literals (#5104) * Fix #5103: Add support for BigInt literals * Fix typos found in testing * Support binary, octal and hex BigInt literals * Make decimal BigInt test consistent other bases * Correct test BigInt test names * Add Node versions to CI * Numeric literal separators (#5215) * implement numeric literal separators * add tests * Revert changes to package-lock.json * small regex adjustment * split tests * add comment * Add Node versions to CI * Fix #5103: Add support for BigInt literals (#5104) * Fix #5103: Add support for BigInt literals * Fix typos found in testing * Support binary, octal and hex BigInt literals * Make decimal BigInt test consistent other bases * Correct test BigInt test names * Add Node versions to CI * Update output * Fix style * support bigint literal with separators * un-disallow property access on number literal * Update output * Refactor numeric literal separator tests to be more like the rest of the tests * Add test for numeric property with underscore Co-authored-by: Geoffrey Booth <[email protected]> Co-authored-by: Robert de Forest <[email protected]> * Update test style and output * numeric separator parsed value * BigInt AST; parseNumber() Co-authored-by: Geoffrey Booth <[email protected]> Co-authored-by: Rich Trott <[email protected]> Co-authored-by: Robert de Forest <[email protected]> Co-authored-by: square <[email protected]>
1 parent bdcb2c7 commit f528e5e

13 files changed

+221
-51
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ node_js:
44
- 6
55
- 8
66
- 10
7+
- 12
8+
- node # Latest
79

810
cache:
911
directories:

Cakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,8 @@ runTests = (CoffeeScript) ->
472472
skipUnless 'var a = 2 ** 2; a **= 3', ['exponentiation.coffee']
473473
skipUnless 'var {...a} = {}', ['object_rest_spread.coffee']
474474
skipUnless '/foo.bar/s.test("foo\tbar")', ['regex_dotall.coffee']
475+
skipUnless '1_2_3', ['numeric_literal_separators.coffee']
476+
skipUnless '1n', ['numbers_bigint.coffee']
475477
files = fs.readdirSync('test').filter (filename) ->
476478
filename not in testFilesToSkip
477479

appveyor.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ environment:
33
- nodejs_version: '6'
44
- nodejs_version: '8'
55
- nodejs_version: '10'
6-
- nodejs_version: '' # Installs latest.
6+
- nodejs_version: '12'
7+
- nodejs_version: '' # Latest
78

89
install:
910
- ps: Install-Product node $env:nodejs_version

lib/coffeescript/helpers.js

Lines changed: 26 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/lexer.js

Lines changed: 7 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/nodes.js

Lines changed: 18 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/helpers.coffee

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,20 @@ exports.nameWhitespaceCharacter = (string) ->
268268
when '\t' then 'tab'
269269
else string
270270

271+
exports.parseNumber = (string) ->
272+
return NaN unless string?
273+
274+
base = switch string.charAt 1
275+
when 'b' then 2
276+
when 'o' then 8
277+
when 'x' then 16
278+
else null
279+
280+
if base?
281+
parseInt string[2..].replace(/_/g, ''), base
282+
else
283+
parseFloat string.replace(/_/g, '')
284+
271285
exports.isFunction = (obj) -> Object::toString.call(obj) is '[object Function]'
272286
exports.isNumber = isNumber = (obj) -> Object::toString.call(obj) is '[object Number]'
273287
exports.isString = isString = (obj) -> Object::toString.call(obj) is '[object String]'

src/lexer.coffee

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# Import the helpers we need.
1515
{count, starts, compact, repeat, invertLiterate, merge,
1616
attachCommentsToNode, locationDataToString, throwSyntaxError
17-
replaceUnicodeCodePointEscapes, flatten} = require './helpers'
17+
replaceUnicodeCodePointEscapes, flatten, parseNumber} = require './helpers'
1818

1919
# The Lexer Class
2020
# ---------------
@@ -272,13 +272,7 @@ exports.Lexer = class Lexer
272272
when /^0\d+/.test number
273273
@error "octal literal '#{number}' must be prefixed with '0o'", length: lexedLength
274274

275-
base = switch number.charAt 1
276-
when 'b' then 2
277-
when 'o' then 8
278-
when 'x' then 16
279-
else null
280-
281-
parsedValue = if base? then parseInt(number[2..], base) else parseFloat(number)
275+
parsedValue = parseNumber number
282276
tokenData = {parsedValue}
283277

284278
tag = if parsedValue is Infinity then 'INFINITY' else 'NUMBER'
@@ -1302,10 +1296,14 @@ JSX_ATTRIBUTE = /// ^
13021296
///
13031297

13041298
NUMBER = ///
1305-
^ 0b[01]+ | # binary
1306-
^ 0o[0-7]+ | # octal
1307-
^ 0x[\da-f]+ | # hex
1308-
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
1299+
^ 0b[01](?:_?[01])*n? | # binary
1300+
^ 0o[0-7](?:_?[0-7])*n? | # octal
1301+
^ 0x[\da-f](?:_?[\da-f])*n? | # hex
1302+
^ \d+n | # decimal bigint
1303+
^ (?:\d(?:_?\d)*)? \.? (?:\d(?:_?\d)*)+ # decimal
1304+
(?:e[+-]? (?:\d(?:_?\d)*)+ )?
1305+
# decimal without support for numeric literal separators for reference:
1306+
# \d*\.?\d+ (?:e[+-]?\d+)?
13091307
///i
13101308

13111309
OPERATOR = /// ^ (

src/nodes.coffee

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Error.stackTraceLimit = Infinity
1212
{compact, flatten, extend, merge, del, starts, ends, some,
1313
addDataToNode, attachCommentsToNode, locationDataToString,
1414
throwSyntaxError, replaceUnicodeCodePointEscapes,
15-
isFunction, isPlainObject, isNumber} = require './helpers'
15+
isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
1616

1717
# Functions required by parser.
1818
exports.extend = extend
@@ -936,15 +936,30 @@ exports.NumberLiteral = class NumberLiteral extends Literal
936936
@parsedValue = @value
937937
@value = "#{@value}"
938938
else
939-
@parsedValue = Number @value
939+
@parsedValue = parseNumber @value
940940

941-
astType: -> 'NumericLiteral'
941+
isBigInt: ->
942+
/n$/.test @value
943+
944+
astType: ->
945+
if @isBigInt()
946+
'BigIntLiteral'
947+
else
948+
'NumericLiteral'
942949

943950
astProperties: ->
944951
return
945-
value: @parsedValue
952+
value:
953+
if @isBigInt()
954+
@parsedValue.toString()
955+
else
956+
@parsedValue
946957
extra:
947-
rawValue: @parsedValue
958+
rawValue:
959+
if @isBigInt()
960+
@parsedValue.toString()
961+
else
962+
@parsedValue
948963
raw: @value
949964

950965
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
@@ -2296,9 +2311,9 @@ exports.Range = class Range extends Base
22962311
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
22972312
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
22982313
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
2299-
@fromNum = if @from.isNumber() then Number @fromVar else null
2300-
@toNum = if @to.isNumber() then Number @toVar else null
2301-
@stepNum = if step?.isNumber() then Number @stepVar else null
2314+
@fromNum = if @from.isNumber() then parseNumber @fromVar else null
2315+
@toNum = if @to.isNumber() then parseNumber @toVar else null
2316+
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
23022317

23032318
# When compiled normally, the range returns the contents of the *for loop*
23042319
# needed to iterate over the values in the range. Used by comprehensions.
@@ -5362,7 +5377,7 @@ exports.For = class For extends While
53625377
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
53635378
if @step and not @range
53645379
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
5365-
stepNum = Number stepVar if @step.isNumber()
5380+
stepNum = parseNumber stepVar if @step.isNumber()
53665381
name = ivar if @pattern
53675382
varPart = ''
53685383
guardPart = ''

test/abstract_syntax_tree.coffee

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,41 @@ test "AST as expected for NumberLiteral node", ->
177177
rawValue: 225
178178
raw: '0xE1'
179179

180+
testExpression '10_000',
181+
type: 'NumericLiteral'
182+
value: 10000
183+
extra:
184+
rawValue: 10000
185+
raw: '10_000'
186+
187+
testExpression '1_2.34_5e6_7',
188+
type: 'NumericLiteral'
189+
value: 12.345e67
190+
extra:
191+
rawValue: 12.345e67
192+
raw: '1_2.34_5e6_7'
193+
194+
testExpression '0o7_7_7',
195+
type: 'NumericLiteral'
196+
value: 0o777
197+
extra:
198+
rawValue: 0o777
199+
raw: '0o7_7_7'
200+
201+
testExpression '42n',
202+
type: 'BigIntLiteral'
203+
value: '42'
204+
extra:
205+
rawValue: '42'
206+
raw: '42n'
207+
208+
testExpression '2e3_08',
209+
type: 'NumericLiteral'
210+
value: Infinity
211+
extra:
212+
rawValue: Infinity
213+
raw: '2e3_08'
214+
180215
test "AST as expected for InfinityLiteral node", ->
181216
testExpression 'Infinity',
182217
type: 'Identifier'

0 commit comments

Comments
 (0)