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); |
} |