blob: 2efc93ee0eee894ed807ac3ff3a60652bd361f57 [file] [edit]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:_internal" show patch, unsafeCast;
import "dart:_string" show StringUncheckedOperations;
import "dart:_string_helper" show skipLeadingWhitespace, skipTrailingWhitespace;
import "dart:_wasm";
import "dart:_error_utils";
@patch
class int {
@patch
static int? tryParse(String source, {int? radix}) {
if (source.isEmpty) {
return null;
}
if (radix == null || radix == 10) {
// Try parsing immediately, without trimming whitespace.
int? result = _tryParseIntRadix10(source, 0, source.length);
if (result != null) return result;
} else {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(
radix - 2,
34,
"Radix $radix not in range 2..36",
);
}
return _parse(source, radix, _kNull);
}
@patch
static int parse(String source, {int? radix}) {
if (source.isEmpty) {
return _handleFormatError(null, source, 0, radix, null) as int;
}
if (radix == null || radix == 10) {
// Try parsing immediately, without trimming whitespace.
int? result = _tryParseIntRadix10(source, 0, source.length);
if (result != null) return result;
} else {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(
radix - 2,
34,
"Radix $radix not in range 2..36",
);
}
// Split here so improve odds of parse being inlined and the checks omitted.
return _parse(source, radix, null)!;
}
static int? _parse(
String source,
int? radix,
int? Function(String)? onError,
) {
int end = skipTrailingWhitespace(source, source.length);
if (end == 0) {
return _handleFormatError(onError, source, source.length, radix, null);
}
int start = skipLeadingWhitespace(source, 0);
int first = source.codeUnitAtUnchecked(start);
int sign = 1;
if (first == 0x2b /* + */ || first == 0x2d /* - */ ) {
sign = 0x2c - first; // -1 if '-', +1 if '+'.
start++;
if (start == end) {
return _handleFormatError(onError, source, end, radix, null);
}
first = source.codeUnitAtUnchecked(start);
}
if (radix == null) {
// check for 0x prefix.
int index = start;
if (first == 0x30 /* 0 */ ) {
index++;
if (index == end) return 0;
first = source.codeUnitAtUnchecked(index);
if ((first | 0x20) == 0x78 /* x */ ) {
index++;
if (index == end) {
return _handleFormatError(onError, source, index, null, null);
}
return _parseRadix(source, 16, index, end, sign, sign > 0, onError);
}
}
radix = 10;
}
return _parseRadix(source, radix, start, end, sign, false, onError);
}
static Null _kNull(_) => null;
static int? _handleFormatError(
int? Function(String)? onError,
String source,
int? index,
int? radix,
String? message,
) {
if (onError != null) return onError(source);
if (message != null) {
throw FormatException(message, source, index);
}
if (radix == null) {
throw FormatException("Invalid number", source, index);
}
throw FormatException("Invalid radix-$radix number", source, index);
}
static int? _parseRadix(
String source,
int radix,
int start,
int end,
int sign,
bool allowOverflow,
int? Function(String)? onError,
) {
// Skip leading zeroes.
while (start < end && source.codeUnitAtUnchecked(start) == 0x30 /* 0 */ ) {
start += 1;
}
final blockSize = _PARSE_LIMITS[radix].toInt();
final length = end - start;
// Parse at most `blockSize` characters without overflows.
final parseBlockLength = length < blockSize ? length : blockSize;
int? blockResult = _parseBlock(
source,
radix,
start,
start + parseBlockLength,
);
if (blockResult == null) {
return _handleFormatError(onError, source, start, radix, null);
}
int result = sign * blockResult;
if (parseBlockLength < blockSize) {
// Overflow is not possible.
return result;
}
// Check overflows on the next digits. We can scan at most two digits before an overflow.
start += parseBlockLength;
for (int i = start; i < end; i++) {
int char = source.codeUnitAtUnchecked(i);
int digit = char ^ 0x30;
if (digit > 9) {
digit = (char | 0x20) - (0x61 - 10);
if (digit < 10 || digit >= radix) {
return _handleFormatError(onError, source, start, radix, null);
}
}
if (sign > 0) {
const max = 9223372036854775807;
if (!allowOverflow && (result > (max - digit) ~/ radix)) {
return _handleFormatError(
onError,
source,
null,
radix,
"Positive input exceeds the limit of integer",
);
}
result = (radix * result) + digit;
} else {
const min = -9223372036854775808;
// We don't need to check `allowOverflow` as overflows are only allowed
// in positive numbers.
if (result < (min + digit) ~/ radix) {
return _handleFormatError(
onError,
source,
null,
radix,
"Negative input exceeds the limit of integer",
);
}
result = (radix * result) - digit;
}
}
return result;
}
/// Parse digits in [source] range from [start] to [end].
///
/// Returns `null` if a character is not valid in radix [radix].
///
/// Does not check for overflows, assumes that the number of digits in the
/// range will fit into an [int].
static int? _parseBlock(String source, int radix, int start, int end) {
int result = 0;
if (radix <= 10) {
for (int i = start; i < end; i++) {
int digit = source.codeUnitAtUnchecked(i) ^ 0x30;
if (digit >= radix) return null;
result = (radix * result) + digit;
}
} else {
for (int i = start; i < end; i++) {
int char = source.codeUnitAtUnchecked(i);
int digit = char ^ 0x30;
if (digit > 9) {
digit = (char | 0x20) - (0x61 - 10);
if (digit < 10 || digit >= radix) return null;
}
result = (radix * result) + digit;
}
}
return result;
}
static int? _tryParseIntRadix10(String str, int start, int end) {
int ix = start;
int sign = 1;
int c = str.codeUnitAtUnchecked(ix);
// Check for leading '+' or '-'.
if ((c == 0x2b) || (c == 0x2d)) {
ix++;
sign = 0x2c - c; // -1 for '-', +1 for '+'.
if (ix == end) {
return null; // Empty.
}
}
if (end - ix > 18) {
return null; // May not fit into an `int`.
}
int result = 0;
for (int i = ix; i < end; i++) {
int c = 0x30 ^ str.codeUnitAtUnchecked(i);
if (9 < c) {
return null;
}
result = (10 * result) + c;
}
return sign * result;
}
// For each radix, 2-36, how many digits are guaranteed to fit in an `int`.
static const _PARSE_LIMITS = ImmutableWasmArray<WasmI64>.literal([
0, // unused
0, // unused
63, // radix: 2
39,
31,
27, // radix: 5
24,
22,
21,
19,
18, // radix: 10
18,
17,
17,
16,
16, // radix: 15
15,
15,
15,
14,
14, // radix: 20
14,
14,
13,
13,
13, // radix: 25
13,
13,
13,
12,
12, // radix: 30
12,
12,
12,
12,
12, // radix: 35
12,
]);
}