Skip to content

Commit 5e8ee77

Browse files
srawlinsCommit Queue
authored and
Commit Queue
committed
Support digit separators
Work towards dart-lang/language#2 The feature is well-specified at the issue, but I will also follow up with a specification to check into the language repo. This change implements the feature more-or-less from front to back (because the back is very close to the front in this case :P; no "backend" work in the VM, etc). Digit separators are made available via a new experiment, `digit-separators`. Care is taken to report a single error when an underscore appears in an unexpected position (see new `separators_error_test.dart`). Three test files are added: * `separators_test.dart` is run with the experiment enabled, and has no compile-time errors. * `separators_error_test.dart` is run with the experiment enabled, and has many compile-time errors. * `separators_error_no_experiment_test.dart` is run with the experiment _disabled_. Change-Id: I7f1b1305d28b708b5ddf83f26188cd6e9ce3dd58 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365181 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Reviewed-by: Kevin Moore <kevmoo@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
1 parent 67a7d12 commit 5e8ee77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1267
-298
lines changed

pkg/_fe_analyzer_shared/lib/src/experiments/flags.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ enum ExperimentalFlag {
4545
experimentEnabledVersion: const Version(2, 0),
4646
experimentReleasedVersion: const Version(2, 0)),
4747

48+
digitSeparators(
49+
name: 'digit-separators',
50+
isEnabledByDefault: false,
51+
isExpired: false,
52+
experimentEnabledVersion: defaultLanguageVersion,
53+
experimentReleasedVersion: defaultLanguageVersion),
54+
4855
enhancedEnums(
4956
name: 'enhanced-enums',
5057
isEnabledByDefault: true,

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16803,6 +16803,19 @@ Message _withArgumentsUnexpectedModifierInNonNnbd(Token token) {
1680316803
);
1680416804
}
1680516805

16806+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
16807+
const Code<Null> codeUnexpectedSeparatorInNumber =
16808+
messageUnexpectedSeparatorInNumber;
16809+
16810+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
16811+
const MessageCode messageUnexpectedSeparatorInNumber = const MessageCode(
16812+
"UnexpectedSeparatorInNumber",
16813+
analyzerCodes: <String>["UNEXPECTED_SEPARATOR_IN_NUMBER"],
16814+
problemMessage:
16815+
r"""Digit separators ('_') in a number literal can only be placed between two digits.""",
16816+
correctionMessage: r"""Try removing the '_'.""",
16817+
);
16818+
1680616819
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
1680716820
const Code<Null> codeUnexpectedSuperParametersInGenerativeConstructors =
1680816821
messageUnexpectedSuperParametersInGenerativeConstructors;

pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,11 +1632,21 @@ class ForwardingListener implements Listener {
16321632
listener?.handleLiteralDouble(token);
16331633
}
16341634

1635+
@override
1636+
void handleLiteralDoubleWithSeparators(Token token) {
1637+
listener?.handleLiteralDoubleWithSeparators(token);
1638+
}
1639+
16351640
@override
16361641
void handleLiteralInt(Token token) {
16371642
listener?.handleLiteralInt(token);
16381643
}
16391644

1645+
@override
1646+
void handleLiteralIntWithSeparators(Token token) {
1647+
listener?.handleLiteralIntWithSeparators(token);
1648+
}
1649+
16401650
@override
16411651
void handleLiteralList(
16421652
int count, Token beginToken, Token? constKeyword, Token endToken) {

pkg/_fe_analyzer_shared/lib/src/parser/identifier_context.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,13 @@ bool looksLikeExpressionStart(Token next) =>
334334
next.isIdentifier ||
335335
next.isKeyword && !looksLikeStatementStart(next) ||
336336
next.type == TokenType.DOUBLE ||
337+
next.type == TokenType.DOUBLE_WITH_SEPARATORS ||
337338
next.type == TokenType.HASH ||
338339
next.type == TokenType.HEXADECIMAL ||
340+
next.type == TokenType.HEXADECIMAL_WITH_SEPARATORS ||
339341
next.type == TokenType.IDENTIFIER ||
340342
next.type == TokenType.INT ||
343+
next.type == TokenType.INT_WITH_SEPARATORS ||
341344
next.type == TokenType.STRING ||
342345
optional('{', next) ||
343346
optional('(', next) ||
@@ -358,10 +361,13 @@ bool looksLikeExpressionStart(Token next) =>
358361
bool looksLikePatternStart(Token next) =>
359362
next.isIdentifier ||
360363
next.type == TokenType.DOUBLE ||
364+
next.type == TokenType.DOUBLE_WITH_SEPARATORS ||
361365
next.type == TokenType.HASH ||
362366
next.type == TokenType.HEXADECIMAL ||
367+
next.type == TokenType.HEXADECIMAL_WITH_SEPARATORS ||
363368
next.type == TokenType.IDENTIFIER ||
364369
next.type == TokenType.INT ||
370+
next.type == TokenType.INT_WITH_SEPARATORS ||
365371
next.type == TokenType.STRING ||
366372
optional('null', next) ||
367373
optional('false', next) ||

pkg/_fe_analyzer_shared/lib/src/parser/listener.dart

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,20 +1964,30 @@ class Listener implements UnescapeErrorListener {
19641964
logEvent("Assert");
19651965
}
19661966

1967-
/** Called with either the token containing a double literal, or
1968-
* an immediately preceding "unary plus" token.
1969-
*/
1967+
/// Called with either the token containing a double literal, or an
1968+
/// immediately preceding "unary minus" token.
19701969
void handleLiteralDouble(Token token) {
19711970
logEvent("LiteralDouble");
19721971
}
19731972

1974-
/** Called with either the token containing an integer literal,
1975-
* or an immediately preceding "unary plus" token.
1976-
*/
1973+
/// Called with either the token containing a double literal with separators,
1974+
/// or an immediately preceding "unary minus" token.
1975+
void handleLiteralDoubleWithSeparators(Token token) {
1976+
logEvent("LiteralDoubleWithSeparators");
1977+
}
1978+
1979+
/// Called with either the token containing an integer literal, or an
1980+
/// immediately preceding "unary minus" token.
19771981
void handleLiteralInt(Token token) {
19781982
logEvent("LiteralInt");
19791983
}
19801984

1985+
/// Called with either the token containing an integer literal with
1986+
/// separators, or an immediately preceding "unary minus" token.
1987+
void handleLiteralIntWithSeparators(Token token) {
1988+
logEvent("LiteralIntWithSeparators");
1989+
}
1990+
19811991
void handleLiteralList(
19821992
int count, Token leftBracket, Token? constKeyword, Token rightBracket) {
19831993
logEvent("LiteralList");

pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6544,13 +6544,22 @@ class Parser {
65446544
reportRecoverableError(
65456545
next, codes.messageInvalidConstantPatternConstPrefix);
65466546
}
6547-
return parseLiteralInt(token);
6547+
if (identical(next.type, TokenType.INT_WITH_SEPARATORS) ||
6548+
identical(next.type, TokenType.HEXADECIMAL_WITH_SEPARATORS)) {
6549+
return parseLiteralIntWithSeparators(token);
6550+
} else {
6551+
return parseLiteralInt(token);
6552+
}
65486553
} else if (kind == DOUBLE_TOKEN) {
65496554
if (constantPatternContext == ConstantPatternContext.explicit) {
65506555
reportRecoverableError(
65516556
next, codes.messageInvalidConstantPatternConstPrefix);
65526557
}
6553-
return parseLiteralDouble(token);
6558+
if (identical(next.type, TokenType.DOUBLE_WITH_SEPARATORS)) {
6559+
return parseLiteralDoubleWithSeparators(token);
6560+
} else {
6561+
return parseLiteralDouble(token);
6562+
}
65546563
} else if (kind == STRING_TOKEN) {
65556564
if (constantPatternContext == ConstantPatternContext.explicit) {
65566565
reportRecoverableError(
@@ -7441,6 +7450,14 @@ class Parser {
74417450
return token;
74427451
}
74437452

7453+
Token parseLiteralIntWithSeparators(Token token) {
7454+
token = token.next!;
7455+
assert(identical(token.kind, INT_TOKEN) ||
7456+
identical(token.kind, HEXADECIMAL_TOKEN));
7457+
listener.handleLiteralIntWithSeparators(token);
7458+
return token;
7459+
}
7460+
74447461
/// ```
74457462
/// doubleLiteral:
74467463
/// double
@@ -7453,6 +7470,13 @@ class Parser {
74537470
return token;
74547471
}
74557472

7473+
Token parseLiteralDoubleWithSeparators(Token token) {
7474+
token = token.next!;
7475+
assert(identical(token.kind, DOUBLE_TOKEN));
7476+
listener.handleLiteralDoubleWithSeparators(token);
7477+
return token;
7478+
}
7479+
74567480
/// ```
74577481
/// stringLiteral:
74587482
/// (multilineString | singleLineString)+

pkg/_fe_analyzer_shared/lib/src/parser/util.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
library _fe_analyzer_shared.parser.util;
66

7+
import 'dart:typed_data';
8+
79
import '../messages/codes.dart' show noLength;
810

911
import '../scanner/scanner.dart' show Token;
@@ -216,6 +218,38 @@ Token splitGtFromGtGtGtEq(Token token) {
216218
..next = token.next);
217219
}
218220

221+
/// Strips separator characters (underscore) from [source].
222+
///
223+
/// No validation is performed on [source]; it could be a valid int, a valid
224+
/// double, or invalid.
225+
String stripSeparators(String source) {
226+
Uint8List list = _separatorStripBuffer;
227+
if (list.length < source.length - 1) {
228+
// Looking at a very long number. Allocate a new buffer.
229+
// We only strip separators after finding that there is at least one
230+
// separator, so the length can be reduced by at least one character.
231+
list = new Uint8List(source.length - 1);
232+
if (list.length < 128) {
233+
// Store the new, larger list as the reusable buffer.
234+
_separatorStripBuffer = list;
235+
}
236+
}
237+
238+
int writeIndex = 0;
239+
for (int i = 0; i < source.length; i++) {
240+
int char = source.codeUnitAt(i);
241+
if (char != 0x5f /* _ */) list[writeIndex++] = char;
242+
}
243+
return new String.fromCharCodes(list, 0, writeIndex);
244+
}
245+
246+
/// A reusable buffer for stripping separators from number literals.
247+
///
248+
/// The majority of number literals fit in 24 characters. A maximal double with
249+
/// no unnecessary leading or trailing zeros is 17 digits, one decimal point,
250+
/// one 'e', two '-'s, and three exponent digits: 24 characters.
251+
Uint8List _separatorStripBuffer = new Uint8List(24);
252+
219253
/// Return a synthetic `>` followed by [next].
220254
/// Call [Token.setNext] to add the token to the stream.
221255
Token syntheticGt(Token next) {

0 commit comments

Comments
 (0)