// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library validation_input_parser; import 'dart:typed_data'; class ValidationParseResult { final Iterable<_Entry> _entries; final int numHandles; final ByteData data; ValidationParseResult(this._entries, this.data, this.numHandles); String toString() => _entries.map((e) => '$e').join('\n'); } ValidationParseResult parse(String input) => new _ValidationTestParser(input).parse(); class ValidationParseError { final String _message; ValidationParseError(this._message); String toString() => _message; } abstract class _Entry { final int size; _Entry(this.size); void write(ByteData buffer, int offset, Map pointers); } class _UnsignedEntry implements _Entry { final int size; final int value; _UnsignedEntry(this.size, this.value) { if ((value >= (1 << (size * 8))) || (value < 0)) { throw new ValidationParseError('$value does not fit in a u$size'); } } void write(ByteData buffer, int offset, Map pointers) { switch (size) { case 1: buffer.setUint8(offset, value); break; case 2: buffer.setUint16(offset, value, Endianness.LITTLE_ENDIAN); break; case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break; case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break; default: throw new ValidationParseError('Unexpected size: $size'); } } String toString() => "[u$size]$value"; bool operator==(_UnsignedEntry other) => (size == other.size) && (value == other.value); } class _SignedEntry implements _Entry { final int size; final int value; _SignedEntry(this.size, this.value) { if ((value >= (1 << ((size * 8) - 1))) || (value < -(1 << ((size * 8) - 1)))) { throw new ValidationParseError('$value does not fit in a s$size'); } } void write(ByteData buffer, int offset, Map pointers) { switch (size) { case 1: buffer.setInt8(offset, value); break; case 2: buffer.setInt16(offset, value, Endianness.LITTLE_ENDIAN); break; case 4: buffer.setInt32(offset, value, Endianness.LITTLE_ENDIAN); break; case 8: buffer.setInt64(offset, value, Endianness.LITTLE_ENDIAN); break; default: throw new ValidationParseError('Unexpected size: $size'); } } String toString() => "[s$size]$value"; bool operator==(_SignedEntry other) => (size == other.size) && (value == other.value); } class _FloatEntry implements _Entry { final int size; final double value; _FloatEntry(this.size, this.value); void write(ByteData buffer, int offset, Map pointers) { switch (size) { case 4: buffer.setFloat32(offset, value, Endianness.LITTLE_ENDIAN); break; case 8: buffer.setFloat64(offset, value, Endianness.LITTLE_ENDIAN); break; default: throw new ValidationParseError('Unexpected size: $size'); } } String toString() => "[f$size]$value"; bool operator==(_FloatEntry other) => (size == other.size) && (value == other.value); } class _DistEntry implements _Entry { final int size; final String id; int offset; bool matched = false; _DistEntry(this.size, this.id); void write(ByteData buffer, int off, Map pointers) { offset = off; if (pointers[id] != null) { throw new ValidationParseError( 'Pointer of same name already exists: $id'); } pointers[id] = this; } String toString() => "[dist$size]$id matched = $matched"; bool operator==(_DistEntry other) => (size == other.size) && (id == other.id); } class _AnchrEntry implements _Entry { final int size = 0; final String id; _AnchrEntry(this.id); void write(ByteData buffer, int off, Map pointers) { _DistEntry dist = pointers[id]; if (dist == null) { throw new ValidationParseError('Did not find "$id" in pointers map.'); } int value = off - dist.offset; if (value < 0) { throw new ValidationParseError('Found a backwards pointer: $id'); } int offset = dist.offset; switch (dist.size) { case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break; case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break; default: throw new ValidationParseError('Unexpected size: $size'); } dist.matched = true; } String toString() => "[anchr]$id"; bool operator==(_AnchrEntry other) => (id == other.id); } class _HandlesEntry implements _Entry { final int size = 0; final int value; _HandlesEntry(this.value); void write(ByteData buffer, int offset, Map pointers) {} String toString() => "[handles]$value"; bool operator==(_HandlesEntry other) => (value == other.value); } class _CommentEntry implements _Entry { final int size = 0; final String value; _CommentEntry(this.value); void write(ByteData buffer, int offset, Map pointers) {} String toString() => "// $value"; bool operator==(_CommentEntry other) => (value == other.value); } class _ValidationTestParser { static final RegExp newline = new RegExp(r'[\r\n]+'); static final RegExp whitespace = new RegExp(r'[ \t\n\r]+'); static final RegExp nakedUintRegExp = new RegExp(r'^0$|^[1-9][0-9]*$|^0[xX][0-9a-fA-F]+$'); static final RegExp unsignedRegExp = new RegExp(r'^\[u([1248])\](0$|[1-9][0-9]*$|0[xX][0-9a-fA-F]+$)'); static final RegExp signedRegExp = new RegExp( r'^\[s([1248])\]([-+]?0$|[-+]?[1-9][0-9]*$|[-+]?0[xX][0-9a-fA-F]+$)'); static final RegExp binaryRegExp = new RegExp(r'^\[(b)\]([01]{8}$)'); static final RegExp floatRegExp = new RegExp(r'^\[([fd])\]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$)'); static final RegExp distRegExp = new RegExp(r'^\[dist([48])\]([0-9a-zA-Z_]+$)'); static final RegExp anchrRegExp = new RegExp(r'^\[(anchr)\]([0-9a-zA-Z_]+$)'); static final RegExp handlesRegExp = new RegExp(r'^\[(handles)\](0$|([1-9][0-9]*$)|(0[xX][0-9a-fA-F]+$))'); static final RegExp commentRegExp = new RegExp(r'//(.*)'); String _input; Map _pointers; _ValidationTestParser(this._input) : _pointers = {}; String _stripComment(String line) => line.replaceFirst(commentRegExp, ""); ValidationParseResult parse() { var entries = _input.split(newline) .map(_stripComment) .expand((s) => s.split(whitespace)) .where((s) => s != "") .map(_parseLine); int size = _calculateSize(entries); var data = (size > 0) ? new ByteData(size) : null; int numHandles = 0; int offset = 0; bool first = true; for (var entry in entries) { entry.write(data, offset, _pointers); offset += entry.size; if (entry is _HandlesEntry) { if (!first) { throw new ValidationParseError('Handles entry was not first'); } numHandles = entry.value; } first = false; } for (var entry in entries) { if (entry is _DistEntry) { if (!_pointers[entry.id].matched) { throw new ValidationParseError('Unmatched dist: $entry'); } } } return new ValidationParseResult(entries, data, numHandles); } _Entry _parseLine(String line) { if (unsignedRegExp.hasMatch(line)) { var match = unsignedRegExp.firstMatch(line); return new _UnsignedEntry( int.parse(match.group(1)), int.parse(match.group(2))); } else if (signedRegExp.hasMatch(line)) { var match = signedRegExp.firstMatch(line); return new _SignedEntry( int.parse(match.group(1)), int.parse(match.group(2))); } else if (binaryRegExp.hasMatch(line)) { var match = binaryRegExp.firstMatch(line); return new _UnsignedEntry(1, int.parse(match.group(2), radix: 2)); } else if (floatRegExp.hasMatch(line)) { var match = floatRegExp.firstMatch(line); int size = match.group(1) == 'f' ? 4 : 8; return new _FloatEntry(size, double.parse(match.group(2))); } else if (distRegExp.hasMatch(line)) { var match = distRegExp.firstMatch(line); return new _DistEntry(int.parse(match.group(1)), match.group(2)); } else if (anchrRegExp.hasMatch(line)) { var match = anchrRegExp.firstMatch(line); return new _AnchrEntry(match.group(2)); } else if (handlesRegExp.hasMatch(line)) { var match = handlesRegExp.firstMatch(line); return new _HandlesEntry(int.parse(match.group(2))); } else if (nakedUintRegExp.hasMatch(line)) { var match = nakedUintRegExp.firstMatch(line); return new _UnsignedEntry(1, int.parse(match.group(0))); } else if (commentRegExp.hasMatch(line)) { var match = commentRegExp.firstMatch(line); return new _CommentEntry(match.group(1)); } else if (line == "") { return new _CommentEntry(""); } else { throw new ValidationParseError('Unkown entry: "$line" in \n$_input'); } } int _calculateSize(Iterable<_Entry> entries) => entries.fold(0, (value, entry) => value + entry.size); } bool _listEquals(Iterable i1, Iterable i2) { var l1 = i1.toList(); var l2 = i2.toList(); if (l1.length != l2.length) return false; for (int i = 0; i < l1.length; i++) { if (l1[i] != l2[i]) return false; } return true; } parserTests() { { var input = " \t // hello world \n\r \t// the answer is 42 "; var result = parse(input); assert(result.data == null); assert(result.numHandles == 0); } { var input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; var result = parse(input); // Check the parse results. var expected = [new _UnsignedEntry(1, 0x10), new _UnsignedEntry(2, 65535), new _UnsignedEntry(4, 65536), new _UnsignedEntry(8, 0xFFFFFFFFFFFFFFFF), new _UnsignedEntry(1, 0), new _UnsignedEntry(1, 0xff)]; assert(_listEquals(result._entries, expected)); //Check the bits. var buffer = new ByteData(17); var offset = 0; buffer.setUint8(offset, 0x10); offset++; buffer.setUint16(offset, 65535, Endianness.LITTLE_ENDIAN); offset += 2; buffer.setUint32(offset, 65536, Endianness.LITTLE_ENDIAN); offset += 4; buffer.setUint64(offset, 0xFFFFFFFFFFFFFFFF, Endianness.LITTLE_ENDIAN); offset += 8; buffer.setUint8(offset, 0); offset++; buffer.setUint8(offset, 0xff); offset++; assert(_listEquals(buffer.buffer.asUint8List(), result.data.buffer.asUint8List())); } { var input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; var result = parse(input); // Check the parse results. var expected = [new _SignedEntry(8, -0x800), new _SignedEntry(1, -128), new _SignedEntry(2, 0), new _SignedEntry(4, -40)]; assert(_listEquals(result._entries, expected)); // Check the bits. var buffer = new ByteData(15); var offset = 0; buffer.setInt64(offset, -0x800, Endianness.LITTLE_ENDIAN); offset += 8; buffer.setInt8(offset, -128); offset += 1; buffer.setInt16(offset, 0, Endianness.LITTLE_ENDIAN); offset += 2; buffer.setInt32(offset, -40, Endianness.LITTLE_ENDIAN); offset += 4; assert(_listEquals(buffer.buffer.asUint8List(), result.data.buffer.asUint8List())); } { var input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; var result = parse(input); // Check the parse results; var expected = [new _UnsignedEntry(1, 11), new _UnsignedEntry(1, 128), new _UnsignedEntry(1, 0)]; assert(_listEquals(result._entries, expected)); // Check the bits. var buffer = new ByteData(3); var offset = 0; buffer.setUint8(offset, 11); offset += 1; buffer.setUint8(offset, 128); offset += 1; buffer.setUint8(offset, 0); offset += 1; assert(_listEquals(buffer.buffer.asUint8List(), result.data.buffer.asUint8List())); } { var input = "[f]+.3e9 [d]-10.03"; var result = parse(input); // Check the parse results. var expected = [new _FloatEntry(4, 0.3e9), new _FloatEntry(8,-10.03)]; assert(_listEquals(result._entries, expected)); // Check the bits. var buffer = new ByteData(12); var offset = 0; buffer.setFloat32(offset, 0.3e9, Endianness.LITTLE_ENDIAN); offset += 4; buffer.setFloat64(offset, -10.03, Endianness.LITTLE_ENDIAN); offset += 8; assert(_listEquals(buffer.buffer.asUint8List(), result.data.buffer.asUint8List())); } { var input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; var result = parse(input); // Check the parse results. var expected = [new _DistEntry(4, "foo"), new _UnsignedEntry(1, 0), new _DistEntry(8, "bar"), new _UnsignedEntry(1, 0), new _AnchrEntry("foo"), new _AnchrEntry("bar")]; assert(_listEquals(result._entries, expected)); // Check the bits. var buffer = new ByteData(14); var offset = 0; buffer.setUint32(offset, 14, Endianness.LITTLE_ENDIAN); offset += 4; buffer.setUint8(offset, 0); offset += 1; buffer.setUint64(offset, 9, Endianness.LITTLE_ENDIAN); offset += 8; buffer.setUint8(offset, 0); offset += 1; assert(_listEquals(buffer.buffer.asUint8List(), result.data.buffer.asUint8List())); } { var input = "// This message has handles! \n[handles]50 [u8]2"; var result = parse(input); var expected = [new _HandlesEntry(50), new _UnsignedEntry(8, 2)]; assert(_listEquals(result._entries, expected)); } { var errorInputs = ["/ hello world", "[u1]x", "[u2]-1000", "[u1]0x100", "[s2]-0x8001", "[b]1", "[b]1111111k", "[dist4]unmatched", "[anchr]hello [dist8]hello", "[dist4]a [dist4]a [anchr]a", "[dist4]a [anchr]a [dist4]a [anchr]a", "0 [handles]50"]; for (var input in errorInputs) { try { var result = parse(input); assert(false); } on ValidationParseError catch(e) { // Pass. } } } }