| Index: lib/src/hex/decoder.dart
|
| diff --git a/lib/src/hex/decoder.dart b/lib/src/hex/decoder.dart
|
| index 2ba169af364e84f036bfd2668619843ae103818e..efb0b0d41b957a744607916c728a4127f58a3ad5 100644
|
| --- a/lib/src/hex/decoder.dart
|
| +++ b/lib/src/hex/decoder.dart
|
| @@ -7,7 +7,7 @@ library convert.hex.decoder;
|
| import 'dart:convert';
|
| import 'dart:typed_data';
|
|
|
| -import 'package:charcode/ascii.dart';
|
| +import '../utils.dart';
|
|
|
| /// The canonical instance of [HexDecoder].
|
| const hexDecoder = const HexDecoder._();
|
| @@ -53,7 +53,7 @@ class _HexDecoderSink extends StringConversionSinkBase {
|
| RangeError.checkValidRange(start, end, string.length);
|
|
|
| if (start == end) {
|
| - if (isLast) close();
|
| + if (isLast) _close(string, end);
|
| return;
|
| }
|
|
|
| @@ -66,7 +66,7 @@ class _HexDecoderSink extends StringConversionSinkBase {
|
| } else {
|
| var hexPairs = (end - start - 1) ~/ 2;
|
| bytes = new Uint8List(1 + hexPairs);
|
| - bytes[0] = _lastDigit + _digitForCodeUnit(codeUnits, start);
|
| + bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start);
|
| start++;
|
| bytesStart = 1;
|
| }
|
| @@ -74,15 +74,20 @@ class _HexDecoderSink extends StringConversionSinkBase {
|
| _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart);
|
|
|
| _sink.add(bytes);
|
| - if (isLast) close();
|
| + if (isLast) _close(string, end);
|
| }
|
|
|
| ByteConversionSink asUtf8Sink(bool allowMalformed) =>
|
| new _HexDecoderByteSink(_sink);
|
|
|
| - void close() {
|
| + void close() => _close();
|
| +
|
| + /// Like [close], but includes [string] and [index] in the [FormatException]
|
| + /// if one is thrown.
|
| + void _close([String string, int index]) {
|
| if (_lastDigit != null) {
|
| - throw new FormatException("Invalid input length, must be even.");
|
| + throw new FormatException(
|
| + "Input ended with incomplete encoded byte.", string, index);
|
| }
|
|
|
| _sink.close();
|
| @@ -109,7 +114,7 @@ class _HexDecoderByteSink extends ByteConversionSinkBase {
|
| RangeError.checkValidRange(start, end, chunk.length);
|
|
|
| if (start == end) {
|
| - if (isLast) close();
|
| + if (isLast) _close(chunk, end);
|
| return;
|
| }
|
|
|
| @@ -121,7 +126,7 @@ class _HexDecoderByteSink extends ByteConversionSinkBase {
|
| } else {
|
| var hexPairs = (end - start - 1) ~/ 2;
|
| bytes = new Uint8List(1 + hexPairs);
|
| - bytes[0] = _lastDigit + _digitForCodeUnit(chunk, start);
|
| + bytes[0] = _lastDigit + digitForCodeUnit(chunk, start);
|
| start++;
|
| bytesStart = 1;
|
| }
|
| @@ -129,12 +134,17 @@ class _HexDecoderByteSink extends ByteConversionSinkBase {
|
| _lastDigit = _decode(chunk, start, end, bytes, bytesStart);
|
|
|
| _sink.add(bytes);
|
| - if (isLast) close();
|
| + if (isLast) _close(chunk, end);
|
| }
|
|
|
| - void close() {
|
| + void close() => _close();
|
| +
|
| + /// Like [close], but includes [chunk] and [index] in the [FormatException]
|
| + /// if one is thrown.
|
| + void _close([List<int> chunk, int index]) {
|
| if (_lastDigit != null) {
|
| - throw new FormatException("Invalid input length, must be even.");
|
| + throw new FormatException(
|
| + "Input ended with incomplete encoded byte.", chunk, index);
|
| }
|
|
|
| _sink.close();
|
| @@ -152,42 +162,11 @@ int _decode(List<int> codeUnits, int sourceStart, int sourceEnd,
|
| List<int> destination, int destinationStart) {
|
| var destinationIndex = destinationStart;
|
| for (var i = sourceStart; i < sourceEnd - 1; i += 2) {
|
| - var firstDigit = _digitForCodeUnit(codeUnits, i);
|
| - var secondDigit = _digitForCodeUnit(codeUnits, i + 1);
|
| + var firstDigit = digitForCodeUnit(codeUnits, i);
|
| + var secondDigit = digitForCodeUnit(codeUnits, i + 1);
|
| destination[destinationIndex++] = 16 * firstDigit + secondDigit;
|
| }
|
|
|
| if ((sourceEnd - sourceStart).isEven) return null;
|
| - return 16 * _digitForCodeUnit(codeUnits, sourceEnd - 1);
|
| -}
|
| -
|
| -/// Returns the digit (0 through 15) corresponding to the hexadecimal code unit
|
| -/// at index [i] in [codeUnits].
|
| -///
|
| -/// If the given code unit isn't valid hexadecimal, throws a [FormatException].
|
| -int _digitForCodeUnit(List<int> codeUnits, int index) {
|
| - // If the code unit is a numeral, get its value. XOR works because 0 in ASCII
|
| - // is `0b110000` and the other numerals come after it in ascending order and
|
| - // take up at most four bits.
|
| - //
|
| - // We check for digits first because it ensures there's only a single branch
|
| - // for 10 out of 16 of the expected cases. We don't count the `digit >= 0`
|
| - // check because branch prediction will always work on it for valid data.
|
| - var codeUnit = codeUnits[index];
|
| - var digit = $0 ^ codeUnit;
|
| - if (digit <= 9) {
|
| - if (digit >= 0) return digit;
|
| - } else {
|
| - // If the code unit is an uppercase letter, convert it to lowercase. This
|
| - // works because uppercase letters in ASCII are exactly `0b100000 = 0x20`
|
| - // less than lowercase letters, so if we ensure that that bit is 1 we ensure
|
| - // that the letter is lowercase.
|
| - var letter = 0x20 | codeUnit;
|
| - if ($a <= letter && letter <= $f) return letter - $a + 10;
|
| - }
|
| -
|
| - throw new FormatException(
|
| - "Invalid hexadecimal code unit "
|
| - "U+${codeUnit.toRadixString(16).padLeft(4, '0')}.",
|
| - codeUnits, index);
|
| + return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1);
|
| }
|
|
|