OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 library validation_input_parser; | |
6 | |
7 import 'dart:typed_data'; | |
8 | |
9 class ValidationParseResult { | |
10 final Iterable<_Entry> _entries; | |
11 final int numHandles; | |
12 final ByteData data; | |
13 | |
14 ValidationParseResult(this._entries, this.data, this.numHandles); | |
15 | |
16 String toString() => _entries.map((e) => '$e').join('\n'); | |
17 } | |
18 | |
19 ValidationParseResult parse(String input) => | |
20 new _ValidationTestParser(input).parse(); | |
21 | |
22 class ValidationParseError { | |
23 final String _message; | |
24 ValidationParseError(this._message); | |
25 String toString() => _message; | |
26 } | |
27 | |
28 abstract class _Entry { | |
29 final int size; | |
30 void write(ByteData buffer, int offset, Map pointers); | |
31 } | |
32 | |
33 class _UnsignedEntry implements _Entry { | |
34 final int size; | |
35 final int value; | |
36 | |
37 _UnsignedEntry(this.size, this.value) { | |
38 if ((value >= (1 << (size * 8))) || (value < 0)) { | |
39 throw new ValidationParseError('$value does not fit in a u$size'); | |
40 } | |
41 } | |
42 | |
43 void write(ByteData buffer, int offset, Map pointers) { | |
44 switch (size) { | |
45 case 1: buffer.setUint8(offset, value); break; | |
46 case 2: buffer.setUint16(offset, value, Endianness.LITTLE_ENDIAN); break; | |
47 case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break; | |
48 case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break; | |
49 default: throw new ValidationParseError('Unexpected size: $size'); | |
50 } | |
51 } | |
52 | |
53 String toString() => "[u$size]$value"; | |
54 bool operator==(_UnsignedEntry other) => | |
55 (size == other.size) && (value == other.value); | |
56 } | |
57 | |
58 class _SignedEntry implements _Entry { | |
59 final int size; | |
60 final int value; | |
61 | |
62 _SignedEntry(this.size, this.value) { | |
63 if ((value >= (1 << ((size * 8) - 1))) || | |
64 (value < -(1 << ((size * 8) - 1)))) { | |
65 throw new ValidationParseError('$value does not fit in a s$size'); | |
66 } | |
67 } | |
68 | |
69 void write(ByteData buffer, int offset, Map pointers) { | |
70 switch (size) { | |
71 case 1: buffer.setInt8(offset, value); break; | |
72 case 2: buffer.setInt16(offset, value, Endianness.LITTLE_ENDIAN); break; | |
73 case 4: buffer.setInt32(offset, value, Endianness.LITTLE_ENDIAN); break; | |
74 case 8: buffer.setInt64(offset, value, Endianness.LITTLE_ENDIAN); break; | |
75 default: throw new ValidationParseError('Unexpected size: $size'); | |
76 } | |
77 } | |
78 | |
79 String toString() => "[s$size]$value"; | |
80 bool operator==(_SignedEntry other) => | |
81 (size == other.size) && (value == other.value); | |
82 } | |
83 | |
84 class _FloatEntry implements _Entry { | |
85 final int size; | |
86 final double value; | |
87 | |
88 _FloatEntry(this.size, this.value); | |
89 | |
90 void write(ByteData buffer, int offset, Map pointers) { | |
91 switch (size) { | |
92 case 4: buffer.setFloat32(offset, value, Endianness.LITTLE_ENDIAN); break; | |
93 case 8: buffer.setFloat64(offset, value, Endianness.LITTLE_ENDIAN); break; | |
94 default: throw new ValidationParseError('Unexpected size: $size'); | |
95 } | |
96 } | |
97 | |
98 String toString() => "[f$size]$value"; | |
99 bool operator==(_FloatEntry other) => | |
100 (size == other.size) && (value == other.value); | |
101 } | |
102 | |
103 class _DistEntry implements _Entry { | |
104 final int size; | |
105 final String id; | |
106 int offset; | |
107 bool matched = false; | |
108 | |
109 _DistEntry(this.size, this.id); | |
110 | |
111 void write(ByteData buffer, int off, Map pointers) { | |
112 offset = off; | |
113 if (pointers[id] != null) { | |
114 throw new ValidationParseError( | |
115 'Pointer of same name already exists: $id'); | |
116 } | |
117 pointers[id] = this; | |
118 } | |
119 | |
120 String toString() => "[dist$size]$id matched = $matched"; | |
121 bool operator==(_DistEntry other) => | |
122 (size == other.size) && (id == other.id); | |
123 } | |
124 | |
125 class _AnchrEntry implements _Entry { | |
126 final int size = 0; | |
127 final String id; | |
128 | |
129 _AnchrEntry(this.id); | |
130 | |
131 void write(ByteData buffer, int off, Map pointers) { | |
132 _DistEntry dist = pointers[id]; | |
133 if (dist == null) { | |
134 throw new ValidationParseError('Did not find "$id" in pointers map.'); | |
135 } | |
136 int value = off - dist.offset; | |
137 if (value < 0) { | |
138 throw new ValidationParseError('Found a backwards pointer: $id'); | |
139 } | |
140 int offset = dist.offset; | |
141 switch (dist.size) { | |
142 case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break; | |
143 case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break; | |
144 default: throw new ValidationParseError('Unexpected size: $size'); | |
145 } | |
146 dist.matched = true; | |
147 } | |
148 | |
149 String toString() => "[anchr]$id"; | |
150 bool operator==(_AnchrEntry other) => (id == other.id); | |
151 } | |
152 | |
153 class _HandlesEntry implements _Entry { | |
154 final int size = 0; | |
155 final int value; | |
156 | |
157 _HandlesEntry(this.value); | |
158 | |
159 void write(ByteData buffer, int offset, Map pointers) {} | |
160 | |
161 String toString() => "[handles]$value"; | |
162 bool operator==(_HandlesEntry other) => (value == other.value); | |
163 } | |
164 | |
165 class _CommentEntry implements _Entry { | |
166 final int size = 0; | |
167 final String value; | |
168 | |
169 _CommentEntry(this.value); | |
170 | |
171 void write(ByteData buffer, int offset, Map pointers) {} | |
172 | |
173 String toString() => "// $value"; | |
174 bool operator==(_CommentEntry other) => (value == other.value); | |
175 } | |
176 | |
177 class _ValidationTestParser { | |
178 static final RegExp newline = new RegExp(r'[\r\n]+'); | |
179 static final RegExp whitespace = new RegExp(r'[ \t\n\r]+'); | |
180 static final RegExp nakedUintRegExp = | |
181 new RegExp(r'^0$|^[1-9][0-9]*$|^0[xX][0-9a-fA-F]+$'); | |
182 static final RegExp unsignedRegExp = | |
183 new RegExp(r'^\[u([1248])\](0$|[1-9][0-9]*$|0[xX][0-9a-fA-F]+$)'); | |
184 static final RegExp signedRegExp = new RegExp( | |
185 r'^\[s([1248])\]([-+]?0$|[-+]?[1-9][0-9]*$|[-+]?0[xX][0-9a-fA-F]+$)'); | |
186 static final RegExp binaryRegExp = | |
187 new RegExp(r'^\[(b)\]([01]{8}$)'); | |
188 static final RegExp floatRegExp = | |
189 new RegExp(r'^\[([fd])\]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$)'); | |
190 static final RegExp distRegExp = | |
191 new RegExp(r'^\[dist([48])\]([0-9a-zA-Z_]+$)'); | |
192 static final RegExp anchrRegExp = | |
193 new RegExp(r'^\[(anchr)\]([0-9a-zA-Z_]+$)'); | |
194 static final RegExp handlesRegExp = | |
195 new RegExp(r'^\[(handles)\](0$|([1-9][0-9]*$)|(0[xX][0-9a-fA-F]+$))'); | |
196 static final RegExp commentRegExp = | |
197 new RegExp(r'//(.*)'); | |
198 | |
199 String _input; | |
200 Map<String, _DistEntry> _pointers; | |
201 | |
202 _ValidationTestParser(this._input) : _pointers = {}; | |
203 | |
204 String _stripComment(String line) => line.replaceFirst(commentRegExp, ""); | |
205 | |
206 ValidationParseResult parse() { | |
207 var entries = _input.split(newline) | |
208 .map(_stripComment) | |
209 .expand((s) => s.split(whitespace)) | |
210 .where((s) => s != "") | |
211 .map(_parseLine); | |
212 int size = _calculateSize(entries); | |
213 var data = (size > 0) ? new ByteData(size) : null; | |
214 int numHandles = 0; | |
215 int offset = 0; | |
216 bool first = true; | |
217 | |
218 for (var entry in entries) { | |
219 entry.write(data, offset, _pointers); | |
220 offset += entry.size; | |
221 | |
222 if (entry is _HandlesEntry) { | |
223 if (!first) { | |
224 throw new ValidationParseError('Handles entry was not first'); | |
225 } | |
226 numHandles = entry.value; | |
227 } | |
228 first = false; | |
229 } | |
230 | |
231 for (var entry in entries) { | |
232 if (entry is _DistEntry) { | |
233 if (!_pointers[entry.id].matched) { | |
234 throw new ValidationParseError('Unmatched dist: $entry'); | |
235 } | |
236 } | |
237 } | |
238 | |
239 return new ValidationParseResult(entries, data, numHandles); | |
240 } | |
241 | |
242 _Entry _parseLine(String line) { | |
243 if (unsignedRegExp.hasMatch(line)) { | |
244 var match = unsignedRegExp.firstMatch(line); | |
245 return new _UnsignedEntry( | |
246 int.parse(match.group(1)), int.parse(match.group(2))); | |
247 } else if (signedRegExp.hasMatch(line)) { | |
248 var match = signedRegExp.firstMatch(line); | |
249 return new _SignedEntry( | |
250 int.parse(match.group(1)), int.parse(match.group(2))); | |
251 } else if (binaryRegExp.hasMatch(line)) { | |
252 var match = binaryRegExp.firstMatch(line); | |
253 return new _UnsignedEntry(1, int.parse(match.group(2), radix: 2)); | |
254 } else if (floatRegExp.hasMatch(line)) { | |
255 var match = floatRegExp.firstMatch(line); | |
256 int size = match.group(1) == 'f' ? 4 : 8; | |
257 return new _FloatEntry(size, double.parse(match.group(2))); | |
258 } else if (distRegExp.hasMatch(line)) { | |
259 var match = distRegExp.firstMatch(line); | |
260 return new _DistEntry(int.parse(match.group(1)), match.group(2)); | |
261 } else if (anchrRegExp.hasMatch(line)) { | |
262 var match = anchrRegExp.firstMatch(line); | |
263 return new _AnchrEntry(match.group(2)); | |
264 } else if (handlesRegExp.hasMatch(line)) { | |
265 var match = handlesRegExp.firstMatch(line); | |
266 return new _HandlesEntry(int.parse(match.group(2))); | |
267 } else if (nakedUintRegExp.hasMatch(line)) { | |
268 var match = nakedUintRegExp.firstMatch(line); | |
269 return new _UnsignedEntry(1, int.parse(match.group(0))); | |
270 } else if (commentRegExp.hasMatch(line)) { | |
271 var match = commentRegExp.firstMatch(line); | |
272 return new _CommentEntry(match.group(1)); | |
273 } else if (line == "") { | |
274 return new _CommentEntry(""); | |
275 } else { | |
276 throw new ValidationParseError('Unkown entry: "$line" in \n$_input'); | |
277 } | |
278 } | |
279 | |
280 int _calculateSize(Iterable<_Entry> entries) => | |
281 entries.fold(0, (value, entry) => value + entry.size); | |
282 } | |
283 | |
284 bool _listEquals(Iterable i1, Iterable i2) { | |
285 var l1 = i1.toList(); | |
286 var l2 = i2.toList(); | |
287 if (l1.length != l2.length) return false; | |
288 for (int i = 0; i < l1.length; i++) { | |
289 if (l1[i] != l2[i]) return false; | |
290 } | |
291 return true; | |
292 } | |
293 | |
294 parserTests() { | |
295 { | |
296 var input = " \t // hello world \n\r \t// the answer is 42 "; | |
297 var result = parse(input); | |
298 assert(result.data == null); | |
299 assert(result.numHandles == 0); | |
300 } | |
301 { | |
302 var input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" | |
303 "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; | |
304 var result = parse(input); | |
305 | |
306 // Check the parse results. | |
307 var expected = [new _UnsignedEntry(1, 0x10), | |
308 new _UnsignedEntry(2, 65535), | |
309 new _UnsignedEntry(4, 65536), | |
310 new _UnsignedEntry(8, 0xFFFFFFFFFFFFFFFF), | |
311 new _UnsignedEntry(1, 0), | |
312 new _UnsignedEntry(1, 0xff)]; | |
313 assert(_listEquals(result._entries, expected)); | |
314 | |
315 //Check the bits. | |
316 var buffer = new ByteData(17); | |
317 var offset = 0; | |
318 buffer.setUint8(offset, 0x10); offset++; | |
319 buffer.setUint16(offset, 65535, Endianness.LITTLE_ENDIAN); offset += 2; | |
320 buffer.setUint32(offset, 65536, Endianness.LITTLE_ENDIAN); offset += 4; | |
321 buffer.setUint64(offset, 0xFFFFFFFFFFFFFFFF, Endianness.LITTLE_ENDIAN); offs
et += 8; | |
322 buffer.setUint8(offset, 0); offset++; | |
323 buffer.setUint8(offset, 0xff); offset++; | |
324 assert(_listEquals(buffer.buffer.asUint8List(), | |
325 result.data.buffer.asUint8List())); | |
326 } | |
327 { | |
328 var input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; | |
329 var result = parse(input); | |
330 | |
331 // Check the parse results. | |
332 var expected = [new _SignedEntry(8, -0x800), | |
333 new _SignedEntry(1, -128), | |
334 new _SignedEntry(2, 0), | |
335 new _SignedEntry(4, -40)]; | |
336 assert(_listEquals(result._entries, expected)); | |
337 | |
338 // Check the bits. | |
339 var buffer = new ByteData(15); | |
340 var offset = 0; | |
341 buffer.setInt64(offset, -0x800, Endianness.LITTLE_ENDIAN); offset += 8; | |
342 buffer.setInt8(offset, -128); offset += 1; | |
343 buffer.setInt16(offset, 0, Endianness.LITTLE_ENDIAN); offset += 2; | |
344 buffer.setInt32(offset, -40, Endianness.LITTLE_ENDIAN); offset += 4; | |
345 assert(_listEquals(buffer.buffer.asUint8List(), | |
346 result.data.buffer.asUint8List())); | |
347 } | |
348 { | |
349 var input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; | |
350 var result = parse(input); | |
351 | |
352 // Check the parse results; | |
353 var expected = [new _UnsignedEntry(1, 11), | |
354 new _UnsignedEntry(1, 128), | |
355 new _UnsignedEntry(1, 0)]; | |
356 assert(_listEquals(result._entries, expected)); | |
357 | |
358 // Check the bits. | |
359 var buffer = new ByteData(3); | |
360 var offset = 0; | |
361 buffer.setUint8(offset, 11); offset += 1; | |
362 buffer.setUint8(offset, 128); offset += 1; | |
363 buffer.setUint8(offset, 0); offset += 1; | |
364 assert(_listEquals(buffer.buffer.asUint8List(), | |
365 result.data.buffer.asUint8List())); | |
366 } | |
367 { | |
368 var input = "[f]+.3e9 [d]-10.03"; | |
369 var result = parse(input); | |
370 | |
371 // Check the parse results. | |
372 var expected = [new _FloatEntry(4, 0.3e9), | |
373 new _FloatEntry(8,-10.03)]; | |
374 assert(_listEquals(result._entries, expected)); | |
375 | |
376 // Check the bits. | |
377 var buffer = new ByteData(12); | |
378 var offset = 0; | |
379 buffer.setFloat32(offset, 0.3e9, Endianness.LITTLE_ENDIAN); offset += 4; | |
380 buffer.setFloat64(offset, -10.03, Endianness.LITTLE_ENDIAN); offset += 8; | |
381 assert(_listEquals(buffer.buffer.asUint8List(), | |
382 result.data.buffer.asUint8List())); | |
383 } | |
384 { | |
385 var input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; | |
386 var result = parse(input); | |
387 | |
388 // Check the parse results. | |
389 var expected = [new _DistEntry(4, "foo"), | |
390 new _UnsignedEntry(1, 0), | |
391 new _DistEntry(8, "bar"), | |
392 new _UnsignedEntry(1, 0), | |
393 new _AnchrEntry("foo"), | |
394 new _AnchrEntry("bar")]; | |
395 assert(_listEquals(result._entries, expected)); | |
396 | |
397 // Check the bits. | |
398 var buffer = new ByteData(14); | |
399 var offset = 0; | |
400 buffer.setUint32(offset, 14, Endianness.LITTLE_ENDIAN); offset += 4; | |
401 buffer.setUint8(offset, 0); offset += 1; | |
402 buffer.setUint64(offset, 9, Endianness.LITTLE_ENDIAN); offset += 8; | |
403 buffer.setUint8(offset, 0); offset += 1; | |
404 assert(_listEquals(buffer.buffer.asUint8List(), | |
405 result.data.buffer.asUint8List())); | |
406 } | |
407 { | |
408 var input = "// This message has handles! \n[handles]50 [u8]2"; | |
409 var result = parse(input); | |
410 var expected = [new _HandlesEntry(50), | |
411 new _UnsignedEntry(8, 2)]; | |
412 assert(_listEquals(result._entries, expected)); | |
413 } | |
414 { | |
415 var errorInputs = ["/ hello world", | |
416 "[u1]x", | |
417 "[u2]-1000", | |
418 "[u1]0x100", | |
419 "[s2]-0x8001", | |
420 "[b]1", | |
421 "[b]1111111k", | |
422 "[dist4]unmatched", | |
423 "[anchr]hello [dist8]hello", | |
424 "[dist4]a [dist4]a [anchr]a", | |
425 "[dist4]a [anchr]a [dist4]a [anchr]a", | |
426 "0 [handles]50"]; | |
427 for (var input in errorInputs) { | |
428 try { | |
429 var result = parse(input); | |
430 assert(false); | |
431 } on ValidationParseError catch(e) { | |
432 // Pass. | |
433 } | |
434 } | |
435 } | |
436 } | |
437 | |
OLD | NEW |