OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
Matt Perry
2014/07/23 00:39:38
2014
hansmuller
2014/07/23 18:06:35
Sorry about that, I think I fixed the date problem
| |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 define([ | |
6 "mojo/public/js/bindings/buffer" | |
7 ], function (Buffer) { | |
abarth-chromium
2014/07/23 02:13:52
No space between function and (
hansmuller
2014/07/23 18:06:35
Done.
| |
8 | |
9 // Files and Lines represent the raw text from a ".data" file. | |
yzshen1
2014/07/23 07:26:55
This is not a very useful comment. You could proba
hansmuller
2014/07/23 18:06:35
Done.
| |
10 | |
11 function InputError(message, line) { | |
12 this.message = message; | |
13 this.line = line; | |
14 } | |
15 | |
16 InputError.prototype.toString = function() { | |
17 var s = 'Error: ' + this.message; | |
18 if (this.line) | |
19 s += ', at line ' + | |
20 (this.line.number + 1) + ': "' + this.line.contents + '"'; | |
21 return s; | |
22 } | |
23 | |
24 function File(contents) { | |
25 this.contents = contents; | |
26 this.index = 0; | |
27 this.lineNumber = 0; | |
28 } | |
29 | |
30 File.prototype.isEmpty = function() { | |
31 return this.index >= this.contents.length; | |
32 } | |
33 | |
34 File.prototype.nextLine = function() { | |
35 if (this.isEmpty()) | |
36 return null; | |
37 var start = this.index; | |
38 var end = this.contents.indexOf('\n', start); | |
39 if (end == -1) | |
40 end = this.contents.length; | |
41 this.index = end + 1; | |
42 return new Line(this.contents.substring(start, end), this.lineNumber++); | |
43 } | |
44 | |
45 function Line(contents, number) { | |
46 var i = contents.indexOf('//'); | |
47 var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim(); | |
48 this.contents = contents; | |
49 this.items = (s.length > 0) ? s.split(/\s+/) : []; | |
50 this.index = 0; | |
51 this.number = number; | |
52 } | |
53 | |
54 Line.prototype.isEmpty = function() { | |
55 return this.index >= this.items.length; | |
56 } | |
57 | |
58 var itemTypeSizes = { | |
59 u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8, | |
60 dist4: 4, dist8: 8, anchr: 0, handles: 0 | |
61 }; | |
62 | |
63 function isValidItemType(type) { | |
64 return itemTypeSizes[type] !== undefined; | |
65 } | |
66 | |
67 Line.prototype.nextItem = function() { | |
68 if (this.isEmpty()) | |
69 return null; | |
70 | |
71 var itemString = this.items[this.index++]; | |
72 var type = 'u1'; | |
73 var value = itemString; | |
74 | |
75 if (itemString.charAt(0) == '[') { | |
76 var i = itemString.indexOf(']'); | |
77 if (i != -1 && i + 1 < itemString.length) { | |
78 type = itemString.substring(1, i); | |
79 value = itemString.substring(i + 1); | |
80 } else { | |
81 throw new InputError('invalid item', this); | |
82 } | |
83 } | |
84 if (!isValidItemType(type)) | |
85 throw new InputError('invalid item type', this); | |
86 | |
87 return new Item(this, type, value); | |
88 } | |
89 | |
90 // The text for Each whitespace delimited binary data "item" loaded | |
91 // from a ".data" file is represented by an Item. | |
92 | |
93 function Item(line, type, value) { | |
94 this.line = line; | |
95 this.type = type; | |
96 this.value = value; | |
97 this.size = itemTypeSizes[type]; | |
98 } | |
99 | |
100 Item.prototype.isFloat = function() { | |
101 return this.type == 'f' || this.type == 'd'; | |
102 } | |
103 | |
104 Item.prototype.isInteger = function() { | |
105 return ['u1', 'u2', 'u4', 'u8', | |
106 's1', 's2', 's4', 's8'].indexOf(this.type) != -1; | |
107 } | |
108 | |
109 Item.prototype.isNumber = function() { | |
110 return this.isFloat() || this.isInteger(); | |
111 } | |
112 | |
113 Item.prototype.isByte = function() { | |
114 return this.type == 'b'; | |
115 } | |
116 | |
117 Item.prototype.isDistance = function() { | |
118 return this.type == 'dist4' || this.type == 'dist8'; | |
119 } | |
120 | |
121 Item.prototype.isAnchor = function() { | |
122 return this.type == 'anchr'; | |
123 } | |
124 | |
125 Item.prototype.isHandles = function() { | |
126 return this.type == 'handles'; | |
127 } | |
128 | |
129 // A TestMessage represents the complete binary message loaded from | |
130 // ".data" file. The parseTestMessage() function below constructs a | |
131 // TestMessage from a File. | |
132 | |
133 function TestMessage(byteLength) { | |
134 this.index = 0; | |
135 this.buffer = new Buffer(byteLength); | |
136 this.distances = {}; | |
137 this.handleCount = 0; | |
138 } | |
139 | |
140 function checkItemNumberValue(item, n, min, max) { | |
141 if (n < min || n > max) | |
142 throw new InputError('invalid item value', item.line); | |
143 } | |
144 | |
145 TestMessage.prototype.addNumber = function(item) { | |
146 var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value); | |
147 if (Number.isNaN(n)) | |
148 throw new InputError("can't parse item value", item.line); | |
149 | |
150 switch(item.type) { | |
151 case 'u1': | |
152 checkItemNumberValue(item, n, 0, 0xFF); | |
153 this.buffer.setUint8(this.index, n); | |
154 this.index += 1; | |
155 break; | |
156 case 'u2': | |
157 checkItemNumberValue(item, n, 0, 0xFFFF); | |
158 this.buffer.setUint16(this.index, n); | |
159 this.index += 2; | |
160 break; | |
161 case 'u4': | |
162 checkItemNumberValue(item, n, 0, 0xFFFFFFFF); | |
163 this.buffer.setUint32(this.index, n); | |
164 this.index += 4; | |
165 break; | |
166 case 'u8': | |
167 checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER); | |
168 this.buffer.setUint64(this.index, n); | |
169 this.index += 8; | |
170 break; | |
171 case 's1': | |
172 checkItemNumberValue(item, n, -128, 127); | |
173 this.buffer.setInt8(this.index, n); | |
174 this.index += 1; | |
175 break; | |
176 case 's2': | |
177 checkItemNumberValue(item, n, -32768, 32767); | |
178 this.buffer.setInt16(this.index, n); | |
179 this.index += 2; | |
180 break; | |
181 case 's4': | |
182 checkItemNumberValue(item, n, -2147483648, 2147483647); | |
183 this.buffer.setInt32(this.index, n); | |
184 this.index += 4; | |
185 break; | |
186 case 's8': | |
187 checkItemNumberValue(item, n, | |
188 Number.MIN_SAFE_INTEGER, | |
189 Number.MAX_SAFE_INTEGER); | |
190 this.buffer.setInt64(this.index, n); | |
191 this.index += 8; | |
192 break; | |
193 case 'f': | |
194 this.buffer.setFloat32(this.index, n); | |
195 this.index += 4; | |
196 break; | |
197 case 'd': | |
198 this.buffer.setFloat64(this.index, n); | |
199 this.index += 8; | |
200 break; | |
201 | |
202 default: | |
203 throw new InputError('unrecognized item type', item.line); | |
204 } | |
205 } | |
206 | |
207 TestMessage.prototype.addByte = function(item) { | |
208 if (!/^[01]{8}$/.test(item.value)) | |
209 throw new InputError('invalid byte item value', item.line); | |
210 function b(i) { | |
211 return (item.value.charAt(7 - i) == '1') ? 1 << i : 0; | |
212 } | |
213 var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7); | |
214 this.buffer.setUint8(this.index, n); | |
215 this.index += 1; | |
216 } | |
217 | |
218 TestMessage.prototype.addDistance = function(item) { | |
219 if (this.distances[item.value]) | |
220 throw new InputError('duplicate distance item', item.line); | |
221 this.distances[item.value] = {index: this.index, item: item}; | |
222 this.index += item.type == 'dist4' ? 4 : 8; | |
223 } | |
224 | |
225 TestMessage.prototype.addAnchor = function(item) { | |
226 var dist = this.distances[item.value]; | |
227 if (!dist) | |
228 throw new InputError('unmatched anchor item', item.line); | |
229 delete this.distances[item.value]; | |
230 | |
231 var n = this.index - dist.index; | |
232 // TODO(hansmuller): validate n | |
233 | |
234 if (dist.item.type == 'dist4') | |
235 this.buffer.setUint32(dist.index, n); | |
236 else if (dist.item.type == 'dist8') | |
237 this.buffer.setUint64(dist.index, n); | |
238 else | |
239 throw new InputError('unrecognzed distance item type', dist.item.line); | |
240 } | |
241 | |
242 TestMessage.prototype.addHandles = function(item) { | |
243 this.handleCount = parseInt(item.value); | |
244 if (Number.isNaN(this.handleCount)) | |
245 throw new InputError("can't parse handleCount", item.line); | |
246 } | |
247 | |
248 TestMessage.prototype.addItem = function(item) { | |
249 if (item.isNumber()) | |
250 this.addNumber(item); | |
251 else if (item.isByte()) | |
252 this.addByte(item); | |
253 else if (item.isDistance()) | |
254 this.addDistance(item); | |
255 else if (item.isAnchor()) | |
256 this.addAnchor(item); | |
257 else if (item.isHandles()) | |
258 this.addHandles(item); | |
259 else | |
260 throw new InputError('unrecognized item type', item.line); | |
261 } | |
262 | |
263 TestMessage.prototype.unanchoredDistances = function() { | |
264 var names = null; | |
265 for (var name in this.distances) { | |
266 if (this.distances.hasOwnProperty(name)) | |
267 names = (names === null) ? name : names + ' ' + name; | |
268 } | |
269 return names; | |
270 } | |
271 | |
272 function parseTestMessage(file) { | |
273 var items = []; | |
274 var messageLength = 0; | |
275 while(!file.isEmpty()) { | |
276 var line = file.nextLine(); | |
277 while (!line.isEmpty()) { | |
278 var item = line.nextItem(); | |
279 if (item.isHandles() && items.length > 0) | |
280 throw new InputError('handles item is not first'); | |
281 messageLength += item.size; | |
282 items.push(item); | |
283 } | |
284 } | |
285 | |
286 var msg = new TestMessage(messageLength); | |
287 for (var i = 0; i < items.length; i++) | |
288 msg.addItem(items[i]); | |
289 | |
290 if (messageLength != msg.index) | |
291 throw new InputError('failed to compute message length'); | |
292 var names = msg.unanchoredDistances(); | |
293 if (names) | |
294 throw new InputError('no anchors for ' + names, 0); | |
295 | |
296 return msg; | |
297 } | |
298 | |
299 // Verify that the TestMessage (et al) data file loading code is OK. | |
300 | |
301 function checkMessageFileParser() { | |
302 function ParserTestFailure(message, file) { | |
303 this.message = message; | |
304 this.file = file; | |
305 } | |
306 | |
307 ParserTestFailure.prototype.toString = function() { | |
308 return 'Error: ' + this.message + ' for "' + this.file.contents + '"'; | |
309 } | |
310 | |
311 function checkData(data, expectedData, file) { | |
312 if (data.byteLength != expectedData.byteLength) { | |
313 var s = "message length (" + data.byteLength + ") doesn't match " + | |
314 "expected length: " + expectedData.byteLength; | |
315 throw new ParserTestFailure(s, file); | |
316 } | |
317 | |
318 for (var i = 0; i < data.byteLength; i++) { | |
319 if (data.getUint8(i) != expectedData.getUint8(i)) { | |
320 var s = 'message data mismatch at byte offset ' + i; | |
321 throw new ParserTestFailure(s, file); | |
322 } | |
323 } | |
324 } | |
325 | |
326 function testFloatItems() { | |
327 var file = new File('[f]+.3e9 [d]-10.03'); | |
328 var msg = parseTestMessage(file); | |
329 var expectedData = new Buffer(12); | |
330 expectedData.setFloat32(0, +.3e9); | |
331 expectedData.setFloat64(4, -10.03); | |
332 checkData(msg.buffer, expectedData, file); | |
333 } | |
334 | |
335 function testUnsignedIntegerItems() { | |
336 var file = new File('[u1]0x10// hello world !! \n\r \t [u2]65535 \n' + | |
337 '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff'); | |
338 var msg = parseTestMessage(file); | |
339 var expectedData = new Buffer(17); | |
340 expectedData.setUint8(0, 0x10); | |
341 expectedData.setUint16(1, 65535); | |
342 expectedData.setUint32(3, 65536); | |
343 expectedData.setUint64(7, 0xFFFFFFFFFFFFF); | |
344 expectedData.setUint8(15, 0); | |
345 expectedData.setUint8(16, 0xff); | |
346 checkData(msg.buffer, expectedData, file); | |
347 } | |
348 | |
349 function testSignedIntegerItems() { | |
350 var file = new File('[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40'); | |
351 var msg = parseTestMessage(file); | |
352 var expectedData = new Buffer(15); | |
353 expectedData.setInt64(0, -0x800); | |
354 expectedData.setInt8(8, -128); | |
355 expectedData.setInt16(9, 0); | |
356 expectedData.setInt32(11, -40); | |
357 checkData(msg.buffer, expectedData, file); | |
358 } | |
359 | |
360 function testByteItems() { | |
361 var file = | |
362 new File('[b]00001011 [b]10000000 // hello world\n [b]00000000'); | |
363 var msg = parseTestMessage(file); | |
364 var expectedData = new Buffer(3); | |
365 expectedData.setUint8(0, 11); | |
366 expectedData.setUint8(1, 128); | |
367 expectedData.setUint8(2, 0); | |
368 checkData(msg.buffer, expectedData, file); | |
369 } | |
370 | |
371 function testAnchors() { | |
372 var file = new File('[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar'); | |
373 var msg = parseTestMessage(file); | |
374 var expectedData = new Buffer(14); | |
375 expectedData.setUint32(0, 14); | |
376 expectedData.setUint8(4, 0); | |
377 expectedData.setUint64(5, 9); | |
378 expectedData.setUint8(13, 0); | |
379 checkData(msg.buffer, expectedData, file); | |
380 } | |
381 | |
382 function testHandles() { | |
383 var file = new File('// This message has handles! \n[handles]50 [u8]2'); | |
384 var msg = parseTestMessage(file); | |
385 var expectedData = new Buffer(8); | |
386 expectedData.setUint64(0, 2); | |
387 | |
388 if (msg.handleCount != 50) { | |
389 var s = 'wrong handle count (' + msg.handleCount + ')'; | |
390 throw new ParserTestFailure(s, file); | |
391 } | |
392 checkData(msg.buffer, expectedData, file); | |
393 } | |
394 | |
395 function testEmptyInput() { | |
396 var file = new File(''); | |
397 var msg = parseTestMessage(file); | |
398 if (msg.buffer.byteLength != 0) | |
399 throw new ParserTestFailure('expected empty message', file); | |
400 } | |
401 | |
402 function testBlankInput() { | |
403 var file = | |
404 new File(' \t // hello world \n\r \t// the answer is 42 '); | |
405 var msg = parseTestMessage(file); | |
406 if (msg.buffer.byteLength != 0) | |
407 throw new ParserTestFailure('expected empty message', file); | |
408 } | |
409 | |
410 function testInvalidInput() { | |
411 function parserShouldFail(fileContents) { | |
412 var file = new File(fileContents); | |
413 try { | |
414 parseTestMessage(file); | |
415 } catch (e) { | |
416 if (e instanceof InputError) | |
417 return; | |
418 throw new ParserTestFailure( | |
419 'unexpected exception ' + e.toString(), file); | |
420 } | |
421 throw new ParserTestFailure("didn't detect invalid input", file); | |
422 } | |
423 | |
424 ['/ hello world', | |
425 '[u1]x', | |
426 '[u2]-1000', | |
427 '[u1]0x100', | |
428 '[s2]-0x8001', | |
429 '[b]1', | |
430 '[b]1111111k', | |
431 '[dist4]unmatched', | |
432 '[anchr]hello [dist8]hello', | |
433 '[dist4]a [dist4]a [anchr]a', | |
434 // '[dist4]a [anchr]a [dist4]a [anchr]a', | |
435 '0 [handles]50' | |
436 ].forEach(parserShouldFail); | |
437 } | |
438 | |
439 try { | |
440 testFloatItems(); | |
441 testUnsignedIntegerItems(); | |
442 testSignedIntegerItems(); | |
443 testByteItems(); | |
444 testInvalidInput(); | |
445 testEmptyInput(); | |
446 testBlankInput(); | |
447 testHandles(); | |
448 testAnchors(); | |
449 } catch (e) { | |
450 return e; | |
451 } | |
452 return null; | |
453 } | |
454 | |
455 var exports = {}; | |
456 exports.checkMessageFileParser = checkMessageFileParser; | |
457 return exports; | |
458 }); | |
OLD | NEW |