| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library convert.hex.decoder; | 5 library convert.hex.decoder; |
| 6 | 6 |
| 7 import 'dart:convert'; | 7 import 'dart:convert'; |
| 8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
| 9 | 9 |
| 10 import 'package:charcode/ascii.dart'; | 10 import '../utils.dart'; |
| 11 | 11 |
| 12 /// The canonical instance of [HexDecoder]. | 12 /// The canonical instance of [HexDecoder]. |
| 13 const hexDecoder = const HexDecoder._(); | 13 const hexDecoder = const HexDecoder._(); |
| 14 | 14 |
| 15 /// A converter that decodes hexadecimal strings into byte arrays. | 15 /// A converter that decodes hexadecimal strings into byte arrays. |
| 16 /// | 16 /// |
| 17 /// Because two hexadecimal digits correspond to a single byte, this will throw | 17 /// Because two hexadecimal digits correspond to a single byte, this will throw |
| 18 /// a [FormatException] if given an odd-length string. It will also throw a | 18 /// a [FormatException] if given an odd-length string. It will also throw a |
| 19 /// [FormatException] if given a string containing non-hexadecimal code units. | 19 /// [FormatException] if given a string containing non-hexadecimal code units. |
| 20 class HexDecoder extends Converter<String, List<int>> { | 20 class HexDecoder extends Converter<String, List<int>> { |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 59 | 59 |
| 60 var codeUnits = string.codeUnits; | 60 var codeUnits = string.codeUnits; |
| 61 var bytes; | 61 var bytes; |
| 62 var bytesStart; | 62 var bytesStart; |
| 63 if (_lastDigit == null) { | 63 if (_lastDigit == null) { |
| 64 bytes = new Uint8List((end - start) ~/ 2); | 64 bytes = new Uint8List((end - start) ~/ 2); |
| 65 bytesStart = 0; | 65 bytesStart = 0; |
| 66 } else { | 66 } else { |
| 67 var hexPairs = (end - start - 1) ~/ 2; | 67 var hexPairs = (end - start - 1) ~/ 2; |
| 68 bytes = new Uint8List(1 + hexPairs); | 68 bytes = new Uint8List(1 + hexPairs); |
| 69 bytes[0] = _lastDigit + _digitForCodeUnit(codeUnits, start); | 69 bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start); |
| 70 start++; | 70 start++; |
| 71 bytesStart = 1; | 71 bytesStart = 1; |
| 72 } | 72 } |
| 73 | 73 |
| 74 _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); | 74 _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); |
| 75 | 75 |
| 76 _sink.add(bytes); | 76 _sink.add(bytes); |
| 77 if (isLast) close(); | 77 if (isLast) close(); |
| 78 } | 78 } |
| 79 | 79 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 } | 114 } |
| 115 | 115 |
| 116 var bytes; | 116 var bytes; |
| 117 var bytesStart; | 117 var bytesStart; |
| 118 if (_lastDigit == null) { | 118 if (_lastDigit == null) { |
| 119 bytes = new Uint8List((end - start) ~/ 2); | 119 bytes = new Uint8List((end - start) ~/ 2); |
| 120 bytesStart = 0; | 120 bytesStart = 0; |
| 121 } else { | 121 } else { |
| 122 var hexPairs = (end - start - 1) ~/ 2; | 122 var hexPairs = (end - start - 1) ~/ 2; |
| 123 bytes = new Uint8List(1 + hexPairs); | 123 bytes = new Uint8List(1 + hexPairs); |
| 124 bytes[0] = _lastDigit + _digitForCodeUnit(chunk, start); | 124 bytes[0] = _lastDigit + digitForCodeUnit(chunk, start); |
| 125 start++; | 125 start++; |
| 126 bytesStart = 1; | 126 bytesStart = 1; |
| 127 } | 127 } |
| 128 | 128 |
| 129 _lastDigit = _decode(chunk, start, end, bytes, bytesStart); | 129 _lastDigit = _decode(chunk, start, end, bytes, bytesStart); |
| 130 | 130 |
| 131 _sink.add(bytes); | 131 _sink.add(bytes); |
| 132 if (isLast) close(); | 132 if (isLast) close(); |
| 133 } | 133 } |
| 134 | 134 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 145 /// | 145 /// |
| 146 /// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes | 146 /// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes |
| 147 /// the result into [destination] starting at [destinationStart]. | 147 /// the result into [destination] starting at [destinationStart]. |
| 148 /// | 148 /// |
| 149 /// If there's a leftover digit at the end of the decoding, this returns that | 149 /// If there's a leftover digit at the end of the decoding, this returns that |
| 150 /// digit. Otherwise it returns `null`. | 150 /// digit. Otherwise it returns `null`. |
| 151 int _decode(List<int> codeUnits, int sourceStart, int sourceEnd, | 151 int _decode(List<int> codeUnits, int sourceStart, int sourceEnd, |
| 152 List<int> destination, int destinationStart) { | 152 List<int> destination, int destinationStart) { |
| 153 var destinationIndex = destinationStart; | 153 var destinationIndex = destinationStart; |
| 154 for (var i = sourceStart; i < sourceEnd - 1; i += 2) { | 154 for (var i = sourceStart; i < sourceEnd - 1; i += 2) { |
| 155 var firstDigit = _digitForCodeUnit(codeUnits, i); | 155 var firstDigit = digitForCodeUnit(codeUnits, i); |
| 156 var secondDigit = _digitForCodeUnit(codeUnits, i + 1); | 156 var secondDigit = digitForCodeUnit(codeUnits, i + 1); |
| 157 destination[destinationIndex++] = 16 * firstDigit + secondDigit; | 157 destination[destinationIndex++] = 16 * firstDigit + secondDigit; |
| 158 } | 158 } |
| 159 | 159 |
| 160 if ((sourceEnd - sourceStart).isEven) return null; | 160 if ((sourceEnd - sourceStart).isEven) return null; |
| 161 return 16 * _digitForCodeUnit(codeUnits, sourceEnd - 1); | 161 return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1); |
| 162 } | 162 } |
| 163 | |
| 164 /// Returns the digit (0 through 15) corresponding to the hexadecimal code unit | |
| 165 /// at index [i] in [codeUnits]. | |
| 166 /// | |
| 167 /// If the given code unit isn't valid hexadecimal, throws a [FormatException]. | |
| 168 int _digitForCodeUnit(List<int> codeUnits, int index) { | |
| 169 // If the code unit is a numeral, get its value. XOR works because 0 in ASCII | |
| 170 // is `0b110000` and the other numerals come after it in ascending order and | |
| 171 // take up at most four bits. | |
| 172 // | |
| 173 // We check for digits first because it ensures there's only a single branch | |
| 174 // for 10 out of 16 of the expected cases. We don't count the `digit >= 0` | |
| 175 // check because branch prediction will always work on it for valid data. | |
| 176 var codeUnit = codeUnits[index]; | |
| 177 var digit = $0 ^ codeUnit; | |
| 178 if (digit <= 9) { | |
| 179 if (digit >= 0) return digit; | |
| 180 } else { | |
| 181 // If the code unit is an uppercase letter, convert it to lowercase. This | |
| 182 // works because uppercase letters in ASCII are exactly `0b100000 = 0x20` | |
| 183 // less than lowercase letters, so if we ensure that that bit is 1 we ensure | |
| 184 // that the letter is lowercase. | |
| 185 var letter = 0x20 | codeUnit; | |
| 186 if ($a <= letter && letter <= $f) return letter - $a + 10; | |
| 187 } | |
| 188 | |
| 189 throw new FormatException( | |
| 190 "Invalid hexadecimal code unit " | |
| 191 "U+${codeUnit.toRadixString(16).padLeft(4, '0')}.", | |
| 192 codeUnits, index); | |
| 193 } | |
| OLD | NEW |