| // 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, |
| ]); |
| } |