First pieces of Kernel text serialization
Combinator-based serialization of Kernel basic literals.
Change-Id: I6d08c80f47f233b178aa57ee6fc887ac91ed9e9c
Reviewed-on: https://dart-review.googlesource.com/c/87320
Commit-Queue: Kevin Millikin <kmillikin@google.com>
Reviewed-by: Daniel Hillerström <hillerstrom@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
diff --git a/pkg/kernel/lib/text/text_serializer.dart b/pkg/kernel/lib/text/text_serializer.dart
new file mode 100644
index 0000000..6b0d487
--- /dev/null
+++ b/pkg/kernel/lib/text/text_serializer.dart
@@ -0,0 +1,280 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library kernel.text_serializer;
+
+import '../ast.dart';
+import '../visitor.dart' show ExpressionVisitor;
+
+// ==== Serialize/deserialize combinators ====
+abstract class TextSerializer<T> {
+ const TextSerializer();
+
+ T readFrom(Iterator<Object> stream);
+ void writeTo(StringBuffer buffer, T object);
+
+ /// True if this serializer/deserializer writes/reads nothing. This is true
+ /// for the serializer [Nothing] and also some serializers derived from it.
+ bool get isEmpty => false;
+}
+
+class Nothing extends TextSerializer<void> {
+ const Nothing();
+
+ void readFrom(Iterator<Object> stream) {}
+
+ void writeTo(StringBuffer buffer, void ignored) {}
+
+ bool get isEmpty => true;
+}
+
+// == Serializer/deserializers for basic Dart types
+class DartInt extends TextSerializer<int> {
+ const DartInt();
+
+ int readFrom(Iterator<Object> stream) {
+ if (stream.current is! String) {
+ throw StateError("expected an atom, found a list");
+ }
+ int result = int.parse(stream.current);
+ stream.moveNext();
+ return result;
+ }
+
+ void writeTo(StringBuffer buffer, int object) {
+ buffer.write(object);
+ }
+}
+
+class DartDouble extends TextSerializer<double> {
+ const DartDouble();
+
+ double readFrom(Iterator<Object> stream) {
+ if (stream.current is! String) {
+ throw StateError("expected an atom, found a list");
+ }
+ double result = double.parse(stream.current);
+ stream.moveNext();
+ return result;
+ }
+
+ void writeTo(StringBuffer buffer, double object) {
+ buffer.write(object);
+ }
+}
+
+class DartBool extends TextSerializer<bool> {
+ const DartBool();
+
+ bool readFrom(Iterator<Object> stream) {
+ if (stream.current is! String) {
+ throw StateError("expected an atom, found a list");
+ }
+ bool result;
+ if (stream.current == "true") {
+ result = true;
+ } else if (stream.current == "false") {
+ result = false;
+ } else {
+ throw StateError("expected 'true' or 'false', found '${stream.current}'");
+ }
+ stream.moveNext();
+ return result;
+ }
+
+ void writeTo(StringBuffer buffer, bool object) {
+ buffer.write(object ? 'true' : 'false');
+ }
+}
+
+// == Serializers for tagged (disjoint) unions.
+//
+// They require a function mapping serializables to a tag string. This is
+// implemented by Tagger visitors.
+class ExpressionTagger extends ExpressionVisitor<String> {
+ const ExpressionTagger();
+
+ String visitIntLiteral(IntLiteral _) => "int";
+ String visitDoubleLiteral(DoubleLiteral _) => "double";
+ String visitBoolLiteral(BoolLiteral _) => "bool";
+ String visitNullLiteral(NullLiteral _) => "null";
+}
+
+// A tagged union of serializer/deserializers.
+class Case<T extends Expression> extends TextSerializer<T> {
+ final List<String> tags;
+ final List<TextSerializer<T>> serializers;
+
+ const Case(this.tags, this.serializers);
+
+ T readFrom(Iterator<Object> stream) {
+ if (stream.current is! Iterator) {
+ throw StateError("expected list, found atom");
+ }
+ Iterator nested = stream.current;
+ nested.moveNext();
+ if (nested.current is! String) {
+ throw StateError("expected atom, found list");
+ }
+ String tag = nested.current;
+ for (int i = 0; i < tags.length; ++i) {
+ if (tags[i] == tag) {
+ nested.moveNext();
+ T result = serializers[i].readFrom(nested);
+ if (stream.moveNext()) {
+ throw StateError("extra cruft in tagged '${tag}'");
+ }
+ return result;
+ }
+ }
+ throw StateError("unrecognized tag '${tag}'");
+ }
+
+ void writeTo(StringBuffer buffer, T object) {
+ String tag = object.accept(const ExpressionTagger());
+ for (int i = 0; i < tags.length; ++i) {
+ if (tags[i] == tag) {
+ buffer.write("(${tag}");
+ if (!serializers[i].isEmpty) {
+ buffer.write(" ");
+ }
+ serializers[i].writeTo(buffer, object);
+ buffer.write(")");
+ return;
+ }
+ }
+ throw StateError("unrecognized tag '${tag}");
+ }
+}
+
+// A serializer/deserializer that unwraps/wraps nodes before serialization and
+// after deserialization.
+class Wrapped<S, K> extends TextSerializer<K> {
+ final S Function(K) unwrap;
+ final K Function(S) wrap;
+ final TextSerializer<S> contents;
+
+ const Wrapped(this.unwrap, this.wrap, this.contents);
+
+ K readFrom(Iterator<Object> stream) {
+ return wrap(contents.readFrom(stream));
+ }
+
+ void writeTo(StringBuffer buffer, K object) {
+ contents.writeTo(buffer, unwrap(object));
+ }
+
+ bool get isEmpty => contents.isEmpty;
+}
+
+// S-expressions
+//
+// An S-expression is an atom or an S-list, an atom is a string that does not
+// contain the delimiters '(', ')', or ' ', and an S-list is a space delimited
+// sequence of S-expressions enclosed in parentheses:
+//
+// <S-expression> ::= <Atom>
+// | <S-list>
+// <S-list> ::= '(' ')'
+// | '(' <S-expression> {' ' <S-expression>}* ')'
+//
+// We use an iterator to read S-expressions. The iterator produces a stream
+// of atoms (strings) and nested iterators (S-lists).
+class TextIterator implements Iterator<Object /* String | TextIterator */ > {
+ static int space = ' '.codeUnitAt(0);
+ static int lparen = '('.codeUnitAt(0);
+ static int rparen = ')'.codeUnitAt(0);
+
+ final String input;
+ int index;
+
+ TextIterator(this.input, this.index);
+
+ // Consume spaces.
+ void skipWhitespace() {
+ while (index < input.length && input.codeUnitAt(index) == space) {
+ ++index;
+ }
+ }
+
+ // Consume the rest of a nested S-expression and the closing delimiter.
+ void skipToEndOfNested() {
+ if (current is TextIterator) {
+ TextIterator it = current;
+ while (it.moveNext());
+ index = it.index + 1;
+ }
+ }
+
+ void skipToEndOfAtom() {
+ do {
+ if (index >= input.length) return;
+ int codeUnit = input.codeUnitAt(index);
+ if (codeUnit == space || codeUnit == rparen) return;
+ ++index;
+ } while (true);
+ }
+
+ @override
+ Object current = null;
+
+ @override
+ bool moveNext() {
+ skipToEndOfNested();
+ skipWhitespace();
+ if (index >= input.length || input.codeUnitAt(index) == rparen) {
+ current = null;
+ return false;
+ }
+ if (input.codeUnitAt(index) == lparen) {
+ current = new TextIterator(input, index + 1);
+ return true;
+ }
+ int start = index;
+ skipToEndOfAtom();
+ current = input.substring(start, index);
+ return true;
+ }
+}
+
+// ==== Serializers for BasicLiterals
+const TextSerializer<BasicLiteral> basicLiteralSerializer = Case([
+ "int",
+ "double",
+ "bool",
+ "null"
+], [
+ intLiteralSerializer,
+ doubleLiteralSerializer,
+ boolLiteralSerializer,
+ nullLiteralSerializer
+]);
+
+const TextSerializer<IntLiteral> intLiteralSerializer =
+ Wrapped(unwrapIntLiteral, wrapIntLiteral, DartInt());
+
+int unwrapIntLiteral(IntLiteral literal) => literal.value;
+
+IntLiteral wrapIntLiteral(int value) => new IntLiteral(value);
+
+const TextSerializer<DoubleLiteral> doubleLiteralSerializer =
+ Wrapped(unwrapDoubleLiteral, wrapDoubleLiteral, DartDouble());
+
+double unwrapDoubleLiteral(DoubleLiteral literal) => literal.value;
+
+DoubleLiteral wrapDoubleLiteral(double value) => new DoubleLiteral(value);
+
+const TextSerializer<BoolLiteral> boolLiteralSerializer =
+ Wrapped(unwrapBoolLiteral, wrapBoolLiteral, DartBool());
+
+bool unwrapBoolLiteral(BoolLiteral literal) => literal.value;
+
+BoolLiteral wrapBoolLiteral(bool value) => new BoolLiteral(value);
+
+const TextSerializer<NullLiteral> nullLiteralSerializer =
+ Wrapped(unwrapNullLiteral, wrapNullLiteral, Nothing());
+
+void unwrapNullLiteral(NullLiteral literal) {}
+
+NullLiteral wrapNullLiteral(void ignored) => new NullLiteral();
diff --git a/pkg/kernel/test/text_serializer_test.dart b/pkg/kernel/test/text_serializer_test.dart
new file mode 100644
index 0000000..dbb5ece
--- /dev/null
+++ b/pkg/kernel/test/text_serializer_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+library kernel.text_serializer_test;
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/text/text_serializer.dart';
+
+void main() {
+ test();
+}
+
+// Wrappers for testing.
+BasicLiteral readBasicLiteral(String input) {
+ TextIterator stream = new TextIterator(input, 0);
+ stream.moveNext();
+ BasicLiteral result = basicLiteralSerializer.readFrom(stream);
+ if (stream.moveNext()) {
+ throw StateError("extra cruft in basic literal");
+ }
+ return result;
+}
+
+String writeBasicLiteral(BasicLiteral literal) {
+ StringBuffer buffer = new StringBuffer();
+ basicLiteralSerializer.writeTo(buffer, literal);
+ return buffer.toString();
+}
+
+void test() {
+ List<String> failures = [];
+ List<String> tests = [
+ "(int 42)",
+ "(int 0)",
+ "(int -1001)",
+ "(double 3.14159)",
+ "(bool true)",
+ "(bool false)",
+ "(null)"
+ ];
+ for (var test in tests) {
+ var literal = readBasicLiteral(test);
+ var output = writeBasicLiteral(literal);
+ if (output != test) {
+ failures.add('* input "${test}" gave output "${output}"');
+ }
+ }
+ if (failures.isNotEmpty) {
+ print('Round trip failures:');
+ failures.forEach(print);
+ throw StateError('Round trip failures');
+ }
+}