Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: utils/string_encoding/utf8_impl.dart

Issue 9410001: restructure string decoding to support iterable use and include benchmarks for UTF-8 decoding. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 final int _UTF8_ONE_BYTE_MAX = 0x7f; 5 final int _UTF8_ONE_BYTE_MAX = 0x7f;
6 final int _UTF8_TWO_BYTE_MAX = 0x7ff; 6 final int _UTF8_TWO_BYTE_MAX = 0x7ff;
7 final int _UTF8_THREE_BYTE_MAX = 0xffff; 7 final int _UTF8_THREE_BYTE_MAX = 0xffff;
8 8
9 final int _UTF8_LO_SIX_BIT_MASK = 0x3f; 9 final int _UTF8_LO_SIX_BIT_MASK = 0x3f;
10 10
11 final int _UTF8_FIRST_BYTE_OF_TWO_BASE = 0xc0; 11 final int _UTF8_FIRST_BYTE_OF_TWO_BASE = 0xc0;
12 final int _UTF8_FIRST_BYTE_OF_THREE_BASE = 0xe0; 12 final int _UTF8_FIRST_BYTE_OF_THREE_BASE = 0xe0;
13 final int _UTF8_FIRST_BYTE_OF_FOUR_BASE = 0xf0; 13 final int _UTF8_FIRST_BYTE_OF_FOUR_BASE = 0xf0;
14 final int _UTF8_FIRST_BYTE_OF_FIVE_BASE = 0xf8; 14 final int _UTF8_FIRST_BYTE_OF_FIVE_BASE = 0xf8;
15 final int _UTF8_FIRST_BYTE_OF_SIX_BASE = 0xfc; 15 final int _UTF8_FIRST_BYTE_OF_SIX_BASE = 0xfc;
16 16
17 final int _UTF8_FIRST_BYTE_OF_TWO_MASK = 0x1f; 17 final int _UTF8_FIRST_BYTE_OF_TWO_MASK = 0x1f;
18 final int _UTF8_FIRST_BYTE_OF_THREE_MASK = 0xf; 18 final int _UTF8_FIRST_BYTE_OF_THREE_MASK = 0xf;
19 final int _UTF8_FIRST_BYTE_OF_FOUR_MASK = 0x7; 19 final int _UTF8_FIRST_BYTE_OF_FOUR_MASK = 0x7;
20 20
21 final int _UTF8_FIRST_BYTE_BOUND_EXCL = 0xfe; 21 final int _UTF8_FIRST_BYTE_BOUND_EXCL = 0xfe;
22 final int _UTF8_SUBSEQUENT_BYTE_BASE = 0x80; 22 final int _UTF8_SUBSEQUENT_BYTE_BASE = 0x80;
23 23
24 /** 24 /**
25 * Produce a String from a sequence of UTF-8 encoded bytes. The parameters 25 * Decodes the UTF-8 bytes as an iterable. Thus, the consumer can only convert
26 * allow an offset into a list of bytes (as int), limiting the length of the 26 * as much of the input as needed. Set the replacementCharacter to null to
27 * values be decoded and the ability of override the default Unicode 27 * throw an IllegalArgumentException rather than replace the bad value.
28 * replacement character. Set the replacementCharacter to null to throw an
29 * IllegalArgumentException rather than replace the bad value.
30 */ 28 */
31 String decodeFromUtf8(List<int> bytes, [int offset = 0, int length, 29 IterableUtf8Decoder decodeUtf8AsIterable(List<int> bytes, [int offset = 0,
32 int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) => 30 int length,
33 codepointsToString(_utf8ToCodepoints( 31 int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) {
34 bytes, offset, length, replacementCodepoint)); 32 return new IterableUtf8Decoder(bytes, offset, length, replacementCodepoint);
33 }
34
35 /**
36 * Produce a String from a List of UTF-8 encoded bytes. The parameters
37 * can set an offset into a list of bytes (as int), limit the length of the
38 * values to be decoded, and override the default Unicode replacement character.
39 * Set the replacementCharacter to null to throw an IllegalArgumentException
40 * rather than replace the bad value.
41 */
42 String decodeUtf8(List<int> bytes, [int offset = 0, int length,
43 int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) {
44 return codepointsToString(
45 (new Utf8Decoder(bytes, offset, length, replacementCodepoint))
46 .decodeRest());
47 }
35 48
36 /** 49 /**
37 * Produce a sequence of UTF-8 encoded bytes from the provided string. 50 * Produce a sequence of UTF-8 encoded bytes from the provided string.
38 */ 51 */
39 List<int> encodeAsUtf8(String str) => 52 List<int> encodeUtf8(String str) =>
40 _codepointsToUtf8(stringToCodepoints(str)); 53 _codepointsToUtf8(stringToCodepoints(str));
41 54
42 int _addToEncoding(int offset, int bytes, int value, List<int> buffer) { 55 int _addToEncoding(int offset, int bytes, int value, List<int> buffer) {
43 while(bytes > 0) { 56 while (bytes > 0) {
44 buffer[offset + bytes] = _UTF8_SUBSEQUENT_BYTE_BASE | 57 buffer[offset + bytes] = _UTF8_SUBSEQUENT_BYTE_BASE |
45 (value & _UTF8_LO_SIX_BIT_MASK); 58 (value & _UTF8_LO_SIX_BIT_MASK);
46 value = value >> 6; 59 value = value >> 6;
47 bytes--; 60 bytes--;
48 } 61 }
49 return value; 62 return value;
50 } 63 }
51 64
52 /** 65 /**
53 * Encode code points as UTF-8 code units. 66 * Encode code points as UTF-8 code units.
54 */ 67 */
55 List<int> _codepointsToUtf8( 68 List<int> _codepointsToUtf8(
56 List<int> codepoints, [int offset = 0, int length]) { 69 List<int> codepoints, [int offset = 0, int length]) {
57 if (!(offset >= 0)) { 70 ListRange<int> source = new ListRange(codepoints, offset, length);
58 throw new IllegalArgumentException("offset");
59 }
60
61 if (!(length == null || length >= 0)) {
62 throw new IllegalArgumentException("length");
63 }
64
65 int end = length != null ?
66 Math.min(codepoints.length, offset + length) :
67 codepoints.length;
68 71
69 int encodedLength = 0; 72 int encodedLength = 0;
70 for (int i = offset; i < end; i++) { 73 for (int value in source) {
71 int value = codepoints[i];
72 if (value < 0 || value > UNICODE_VALID_RANGE_MAX) { 74 if (value < 0 || value > UNICODE_VALID_RANGE_MAX) {
73 encodedLength += 3; 75 encodedLength += 3;
74 } else if (value <= _UTF8_ONE_BYTE_MAX) { 76 } else if (value <= _UTF8_ONE_BYTE_MAX) {
75 encodedLength++; 77 encodedLength++;
76 } else if (value <= _UTF8_TWO_BYTE_MAX) { 78 } else if (value <= _UTF8_TWO_BYTE_MAX) {
77 encodedLength += 2; 79 encodedLength += 2;
78 } else if (value <= _UTF8_THREE_BYTE_MAX) { 80 } else if (value <= _UTF8_THREE_BYTE_MAX) {
79 encodedLength += 3; 81 encodedLength += 3;
80 } else if (value <= UNICODE_VALID_RANGE_MAX) { 82 } else if (value <= UNICODE_VALID_RANGE_MAX) {
81 encodedLength += 4; 83 encodedLength += 4;
82 } 84 }
83 } 85 }
84 86
85 List<int> encoded = new List<int>(encodedLength); 87 List<int> encoded = new List<int>(encodedLength);
86 int insertAt = 0; 88 int insertAt = 0;
87 for (int i = offset; i < end; i++) { 89 for (int value in source) {
88 int value = codepoints[i];
89 if (value < 0 || value > UNICODE_VALID_RANGE_MAX) { 90 if (value < 0 || value > UNICODE_VALID_RANGE_MAX) {
90 encoded.setRange(insertAt, 3, [0xef, 0xbf, 0xbd]); 91 encoded.setRange(insertAt, 3, [0xef, 0xbf, 0xbd]);
91 insertAt += 3; 92 insertAt += 3;
92 } else if (value <= _UTF8_ONE_BYTE_MAX) { 93 } else if (value <= _UTF8_ONE_BYTE_MAX) {
93 encoded[insertAt] = value; 94 encoded[insertAt] = value;
94 insertAt++; 95 insertAt++;
95 } else if (value <= _UTF8_TWO_BYTE_MAX) { 96 } else if (value <= _UTF8_TWO_BYTE_MAX) {
96 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_TWO_BASE | ( 97 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_TWO_BASE | (
97 _UTF8_FIRST_BYTE_OF_TWO_MASK & 98 _UTF8_FIRST_BYTE_OF_TWO_MASK &
98 _addToEncoding(insertAt, 1, value, encoded)); 99 _addToEncoding(insertAt, 1, value, encoded));
99 insertAt += 2; 100 insertAt += 2;
100 } else if (value <= _UTF8_THREE_BYTE_MAX) { 101 } else if (value <= _UTF8_THREE_BYTE_MAX) {
101 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_THREE_BASE | ( 102 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_THREE_BASE | (
102 _UTF8_FIRST_BYTE_OF_THREE_MASK & 103 _UTF8_FIRST_BYTE_OF_THREE_MASK &
103 _addToEncoding(insertAt, 2, value, encoded)); 104 _addToEncoding(insertAt, 2, value, encoded));
104 insertAt += 3; 105 insertAt += 3;
105 } else if (value <= UNICODE_VALID_RANGE_MAX) { 106 } else if (value <= UNICODE_VALID_RANGE_MAX) {
106 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_FOUR_BASE | ( 107 encoded[insertAt] = _UTF8_FIRST_BYTE_OF_FOUR_BASE | (
107 _UTF8_FIRST_BYTE_OF_FOUR_MASK & 108 _UTF8_FIRST_BYTE_OF_FOUR_MASK &
108 _addToEncoding(insertAt, 3, value, encoded)); 109 _addToEncoding(insertAt, 3, value, encoded));
109 insertAt += 4; 110 insertAt += 4;
110 } 111 }
111 } 112 }
112 return encoded; 113 return encoded;
113 } 114 }
114 115
115
116 // Because UTF-8 specifies byte order, we do not have to follow the pattern 116 // Because UTF-8 specifies byte order, we do not have to follow the pattern
117 // used by UTF-16 & UTF-32 regarding byte order. 117 // used by UTF-16 & UTF-32 regarding byte order.
118 List<int> _utf8ToCodepoints( 118 List<int> _utf8ToCodepoints(
119 List<int> utf8EncodedBytes, [int offset = 0, int length, 119 List<int> utf8EncodedBytes, [int offset = 0, int length,
120 int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { 120 int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) {
121 if (!(offset >= 0)) { 121 return new Utf8Decoder(utf8EncodedBytes, offset, length,
122 throw new IllegalArgumentException("offset"); 122 replacementCodepoint).decodeRest();
123 } 123 }
124 124
125 if (!(length == null || length >= 0)) { 125 /**
126 throw new IllegalArgumentException("length"); 126 * Return type of [decodeUtf8AsIterable] and variants. The Iterable type
127 } 127 * provides an iterator on demand and the iterator will only translate bytes
128 * as requested by the user of the iterator. (Note: results are not cached.)
129 */
130 class IterableUtf8Decoder implements Iterable<int> {
131 final List<int> bytes;
132 final int offset;
133 final int length;
134 final int replacementCodepoint;
128 135
129 int end = length != null ? 136 IterableUtf8Decoder(List<int> this.bytes, [int this.offset = 0,
130 Math.min(utf8EncodedBytes.length, offset + length) : 137 int this.length = null,
131 utf8EncodedBytes.length; 138 int this.replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]);
132 139
133 void decode(void f(int v)) { 140 Utf8Decoder iterator() => new Utf8Decoder(bytes, offset, length,
134 int i = offset; 141 replacementCodepoint);
135 while (i < end) { 142 }
136 int value = utf8EncodedBytes[i++];
137 if (value < 0) {
138 f(null);
139 continue;
140 }
141 143
142 if (value <= _UTF8_ONE_BYTE_MAX) { 144 /**
143 f(value); 145 * Provides an iterator of Unicode codepoints from UTF-8 encoded bytes. The
144 } else if (value < _UTF8_FIRST_BYTE_OF_TWO_BASE) { 146 * parameters can set an offset into a list of bytes (as int), limit the length
145 f(null); 147 * of the values to be decoded, and override the default Unicode replacement
146 continue; 148 * character. Set the replacementCharacter to null to throw an
147 } else { 149 * IllegalArgumentException rather than replace the bad value. The return value
148 int additionalBytes = 0; 150 * from this method can be used as an Iterable (e.g. in a for-loop).
149 if (value < _UTF8_FIRST_BYTE_OF_THREE_BASE) { 151 */
150 value -= _UTF8_FIRST_BYTE_OF_TWO_BASE; 152 class Utf8Decoder implements Iterator<int> {
151 additionalBytes = 1; 153 final ListRangeIterator<int> utf8EncodedBytesIterator;
152 } else if (value < _UTF8_FIRST_BYTE_OF_FOUR_BASE) { 154 final int replacementCodepoint;
153 value -= _UTF8_FIRST_BYTE_OF_THREE_BASE; 155
154 additionalBytes = 2; 156 Utf8Decoder(List<int> utf8EncodedBytes, [int offset = 0, int length,
155 } else if (value < _UTF8_FIRST_BYTE_OF_FIVE_BASE) { 157 int this.replacementCodepoint =
156 value -= _UTF8_FIRST_BYTE_OF_FOUR_BASE; 158 UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) :
157 additionalBytes = 3; 159 utf8EncodedBytesIterator = (new ListRange(utf8EncodedBytes, offset,
158 } else if (value < _UTF8_FIRST_BYTE_OF_SIX_BASE) { 160 length)).iterator();
159 value -= _UTF8_FIRST_BYTE_OF_FIVE_BASE; 161
160 additionalBytes = 4; 162
161 } else if (value < _UTF8_FIRST_BYTE_BOUND_EXCL) { 163 Utf8Decoder._fromListRangeIterator(ListRange<int> source, [
162 value -= _UTF8_FIRST_BYTE_OF_SIX_BASE; 164 int this.replacementCodepoint =
163 additionalBytes = 5; 165 UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) :
164 } else { 166 utf8EncodedBytesIterator = source.iterator();
165 f(null); 167
166 continue; 168 /** Decode the remaininder of the characters in this decoder
167 } 169 * into a [List<int>].
168 int j = 0; 170 */
169 while (j < additionalBytes && i < end) { 171 List<int> decodeRest() {
170 int nextValue = utf8EncodedBytes[i++]; 172 List<int> codepoints = new List<int>(utf8EncodedBytesIterator.remaining);
171 if (nextValue > _UTF8_ONE_BYTE_MAX && 173 int i = 0;
172 nextValue < _UTF8_FIRST_BYTE_OF_TWO_BASE) { 174 while (hasNext()) {
173 value = (value << 6) | (nextValue & _UTF8_LO_SIX_BIT_MASK); 175 codepoints[i++] = next();
174 } else { 176 }
175 // if sequence-starting code unit, reposition cursor to start here 177 if (i == codepoints.length) {
176 if (nextValue >= _UTF8_FIRST_BYTE_OF_TWO_BASE) { 178 return codepoints;
177 i--; 179 } else {
178 } 180 List<int> truncCodepoints = new List<int>(i);
179 break; 181 truncCodepoints.setRange(0, i, codepoints);
180 } 182 return truncCodepoints;
181 j++;
182 }
183 if (j == additionalBytes && (
184 value < UNICODE_UTF16_RESERVED_LO ||
185 value > UNICODE_UTF16_RESERVED_HI)) {
186 if ((additionalBytes == 1 && value > _UTF8_ONE_BYTE_MAX) ||
187 (additionalBytes == 2 && value > _UTF8_TWO_BYTE_MAX) ||
188 (additionalBytes == 3 && value > _UTF8_THREE_BYTE_MAX &&
189 value <= UNICODE_VALID_RANGE_MAX)) {
190 f(value);
191 } else {
192 f(null);
193 }
194 } else {
195 f(null);
196 continue;
197 }
198 }
199 } 183 }
200 } 184 }
201 185
202 // First pass through data to 1) size the output buffer and 2) check for 186 bool hasNext() => utf8EncodedBytesIterator.hasNext();
203 // special case optimization where A) the length stays the same and B) 187
204 // no special replacement characters are used. If these criteria are met 188 int next() {
205 // we can just copy input to the output. 189 int value = utf8EncodedBytesIterator.next();
206 int codepointBufferLength = 0; 190 int additionalBytes = 0;
207 bool hasReplacements = false; 191
208 decode(void _(int value) { 192 if (value < 0) {
209 codepointBufferLength++; 193 if (replacementCodepoint != null) {
210 if (value == null) { 194 return replacementCodepoint;
211 hasReplacements = true; 195 } else {
196 throw new IllegalArgumentException(
197 "Invalid UTF8 at ${utf8EncodedBytesIterator.position}");
212 } 198 }
213 }); 199 } else if (value <= _UTF8_ONE_BYTE_MAX) {
214 200 return value;
215 // If the string calls for replacements, but when the method is called 201 } else if (value < _UTF8_FIRST_BYTE_OF_TWO_BASE) {
216 // with replacementCodepoint explicitly set to null, then throw an exception. 202 if (replacementCodepoint != null) {
217 if (hasReplacements && replacementCodepoint == null) { 203 return replacementCodepoint;
218 throw new IllegalArgumentException("Invalid encoding"); 204 } else {
205 throw new IllegalArgumentException(
206 "Invalid UTF8 at ${utf8EncodedBytesIterator.position}");
207 }
208 } else if (value < _UTF8_FIRST_BYTE_OF_THREE_BASE) {
209 value -= _UTF8_FIRST_BYTE_OF_TWO_BASE;
210 additionalBytes = 1;
211 } else if (value < _UTF8_FIRST_BYTE_OF_FOUR_BASE) {
212 value -= _UTF8_FIRST_BYTE_OF_THREE_BASE;
213 additionalBytes = 2;
214 } else if (value < _UTF8_FIRST_BYTE_OF_FIVE_BASE) {
215 value -= _UTF8_FIRST_BYTE_OF_FOUR_BASE;
216 additionalBytes = 3;
217 } else if (value < _UTF8_FIRST_BYTE_OF_SIX_BASE) {
218 value -= _UTF8_FIRST_BYTE_OF_FIVE_BASE;
219 additionalBytes = 4;
220 } else if (value < _UTF8_FIRST_BYTE_BOUND_EXCL) {
221 value -= _UTF8_FIRST_BYTE_OF_SIX_BASE;
222 additionalBytes = 5;
223 } else if (replacementCodepoint != null) {
224 return replacementCodepoint;
225 } else {
226 throw new IllegalArgumentException(
227 "Invalid UTF8 at ${utf8EncodedBytesIterator.position}");
228 }
229 int j = 0;
230 while (j < additionalBytes && utf8EncodedBytesIterator.hasNext()) {
231 int nextValue = utf8EncodedBytesIterator.next();
232 if (nextValue > _UTF8_ONE_BYTE_MAX &&
233 nextValue < _UTF8_FIRST_BYTE_OF_TWO_BASE) {
234 value = ((value << 6) | (nextValue & _UTF8_LO_SIX_BIT_MASK));
235 } else {
236 // if sequence-starting code unit, reposition cursor to start here
237 if (nextValue >= _UTF8_FIRST_BYTE_OF_TWO_BASE) {
238 utf8EncodedBytesIterator.backup();
239 }
240 break;
241 }
242 j++;
243 }
244 bool validSequence = (j == additionalBytes && (
245 value < UNICODE_UTF16_RESERVED_LO ||
246 value > UNICODE_UTF16_RESERVED_HI));
247 bool nonOverlong =
248 (additionalBytes == 1 && value > _UTF8_ONE_BYTE_MAX) ||
249 (additionalBytes == 2 && value > _UTF8_TWO_BYTE_MAX) ||
250 (additionalBytes == 3 && value > _UTF8_THREE_BYTE_MAX);
251 bool inRange = value <= UNICODE_VALID_RANGE_MAX;
252 if (validSequence && nonOverlong && inRange) {
253 return value;
254 } else if (replacementCodepoint != null) {
255 return replacementCodepoint;
256 } else {
257 throw new IllegalArgumentException(
258 "Invalid UTF8 at ${utf8EncodedBytesIterator.position - j}");
259 }
219 } 260 }
220
221 int _length = end - offset;
222 List<int> codepointBuffer = new List<int>(codepointBufferLength);
223 if (_length == codepointBufferLength && !hasReplacements) {
224 codepointBuffer.setRange(0, _length, utf8EncodedBytes, offset);
225 } else {
226 int i = 0;
227 decode(
228 void _(int value) {
229 if (value != null) {
230 codepointBuffer[i++] = value;
231 } else {
232 codepointBuffer[i++] = replacementCodepoint;
233 }
234 }
235 );
236 }
237 return codepointBuffer;
238 } 261 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698