mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
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 <GeoffreyBooth@users.noreply.github.com> Co-authored-by: Robert de Forest <guitar.robot@gmail.com> * Update test style and output * numeric separator parsed value * BigInt AST; parseNumber() Co-authored-by: Geoffrey Booth <GeoffreyBooth@users.noreply.github.com> Co-authored-by: Rich Trott <rtrott@gmail.com> Co-authored-by: Robert de Forest <guitar.robot@gmail.com> Co-authored-by: square <Inve1951@users.noreply.github.com>
This commit is contained in:
committed by
Geoffrey Booth
parent
bdcb2c73af
commit
f528e5e754
@@ -4,6 +4,8 @@ node_js:
|
||||
- 6
|
||||
- 8
|
||||
- 10
|
||||
- 12
|
||||
- node # Latest
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
2
Cakefile
2
Cakefile
@@ -472,6 +472,8 @@ runTests = (CoffeeScript) ->
|
||||
skipUnless 'var a = 2 ** 2; a **= 3', ['exponentiation.coffee']
|
||||
skipUnless 'var {...a} = {}', ['object_rest_spread.coffee']
|
||||
skipUnless '/foo.bar/s.test("foo\tbar")', ['regex_dotall.coffee']
|
||||
skipUnless '1_2_3', ['numeric_literal_separators.coffee']
|
||||
skipUnless '1n', ['numbers_bigint.coffee']
|
||||
files = fs.readdirSync('test').filter (filename) ->
|
||||
filename not in testFilesToSkip
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ environment:
|
||||
- nodejs_version: '6'
|
||||
- nodejs_version: '8'
|
||||
- nodejs_version: '10'
|
||||
- nodejs_version: '' # Installs latest.
|
||||
- nodejs_version: '12'
|
||||
- nodejs_version: '' # Latest
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
// Build a dictionary of extra token properties organized by tokens’ locations
|
||||
// used as lookup hashes.
|
||||
exports.buildTokenDataDictionary = buildTokenDataDictionary = function(tokens) {
|
||||
var base, i, len1, token, tokenData, tokenHash;
|
||||
var base1, i, len1, token, tokenData, tokenHash;
|
||||
tokenData = {};
|
||||
for (i = 0, len1 = tokens.length; i < len1; i++) {
|
||||
token = tokens[i];
|
||||
@@ -221,7 +221,7 @@
|
||||
// and therefore matching `tokenHash`es, merge the comments from both/all
|
||||
// tokens together into one array, even if there are duplicate comments;
|
||||
// they will get sorted out later.
|
||||
((base = tokenData[tokenHash]).comments != null ? base.comments : base.comments = []).push(...token.comments);
|
||||
((base1 = tokenData[tokenHash]).comments != null ? base1.comments : base1.comments = []).push(...token.comments);
|
||||
}
|
||||
}
|
||||
return tokenData;
|
||||
@@ -385,6 +385,30 @@ ${marker}`;
|
||||
}
|
||||
};
|
||||
|
||||
exports.parseNumber = function(string) {
|
||||
var base;
|
||||
if (string == null) {
|
||||
return 0/0;
|
||||
}
|
||||
base = (function() {
|
||||
switch (string.charAt(1)) {
|
||||
case 'b':
|
||||
return 2;
|
||||
case 'o':
|
||||
return 8;
|
||||
case 'x':
|
||||
return 16;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (base != null) {
|
||||
return parseInt(string.slice(2).replace(/_/g, ''), base);
|
||||
} else {
|
||||
return parseFloat(string.replace(/_/g, ''));
|
||||
}
|
||||
};
|
||||
|
||||
exports.isFunction = function(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Function]';
|
||||
};
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
// where locationData is {first_line, first_column, last_line, last_column, last_line_exclusive, last_column_exclusive}, which is a
|
||||
// format that can be fed directly into [Jison](https://github.com/zaach/jison). These
|
||||
// are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
|
||||
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_COMMENT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_JSX, INVERSES, JSTOKEN, JSX_ATTRIBUTE, JSX_FRAGMENT_IDENTIFIER, JSX_IDENTIFIER, JSX_IDENTIFIER_PART, JSX_INTERPOLATION, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_SINGLE, STRING_START, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, VALID_FLAGS, WHITESPACE, addTokenData, attachCommentsToNode, compact, count, flatten, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, replaceUnicodeCodePointEscapes, starts, throwSyntaxError,
|
||||
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_COMMENT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_JSX, INVERSES, JSTOKEN, JSX_ATTRIBUTE, JSX_FRAGMENT_IDENTIFIER, JSX_IDENTIFIER, JSX_IDENTIFIER_PART, JSX_INTERPOLATION, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_SINGLE, STRING_START, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, VALID_FLAGS, WHITESPACE, addTokenData, attachCommentsToNode, compact, count, flatten, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, parseNumber, repeat, replaceUnicodeCodePointEscapes, starts, throwSyntaxError,
|
||||
indexOf = [].indexOf,
|
||||
slice = [].slice;
|
||||
|
||||
({Rewriter, INVERSES} = require('./rewriter'));
|
||||
|
||||
// Import the helpers we need.
|
||||
({count, starts, compact, repeat, invertLiterate, merge, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, flatten} = require('./helpers'));
|
||||
({count, starts, compact, repeat, invertLiterate, merge, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, flatten, parseNumber} = require('./helpers'));
|
||||
|
||||
// The Lexer Class
|
||||
// ---------------
|
||||
@@ -323,19 +323,7 @@
|
||||
length: lexedLength
|
||||
});
|
||||
}
|
||||
base = (function() {
|
||||
switch (number.charAt(1)) {
|
||||
case 'b':
|
||||
return 2;
|
||||
case 'o':
|
||||
return 8;
|
||||
case 'x':
|
||||
return 16;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
parsedValue = base != null ? parseInt(number.slice(2), base) : parseFloat(number);
|
||||
parsedValue = parseNumber(number);
|
||||
tokenData = {parsedValue};
|
||||
tag = parsedValue === 2e308 ? 'INFINITY' : 'NUMBER';
|
||||
if (tag === 'INFINITY') {
|
||||
@@ -1802,10 +1790,13 @@
|
||||
// Is this an attribute with a value?
|
||||
}(?:\\s*:\\s*${JSX_IDENTIFIER_PART})?)([^\\S]*=(?!=))?`);
|
||||
|
||||
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i; // binary
|
||||
NUMBER = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+n|^(?:\d(?:_?\d)*)?\.?(?:\d(?:_?\d)*)+(?:e[+-]?(?:\d(?:_?\d)*)+)?/i; // binary
|
||||
// octal
|
||||
// hex
|
||||
// decimal bigint
|
||||
// decimal
|
||||
// decimal without support for numeric literal separators for reference:
|
||||
// \d*\.?\d+ (?:e[+-]?\d+)?
|
||||
|
||||
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
|
||||
// compound assign / compare
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// nodes are created as the result of actions in the [grammar](grammar.html),
|
||||
// but some are created by other nodes as a method of code generation. To convert
|
||||
// the syntax tree into a string of JavaScript code, call `compile()` on the root.
|
||||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Catch, Class, ClassProperty, ClassPrototypeProperty, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Directive, DynamicImport, DynamicImportCall, Elision, EmptyInterpolation, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JSXAttribute, JSXAttributes, JSXElement, JSXEmptyExpression, JSXExpressionContainer, JSXIdentifier, JSXNamespacedName, JSXTag, JSXText, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, MetaProperty, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, Root, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Sequence, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, SwitchCase, SwitchWhen, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, TemplateElement, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, astAsBlockIfNeeded, attachCommentsToNode, compact, del, emptyExpressionLocationData, ends, extend, extractSameLineLocationDataFirst, extractSameLineLocationDataLast, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, sniffDirectives, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility, zeroWidthLocationDataFromEndLocation,
|
||||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Catch, Class, ClassProperty, ClassPrototypeProperty, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Directive, DynamicImport, DynamicImportCall, Elision, EmptyInterpolation, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JSXAttribute, JSXAttributes, JSXElement, JSXEmptyExpression, JSXExpressionContainer, JSXIdentifier, JSXNamespacedName, JSXTag, JSXText, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, MetaProperty, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, Root, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Sequence, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, SwitchCase, SwitchWhen, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, TemplateElement, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, astAsBlockIfNeeded, attachCommentsToNode, compact, del, emptyExpressionLocationData, ends, extend, extractSameLineLocationDataFirst, extractSameLineLocationDataLast, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, parseNumber, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, sniffDirectives, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility, zeroWidthLocationDataFromEndLocation,
|
||||
indexOf = [].indexOf,
|
||||
splice = [].splice,
|
||||
slice1 = [].slice;
|
||||
@@ -16,7 +16,7 @@
|
||||
({isUnassignable, JS_FORBIDDEN} = require('./lexer'));
|
||||
|
||||
// Import the helpers we plan to use.
|
||||
({compact, flatten, extend, merge, del, starts, ends, some, addDataToNode, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, isFunction, isPlainObject, isNumber} = require('./helpers'));
|
||||
({compact, flatten, extend, merge, del, starts, ends, some, addDataToNode, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, isFunction, isPlainObject, isNumber, parseNumber} = require('./helpers'));
|
||||
|
||||
// Functions required by parser.
|
||||
exports.extend = extend;
|
||||
@@ -1333,20 +1333,28 @@
|
||||
this.parsedValue = this.value;
|
||||
this.value = `${this.value}`;
|
||||
} else {
|
||||
this.parsedValue = Number(this.value);
|
||||
this.parsedValue = parseNumber(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isBigInt() {
|
||||
return /n$/.test(this.value);
|
||||
}
|
||||
|
||||
astType() {
|
||||
return 'NumericLiteral';
|
||||
if (this.isBigInt()) {
|
||||
return 'BigIntLiteral';
|
||||
} else {
|
||||
return 'NumericLiteral';
|
||||
}
|
||||
}
|
||||
|
||||
astProperties() {
|
||||
return {
|
||||
value: this.parsedValue,
|
||||
value: this.isBigInt() ? this.parsedValue.toString() : this.parsedValue,
|
||||
extra: {
|
||||
rawValue: this.parsedValue,
|
||||
rawValue: this.isBigInt() ? this.parsedValue.toString() : this.parsedValue,
|
||||
raw: this.value
|
||||
}
|
||||
};
|
||||
@@ -3432,9 +3440,9 @@
|
||||
if (step = del(o, 'step')) {
|
||||
[this.step, this.stepVar] = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, shouldCache));
|
||||
}
|
||||
this.fromNum = this.from.isNumber() ? Number(this.fromVar) : null;
|
||||
this.toNum = this.to.isNumber() ? Number(this.toVar) : null;
|
||||
return this.stepNum = (step != null ? step.isNumber() : void 0) ? Number(this.stepVar) : null;
|
||||
this.fromNum = this.from.isNumber() ? parseNumber(this.fromVar) : null;
|
||||
this.toNum = this.to.isNumber() ? parseNumber(this.toVar) : null;
|
||||
return this.stepNum = (step != null ? step.isNumber() : void 0) ? parseNumber(this.stepVar) : null;
|
||||
}
|
||||
|
||||
// When compiled normally, the range returns the contents of the *for loop*
|
||||
@@ -8076,7 +8084,7 @@
|
||||
if (this.step && !this.range) {
|
||||
[step, stepVar] = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, shouldCacheOrIsAssignable));
|
||||
if (this.step.isNumber()) {
|
||||
stepNum = Number(stepVar);
|
||||
stepNum = parseNumber(stepVar);
|
||||
}
|
||||
}
|
||||
if (this.pattern) {
|
||||
|
||||
@@ -268,6 +268,20 @@ exports.nameWhitespaceCharacter = (string) ->
|
||||
when '\t' then 'tab'
|
||||
else string
|
||||
|
||||
exports.parseNumber = (string) ->
|
||||
return NaN unless string?
|
||||
|
||||
base = switch string.charAt 1
|
||||
when 'b' then 2
|
||||
when 'o' then 8
|
||||
when 'x' then 16
|
||||
else null
|
||||
|
||||
if base?
|
||||
parseInt string[2..].replace(/_/g, ''), base
|
||||
else
|
||||
parseFloat string.replace(/_/g, '')
|
||||
|
||||
exports.isFunction = (obj) -> Object::toString.call(obj) is '[object Function]'
|
||||
exports.isNumber = isNumber = (obj) -> Object::toString.call(obj) is '[object Number]'
|
||||
exports.isString = isString = (obj) -> Object::toString.call(obj) is '[object String]'
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# Import the helpers we need.
|
||||
{count, starts, compact, repeat, invertLiterate, merge,
|
||||
attachCommentsToNode, locationDataToString, throwSyntaxError
|
||||
replaceUnicodeCodePointEscapes, flatten} = require './helpers'
|
||||
replaceUnicodeCodePointEscapes, flatten, parseNumber} = require './helpers'
|
||||
|
||||
# The Lexer Class
|
||||
# ---------------
|
||||
@@ -272,13 +272,7 @@ exports.Lexer = class Lexer
|
||||
when /^0\d+/.test number
|
||||
@error "octal literal '#{number}' must be prefixed with '0o'", length: lexedLength
|
||||
|
||||
base = switch number.charAt 1
|
||||
when 'b' then 2
|
||||
when 'o' then 8
|
||||
when 'x' then 16
|
||||
else null
|
||||
|
||||
parsedValue = if base? then parseInt(number[2..], base) else parseFloat(number)
|
||||
parsedValue = parseNumber number
|
||||
tokenData = {parsedValue}
|
||||
|
||||
tag = if parsedValue is Infinity then 'INFINITY' else 'NUMBER'
|
||||
@@ -1302,10 +1296,14 @@ JSX_ATTRIBUTE = /// ^
|
||||
///
|
||||
|
||||
NUMBER = ///
|
||||
^ 0b[01]+ | # binary
|
||||
^ 0o[0-7]+ | # octal
|
||||
^ 0x[\da-f]+ | # hex
|
||||
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
|
||||
^ 0b[01](?:_?[01])*n? | # binary
|
||||
^ 0o[0-7](?:_?[0-7])*n? | # octal
|
||||
^ 0x[\da-f](?:_?[\da-f])*n? | # hex
|
||||
^ \d+n | # decimal bigint
|
||||
^ (?:\d(?:_?\d)*)? \.? (?:\d(?:_?\d)*)+ # decimal
|
||||
(?:e[+-]? (?:\d(?:_?\d)*)+ )?
|
||||
# decimal without support for numeric literal separators for reference:
|
||||
# \d*\.?\d+ (?:e[+-]?\d+)?
|
||||
///i
|
||||
|
||||
OPERATOR = /// ^ (
|
||||
|
||||
@@ -12,7 +12,7 @@ Error.stackTraceLimit = Infinity
|
||||
{compact, flatten, extend, merge, del, starts, ends, some,
|
||||
addDataToNode, attachCommentsToNode, locationDataToString,
|
||||
throwSyntaxError, replaceUnicodeCodePointEscapes,
|
||||
isFunction, isPlainObject, isNumber} = require './helpers'
|
||||
isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
|
||||
|
||||
# Functions required by parser.
|
||||
exports.extend = extend
|
||||
@@ -936,15 +936,30 @@ exports.NumberLiteral = class NumberLiteral extends Literal
|
||||
@parsedValue = @value
|
||||
@value = "#{@value}"
|
||||
else
|
||||
@parsedValue = Number @value
|
||||
@parsedValue = parseNumber @value
|
||||
|
||||
astType: -> 'NumericLiteral'
|
||||
isBigInt: ->
|
||||
/n$/.test @value
|
||||
|
||||
astType: ->
|
||||
if @isBigInt()
|
||||
'BigIntLiteral'
|
||||
else
|
||||
'NumericLiteral'
|
||||
|
||||
astProperties: ->
|
||||
return
|
||||
value: @parsedValue
|
||||
value:
|
||||
if @isBigInt()
|
||||
@parsedValue.toString()
|
||||
else
|
||||
@parsedValue
|
||||
extra:
|
||||
rawValue: @parsedValue
|
||||
rawValue:
|
||||
if @isBigInt()
|
||||
@parsedValue.toString()
|
||||
else
|
||||
@parsedValue
|
||||
raw: @value
|
||||
|
||||
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
|
||||
@@ -2296,9 +2311,9 @@ exports.Range = class Range extends Base
|
||||
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
|
||||
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
|
||||
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
|
||||
@fromNum = if @from.isNumber() then Number @fromVar else null
|
||||
@toNum = if @to.isNumber() then Number @toVar else null
|
||||
@stepNum = if step?.isNumber() then Number @stepVar else null
|
||||
@fromNum = if @from.isNumber() then parseNumber @fromVar else null
|
||||
@toNum = if @to.isNumber() then parseNumber @toVar else null
|
||||
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
|
||||
|
||||
# When compiled normally, the range returns the contents of the *for loop*
|
||||
# needed to iterate over the values in the range. Used by comprehensions.
|
||||
@@ -5362,7 +5377,7 @@ exports.For = class For extends While
|
||||
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
|
||||
if @step and not @range
|
||||
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
|
||||
stepNum = Number stepVar if @step.isNumber()
|
||||
stepNum = parseNumber stepVar if @step.isNumber()
|
||||
name = ivar if @pattern
|
||||
varPart = ''
|
||||
guardPart = ''
|
||||
|
||||
@@ -177,6 +177,41 @@ test "AST as expected for NumberLiteral node", ->
|
||||
rawValue: 225
|
||||
raw: '0xE1'
|
||||
|
||||
testExpression '10_000',
|
||||
type: 'NumericLiteral'
|
||||
value: 10000
|
||||
extra:
|
||||
rawValue: 10000
|
||||
raw: '10_000'
|
||||
|
||||
testExpression '1_2.34_5e6_7',
|
||||
type: 'NumericLiteral'
|
||||
value: 12.345e67
|
||||
extra:
|
||||
rawValue: 12.345e67
|
||||
raw: '1_2.34_5e6_7'
|
||||
|
||||
testExpression '0o7_7_7',
|
||||
type: 'NumericLiteral'
|
||||
value: 0o777
|
||||
extra:
|
||||
rawValue: 0o777
|
||||
raw: '0o7_7_7'
|
||||
|
||||
testExpression '42n',
|
||||
type: 'BigIntLiteral'
|
||||
value: '42'
|
||||
extra:
|
||||
rawValue: '42'
|
||||
raw: '42n'
|
||||
|
||||
testExpression '2e3_08',
|
||||
type: 'NumericLiteral'
|
||||
value: Infinity
|
||||
extra:
|
||||
rawValue: Infinity
|
||||
raw: '2e3_08'
|
||||
|
||||
test "AST as expected for InfinityLiteral node", ->
|
||||
testExpression 'Infinity',
|
||||
type: 'Identifier'
|
||||
|
||||
@@ -92,7 +92,7 @@ if require?
|
||||
|
||||
eq error.message, 'hello world'
|
||||
doesNotThrow(-> error.stack)
|
||||
notEqual error.stack.toString().indexOf(filePath), -1
|
||||
notEqual error.stack.toString().indexOf(filePath), -1, "Expected " + filePath + "in stack trace: " + error.stack.toString()
|
||||
|
||||
test "#4418: stack traces for compiled files reference the correct line number", ->
|
||||
# The browser is already compiling other anonymous scripts (the tests)
|
||||
|
||||
17
test/numbers_bigint.coffee
Normal file
17
test/numbers_bigint.coffee
Normal file
@@ -0,0 +1,17 @@
|
||||
# BigInt Literals
|
||||
# ---------------
|
||||
|
||||
test "BigInt exists", ->
|
||||
'object' is typeof BigInt
|
||||
|
||||
test "Parser recognizes decimal BigInt literals", ->
|
||||
eq 42n, BigInt 42
|
||||
|
||||
test "Parser recognizes binary BigInt literals", ->
|
||||
eq 42n, 0b101010n
|
||||
|
||||
test "Parser recognizes octal BigInt literals", ->
|
||||
eq 42n, 0o52n
|
||||
|
||||
test "Parser recognizes hexadecimal BigInt literals", ->
|
||||
eq 42n, 0x2an
|
||||
63
test/numeric_literal_separators.coffee
Normal file
63
test/numeric_literal_separators.coffee
Normal file
@@ -0,0 +1,63 @@
|
||||
# Numeric Literal Separators
|
||||
# --------------------------
|
||||
|
||||
test 'integer literals with separators', ->
|
||||
eq 123_456, 123456
|
||||
eq 12_34_56, 123456
|
||||
|
||||
test 'decimal literals with separators', ->
|
||||
eq 1_2.34_5, 12.345
|
||||
eq 1_0e1_0, 10e10
|
||||
eq 1_2.34_5e6_7, 12.345e67
|
||||
|
||||
test 'hexadecimal literals with separators', ->
|
||||
eq 0x1_2_3_4, 0x1234
|
||||
|
||||
test 'binary literals with separators', ->
|
||||
eq 0b10_10, 0b1010
|
||||
|
||||
test 'octal literals with separators', ->
|
||||
eq 0o7_7_7, 0o777
|
||||
|
||||
test 'infinity with separator', ->
|
||||
eq 2e3_08, Infinity
|
||||
|
||||
test 'range with separators', ->
|
||||
range = [10_000...10_002]
|
||||
eq range.length, 2
|
||||
eq range[0], 10000
|
||||
|
||||
test 'property access on a number', ->
|
||||
# Somehow, `3..toFixed()` is valid JavaScript; though just `3.toFixed()`
|
||||
# is not. CoffeeScript has long allowed code like `3.toFixed()` to compile
|
||||
# into `3..toFixed()`.
|
||||
eq 3.toFixed(), '3'
|
||||
# Where this can conflict with numeric literal separators is when the
|
||||
# property name contains an underscore.
|
||||
Number::_23 = _23 = 'x'
|
||||
eq 1._23, 'x'
|
||||
ok 1._34 is undefined
|
||||
delete Number::_23
|
||||
|
||||
test 'invalid decimal literal separators do not compile', ->
|
||||
# `1._23` is a valid property access (see previous test)
|
||||
throwsCompileError '1_.23'
|
||||
throwsCompileError '1e_2'
|
||||
throwsCompileError '1e2_'
|
||||
throwsCompileError '1_'
|
||||
throwsCompileError '1__2'
|
||||
|
||||
test 'invalid hexadecimal literal separators do not compile', ->
|
||||
throwsCompileError '0x_1234'
|
||||
throwsCompileError '0x1234_'
|
||||
throwsCompileError '0x1__34'
|
||||
|
||||
test 'invalid binary literal separators do not compile', ->
|
||||
throwsCompileError '0b_100'
|
||||
throwsCompileError '0b100_'
|
||||
throwsCompileError '0b1__1'
|
||||
|
||||
test 'invalid octal literal separators do not compile', ->
|
||||
throwsCompileError '0o_777'
|
||||
throwsCompileError '0o777_'
|
||||
throwsCompileError '0o6__6'
|
||||
Reference in New Issue
Block a user