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

Unified Diff: pkg/analysis_server/tool/spec/codegen_dart_protocol.dart

Issue 479043002: Code generate Dart classes representing the analysis server API. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 4 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/tool/spec/codegen_dart_protocol.dart
diff --git a/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart b/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ba54b62e679680f9de3a222f4a0ed91ea4970486
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart
@@ -0,0 +1,821 @@
+// Copyright (c) 2014, 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 codegen.protocol;
+
+import 'dart:convert';
+
+import 'api.dart';
+import 'codegen_tools.dart';
+import 'from_html.dart';
+import 'implied_types.dart';
+import 'to_html.dart';
+
+import 'package:html5lib/dom.dart' as dom;
+
+/**
+ * Container for code that can be used to translate a data type from JSON.
+ */
+abstract class FromJsonCode {
+ /**
+ * True if the data type is already in JSON form, so the translation is the
+ * identity function.
+ */
+ bool get isIdentity;
+
+ /**
+ * Get the translation code in the form of a closure.
+ */
+ String get asClosure;
+
+ /**
+ * Get the translation code in the form of a code snippet, where [jsonPath]
+ * is the variable holding the JSON path, and [json] is the variable holding
+ * the raw JSON.
+ */
+ String asSnippet(String jsonPath, String json);
+}
+
+/**
+ * Representation of FromJsonCode for a function defined elsewhere.
+ */
+class FromJsonFunction extends FromJsonCode {
+ final String asClosure;
+
+ FromJsonFunction(this.asClosure);
+
+ @override
+ bool get isIdentity => false;
+
+ @override
+ String asSnippet(String jsonPath, String json) =>
+ '$asClosure($jsonPath, $json)';
+}
+
+typedef String FromJsonSnippetCallback(String jsonPath, String json);
+
+/**
+ * Representation of FromJsonCode for a snippet of inline code.
+ */
+class FromJsonSnippet extends FromJsonCode {
+ /**
+ * Callback that can be used to generate the code snippet, once the names
+ * of the [jsonPath] and [json] variables are known.
+ */
+ final FromJsonSnippetCallback callback;
+
+ FromJsonSnippet(this.callback);
+
+ @override
+ bool get isIdentity => false;
+
+ @override
+ String get asClosure =>
+ '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}';
+
+ @override
+ String asSnippet(String jsonPath, String json) => callback(jsonPath, json);
+}
+
+/**
+ * Representation of FromJsonCode for the identity transformation.
+ */
+class FromJsonIdentity extends FromJsonSnippet {
+ FromJsonIdentity() : super((String jsonPath, String json) => json);
+
+ @override
+ bool get isIdentity => true;
+}
+
+/**
+ * Container for code that can be used to translate a data type to JSON.
+ */
+abstract class ToJsonCode {
+ /**
+ * True if the data type is already in JSON form, so the translation is the
+ * identity function.
+ */
+ bool get isIdentity;
+
+ /**
+ * Get the translation code in the form of a closure.
+ */
+ String get asClosure;
+
+ /**
+ * Get the translation code in the form of a code snippet, where [value]
+ * is the variable holding the object to be translated.
+ */
+ String asSnippet(String value);
+}
+
+/**
+ * Representation of ToJsonCode for a function defined elsewhere.
+ */
+class ToJsonFunction extends ToJsonCode {
+ final String asClosure;
+
+ ToJsonFunction(this.asClosure);
+
+ @override
+ bool get isIdentity => false;
+
+ @override
+ String asSnippet(String value) => '$asClosure($value)';
+}
+
+typedef String ToJsonSnippetCallback(String value);
+
+/**
+ * Representation of ToJsonCode for a snippet of inline code.
+ */
+class ToJsonSnippet extends ToJsonCode {
+ /**
+ * Callback that can be used to generate the code snippet, once the name
+ * of the [value] variable is known.
+ */
+ final ToJsonSnippetCallback callback;
+
+ /**
+ * Dart type of the [value] variable.
+ */
+ final String type;
+
+ ToJsonSnippet(this.type, this.callback);
+
+ @override
+ bool get isIdentity => false;
+
+ @override
+ String get asClosure => '($type value) => ${callback('value')}';
+
+ @override
+ String asSnippet(String value) => callback(value);
+}
+
+/**
+ * Representation of FromJsonCode for the identity transformation.
+ */
+class ToJsonIdentity extends ToJsonSnippet {
+ ToJsonIdentity(String type) : super(type, (String value) => value);
+
+ @override
+ bool get isIdentity => true;
+}
+
+/**
+ * Visitor which produces Dart code representing the API.
+ */
+class CodegenProtocolVisitor extends HierarchicalApiVisitor with CodeGenerator {
+ /**
+ * Type references in the spec that are named something else in Dart.
+ */
+ static const Map<String, String> _typeRenames = const {
+ 'object': 'Object',
+ };
+
+ /**
+ * Visitor used to produce doc comments.
+ */
+ final ToHtmlVisitor toHtmlVisitor;
+
+ /**
+ * Types implied by the API. This includes types explicitly named in the
+ * API as well as those implied by the definitions of requests, responses,
+ * notifications, etc.
+ */
+ final Map<String, ImpliedType> impliedTypes;
+
+ CodegenProtocolVisitor(Api api)
+ : super(api),
+ toHtmlVisitor = new ToHtmlVisitor(api),
+ impliedTypes = computeImpliedTypes(api);
+
+ @override
+ visitApi() {
+ outputHeader();
+ writeln();
+ writeln('part of protocol2;');
+ emitClasses();
+ writeln();
+ emitJsonDecoder();
+ emitConvenienceFunctions();
+ }
+
+ /**
+ * Translate each type implied by the API to a class.
+ */
+ void emitClasses() {
+ for (ImpliedType impliedType in impliedTypes.values) {
+ TypeDecl type = impliedType.type;
+ if (type != null) {
+ String dartTypeName = capitalize(impliedType.camelName);
+ if (type is TypeObject) {
+ writeln();
+ emitObjectClass(dartTypeName, type, impliedType);
+ } else if (type is TypeEnum) {
+ writeln();
+ emitEnumClass(dartTypeName, type, impliedType);
+ }
+ }
+ }
+ }
+
+ /**
+ * Emit the class to encapsulate an object type.
+ */
+ void emitObjectClass(String className, TypeObject type, ImpliedType
+ impliedType) {
+ docComment(toHtmlVisitor.collectHtml(() {
+ toHtmlVisitor.p(() {
+ toHtmlVisitor.write(impliedType.humanReadableName);
+ });
+ if (impliedType.type != null) {
+ toHtmlVisitor.showType(null, impliedType.type);
+ }
+ }));
+ writeln('class $className {');
+ indent(() {
+ for (TypeObjectField field in type.fields) {
+ if (field.value != null) {
+ continue;
+ }
+ docComment(toHtmlVisitor.collectHtml(() {
+ toHtmlVisitor.translateHtml(field.html);
+ }));
+ writeln('final ${dartType(field.type)} ${field.name};');
+ writeln();
+ }
+ emitObjectConstructor(type, className);
+ writeln();
+ emitToJsonMember(type);
+ writeln();
+ writeln('String toString() => JSON.encode(toJson());');
+ writeln();
+ emitObjectEqualsMember(type, className);
+ writeln();
+ emitObjectHashCode(type);
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the constructor for an object class.
+ */
+ void emitObjectConstructor(TypeObject type, String className) {
+ List<String> args = <String>[];
+ List<String> optionalArgs = <String>[];
+ for (TypeObjectField field in type.fields) {
+ if (field.value != null) {
+ continue;
+ }
+ String arg = 'this.${field.name}';
+ if (field.optional) {
+ optionalArgs.add(arg);
+ } else {
+ args.add(arg);
+ }
+ }
+ if (optionalArgs.isNotEmpty) {
+ args.add('{${optionalArgs.join(', ')}}');
+ }
+ writeln('$className(${args.join(', ')});');
+ }
+
+ /**
+ * Emit the toJson() code for an object class.
+ */
+ void emitToJsonMember(TypeObject type) {
+ writeln('Map<String, dynamic> toJson() {');
+ indent(() {
+ writeln('Map<String, dynamic> result = {};');
+ for (TypeObjectField field in type.fields) {
+ String fieldNameString = literalString(field.name);
+ if (field.value != null) {
+ writeln('result[$fieldNameString] = ${literalString(field.value)};');
+ continue;
+ }
+ String fieldToJson = toJsonCode(field.type).asSnippet(field.name);
+ String populateField = 'result[$fieldNameString] = $fieldToJson;';
+ if (field.optional) {
+ writeln('if (${field.name} != null) {');
+ indent(() {
+ writeln(populateField);
+ });
+ writeln('}');
+ } else {
+ writeln(populateField);
+ }
+ }
+ writeln('return result;');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the operator== code for an object class.
+ */
+ void emitObjectEqualsMember(TypeObject type, String className) {
+ writeln('bool operator==(other) {');
+ indent(() {
+ writeln('if (other is $className) {');
+ indent(() {
+ var comparisons = <String>[];
+ for (TypeObjectField field in type.fields) {
+ if (field.value != null) {
+ continue;
+ }
+ comparisons.add('${field.name} == other.${field.name}');
+ }
+ if (comparisons.isEmpty) {
+ writeln('return true;');
+ } else {
+ String concatenated = comparisons.join(' &&\n ');
+ writeln('return $concatenated;');
+ }
+ });
+ writeln('}');
+ writeln('return false;');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the hashCode getter for an object class.
+ */
+ void emitObjectHashCode(TypeObject type) {
+ writeln('int get hashCode {');
+ indent(() {
+ writeln('int hash = 0;');
+ for (TypeObjectField field in type.fields) {
+ String valueToCombine;
+ if (field.value != null) {
+ valueToCombine = field.value.hashCode.toString();
+ } else {
+ valueToCombine = '${field.name}.hashCode';
+ }
+ writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);');
+ }
+ writeln('return _JenkinsSmiHash.finish(hash);');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit a class to encapsulate an enum.
+ */
+ void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) {
+ docComment(toHtmlVisitor.collectHtml(() {
+ toHtmlVisitor.p(() {
+ toHtmlVisitor.write(impliedType.humanReadableName);
+ });
+ if (impliedType.type != null) {
+ toHtmlVisitor.showType(null, impliedType.type);
+ }
+ }));
+ writeln('class $className {');
+ indent(() {
+ for (TypeEnumValue value in type.values) {
+ docComment(toHtmlVisitor.collectHtml(() {
+ toHtmlVisitor.translateHtml(value.html);
+ }));
+ String valueString = literalString(value.value);
+ writeln(
+ 'static const ${value.value} = const $className._($valueString);');
+ writeln();
+ }
+ writeln('final String name;');
+ writeln();
+ writeln('const $className._(this.name);');
+ writeln();
+ emitEnumClassConstructor(className, type);
+ writeln();
+ writeln('String toString() => "$className.\$name";');
+ writeln();
+ writeln('String toJson() => name;');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the constructor for an enum class.
+ */
+ void emitEnumClassConstructor(String className, TypeEnum type) {
+ writeln('factory $className(String name) {');
+ indent(() {
+ writeln('switch (name) {');
+ indent(() {
+ for (TypeEnumValue value in type.values) {
+ String valueString = literalString(value.value);
+ writeln('case $valueString:');
+ indent(() {
+ writeln('return ${value.value};');
+ });
+ }
+ });
+ writeln('}');
+ writeln(r"throw new Exception('Illegal enum value: $name');");
+ });
+ writeln('}');
+ }
+
+ /**
+ * Compute the code necessary to convert [type] to JSON.
+ */
+ ToJsonCode toJsonCode(TypeDecl type) {
+ TypeDecl resolvedType = resolveTypeReferenceChain(type);
+ if (resolvedType is TypeReference) {
+ return new ToJsonIdentity(dartType(type));
+ } else if (resolvedType is TypeList) {
+ ToJsonCode itemCode = toJsonCode(resolvedType.itemType);
+ if (itemCode.isIdentity) {
+ return new ToJsonIdentity(dartType(type));
+ } else {
+ return new ToJsonSnippet(dartType(type), (String value) =>
+ '$value.map(${itemCode.asClosure})');
+ }
+ } else if (resolvedType is TypeMap) {
+ ToJsonCode keyCode;
+ if (dartType(resolvedType.keyType) != 'String') {
+ keyCode = toJsonCode(resolvedType.keyType);
+ } else {
+ keyCode = new ToJsonIdentity(dartType(resolvedType.keyType));
+ }
+ ToJsonCode valueCode = toJsonCode(resolvedType.valueType);
+ if (keyCode.isIdentity && valueCode.isIdentity) {
+ return new ToJsonIdentity(dartType(resolvedType));
+ } else {
+ return new ToJsonSnippet(dartType(type), (String value) {
+ StringBuffer result = new StringBuffer();
+ result.write('_mapMap($value');
+ if (!keyCode.isIdentity) {
+ result.write(', keyCallback: ${keyCode.asClosure}');
+ }
+ if (!valueCode.isIdentity) {
+ result.write(', valueCallback: ${valueCode.asClosure}');
+ }
+ result.write(')');
+ return result.toString();
+ });
+ }
+ } else if (resolvedType is TypeUnion) {
+ for (TypeDecl choice in resolvedType.choices) {
+ if (resolveTypeReferenceChain(choice) is! TypeObject) {
+ throw new Exception('Union types must be unions of objects');
+ }
+ }
+ return new ToJsonSnippet(dartType(type), (String value) =>
+ '$value.toJson()');
+ } else if (resolvedType is TypeObject || resolvedType is TypeEnum) {
+ return new ToJsonSnippet(dartType(type), (String value) =>
+ '$value.toJson()');
+ } else {
+ throw new Exception("Can't convert $resolvedType from JSON");
+ }
+ }
+
+ /**
+ * Emit the JsonDecoder class, which contains code necessary to translate
+ * objects from JSON format.
+ */
+ void emitJsonDecoder() {
+ writeln('abstract class JsonDecoder extends JsonDecoderBase {');
+ indent(() {
+ for (ImpliedType impliedType in impliedTypes.values) {
+ TypeDecl type = impliedType.type;
+ if (type != null) {
+ String dartTypeName = capitalize(impliedType.camelName);
+ if (type is TypeObject) {
+ writeln();
+ emitObjectFromJson(dartTypeName, type, impliedType);
+ } else if (type is TypeEnum) {
+ writeln();
+ emitEnumFromJson(dartTypeName, type, impliedType);
+ }
+ }
+ }
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the method for decoding an object from JSON.
+ */
+ void emitObjectFromJson(String className, TypeObject type, ImpliedType
+ impliedType) {
+ String humanReadableNameString = literalString(impliedType.humanReadableName
+ );
+ String methodName = camelJoin(['decode', className]);
+ writeln('$className $methodName(String jsonPath, Object json) {');
+ indent(() {
+ writeln('if (json is Map) {');
+ indent(() {
+ List<String> args = <String>[];
+ List<String> optionalArgs = <String>[];
+ for (TypeObjectField field in type.fields) {
+ String fieldNameString = literalString(field.name);
+ String fieldAccessor = 'json[$fieldNameString]';
+ String jsonPath = 'jsonPath + ${literalString('.${field.name}')}';
+ if (field.value != null) {
+ String valueString = literalString(field.value);
+ writeln('if ($fieldAccessor != $valueString) {');
+ indent(() {
+ writeln('throw mismatch(jsonPath, "equal " + $valueString);');
+ });
+ writeln('}');
+ continue;
+ }
+ if (field.optional) {
+ optionalArgs.add('${field.name}: ${field.name}');
+ } else {
+ args.add(field.name);
+ }
+ String fieldDartType = dartType(field.type);
+ writeln('$fieldDartType ${field.name};');
+ writeln('if (json.containsKey($fieldNameString)) {');
+ indent(() {
+ String toJson = fromJsonCode(field.type).asSnippet(jsonPath,
+ fieldAccessor);
+ writeln('${field.name} = $toJson;');
+ });
+ write('}');
+ if (!field.optional) {
+ writeln(' else {');
+ indent(() {
+ writeln("throw missingKey(jsonPath, $fieldNameString);");
+ });
+ writeln('}');
+ } else {
+ writeln();
+ }
+ }
+ args.addAll(optionalArgs);
+ writeln('return new $className(${args.join(', ')});');
+ });
+ writeln('} else {');
+ indent(() {
+ writeln('throw mismatch(jsonPath, $humanReadableNameString);');
+ });
+ writeln('}');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Emit the method for decoding an enum from JSON.
+ */
+ void emitEnumFromJson(String className, TypeEnum type, ImpliedType
+ impliedType) {
+ String methodName = camelJoin(['decode', className]);
+ writeln('$className $methodName(String jsonPath, Object json) {');
+ indent(() {
+ writeln('if (json is String) {');
+ indent(() {
+ writeln('try {');
+ indent(() {
+ writeln('return new $className(json);');
+ });
+ writeln('} catch(_) {');
+ indent(() {
+ writeln('// Fall through');
+ });
+ writeln('}');
+ });
+ writeln('}');
+ String humanReadableNameString = literalString(
+ impliedType.humanReadableName);
+ writeln('throw mismatch(jsonPath, $humanReadableNameString);');
+ });
+ writeln('}');
+ }
+
+ /**
+ * Compute the code necessary to translate [type] from JSON.
+ */
+ FromJsonCode fromJsonCode(TypeDecl type) {
+ if (type is TypeReference) {
+ TypeDefinition referencedDefinition = api.types[type.typeName];
+ if (referencedDefinition != null) {
+ TypeDecl referencedType = referencedDefinition.type;
+ if (referencedType is TypeObject || referencedType is TypeEnum) {
+ String methodName = camelJoin(['decode', type.typeName]);
+ return new FromJsonFunction(methodName);
+ } else {
+ return fromJsonCode(referencedType);
+ }
+ } else {
+ switch (type.typeName) {
+ case 'String':
+ return new FromJsonFunction('_decodeString');
+ case 'bool':
+ return new FromJsonFunction('_decodeBool');
+ case 'int':
+ return new FromJsonFunction('_decodeInt');
+ case 'object':
+ return new FromJsonIdentity();
+ default:
+ throw new Exception('Unexpected type name ${type.typeName}');
+ }
+ }
+ } else if (type is TypeMap) {
+ FromJsonCode keyCode;
+ if (dartType(type.keyType) != 'String') {
+ keyCode = fromJsonCode(type.keyType);
+ } else {
+ keyCode = new FromJsonIdentity();
+ }
+ FromJsonCode valueCode = fromJsonCode(type.valueType);
+ if (keyCode.isIdentity && valueCode.isIdentity) {
+ return new FromJsonFunction('_decodeMap');
+ } else {
+ return new FromJsonSnippet((String jsonPath, String json) {
+ StringBuffer result = new StringBuffer();
+ result.write('_decodeMap($jsonPath, $json');
+ if (!keyCode.isIdentity) {
+ result.write(', keyDecoder: ${keyCode.asClosure}');
+ }
+ if (!valueCode.isIdentity) {
+ result.write(', valueDecoder: ${valueCode.asClosure}');
+ }
+ result.write(')');
+ return result.toString();
+ });
+ }
+ } else if (type is TypeList) {
+ FromJsonCode itemCode = fromJsonCode(type.itemType);
+ if (itemCode.isIdentity) {
+ return new FromJsonFunction('_decodeList');
+ } else {
+ return new FromJsonSnippet((String jsonPath, String json) =>
+ '_decodeList($jsonPath, $json, ${itemCode.asClosure})');
+ }
+ } else if (type is TypeUnion) {
+ List<String> decoders = <String>[];
+ for (TypeDecl choice in type.choices) {
+ TypeDecl resolvedChoice = resolveTypeReferenceChain(choice);
+ if (resolvedChoice is TypeObject) {
+ TypeObjectField field = resolvedChoice.getField(type.field);
+ if (field == null) {
+ throw new Exception(
+ 'Each choice in the union needs a field named ${type.field}');
+ }
+ if (field.value == null) {
+ throw new Exception(
+ 'Each choice in the union needs a constant value for the field ${type.field}');
+ }
+ String closure = fromJsonCode(choice).asClosure;
+ decoders.add('${literalString(field.value)}: $closure');
+ } else {
+ throw new Exception('Union types must be unions of objects.');
+ }
+ }
+ return new FromJsonSnippet((String jsonPath, String json) =>
+ '_decodeUnion($jsonPath, $json, ${literalString(type.field)}, {${decoders.join(', ')}})'
+ );
+ } else {
+ throw new Exception("Can't convert $type from JSON");
+ }
+ }
+
+ /**
+ * Emit convenience functions for decoding parts of the protocol (requests,
+ * responses, and notifications).
+ */
+ void emitConvenienceFunctions() {
+ for (Domain domain in api.domains) {
+ String domainName = domain.name;
+ for (Request request in domain.requests) {
+ String requestNameCamel = camelJoin([domainName, request.method]);
+ String requestLongName = request.longMethod;
+ dom.Element html = request.html;
+ if (request.params != null) {
+ emitConvenienceDecoder(requestNameCamel, requestLongName, html,
+ 'request', 'params', 'Request', 'request', 'new RequestDecoder(request)',
+ 'a request');
+ }
+ if (request.result != null) {
+ emitConvenienceDecoder(requestNameCamel, requestLongName, html,
+ 'response', 'result', 'Response', 'response', 'new ResponseDecoder()',
+ 'the response to a request');
+ }
+ }
+ for (Notification notification in domain.notifications) {
+ String notificationNameCamel = camelJoin([domainName,
+ notification.event]);
+ String notificationLongName = notification.longEvent;
+ dom.Element html = notification.html;
+ if (notification.params != null) {
+ emitConvenienceDecoder(notificationNameCamel, notificationLongName,
+ html, 'notification', 'params', 'Notification', 'notification',
+ 'new ResponseDecoder()', 'a notification');
+ }
+ }
+ }
+ for (Refactoring refactoring in api.refactorings) {
+ String refactoringNameCamel = camelJoin(refactoring.kind.toLowerCase(
+ ).split('_'));
+ String refactoringLongName = refactoring.kind;
+ dom.Element html = refactoring.html;
+ if (refactoring.feedback != null) {
+ emitConvenienceDecoder(refactoringNameCamel, refactoringLongName, html,
+ 'feedback', 'feedback', 'EditGetRefactoringResult', 'refactoringResult',
+ 'new ResponseDecoder()', 'refactoring feedback');
+ }
+ if (refactoring.options != null) {
+ emitConvenienceDecoder(refactoringNameCamel, refactoringLongName, html,
+ 'options', 'options', 'EditGetRefactoringParams', 'refactoringParams',
+ 'new RequestDecoder(request)', 'refactoring options', extraArgs:
+ ['Request request']);
+ }
+ }
+ }
+
+ /**
+ * Emit a convenience function for decoding a piece of protocol. [camelName]
+ * is the name of the protocol piece (e.g. "analysisGetErrors"). [longName]
+ * is the long name used in the spec (e.g. "analysis.getErrors"). [html] is
+ * the HTML documentation of the protocol piece. [fieldName] is the field
+ * within the protocol piece to be decoded (e.g. 'params'). [inputType] is
+ * the type of the object to be input to the convenience function.
+ * [inputName] is the name of the parameter to the convenience function.
+ * [makeDecoder] is a code snippet for creating the JsonDecoder-derived class
+ * to do the decoding. [description] is a short description of the kind of
+ * protocol being decoded (e.g. "a notification").
+ */
+ void emitConvenienceDecoder(String camelName, String longName, dom.Element
+ html, String methodSuffix, String fieldName, String inputType, String
+ inputName, String makeDecoder, String description, {List<String> extraArgs:
+ const []}) {
+ String impliedTypeName = camelJoin([camelName, fieldName], doCapitalize:
+ true);
+ String methodName = camelJoin(['decode', camelName, methodSuffix]);
+ String paramsDecoder = camelJoin(['decode', impliedTypeName]);
+ writeln();
+ docComment(toHtmlVisitor.collectHtml(() {
+ toHtmlVisitor.p(() {
+ toHtmlVisitor.writeln('Decode $description of type $longName');
+ });
+ toHtmlVisitor.translateHtml(html);
+ }));
+ List<String> args = ['$inputType $inputName'];
+ args.addAll(extraArgs);
+ writeln('$impliedTypeName $methodName(${args.join(', ')}) {');
+ indent(() {
+ writeln('var decoder = $makeDecoder;');
+ String fieldNameString = literalString(fieldName);
+ writeln(
+ "return decoder.$paramsDecoder($fieldNameString, $inputName.$fieldName);");
+ });
+ writeln('}');
+ }
+
+ /**
+ * Create a string literal that evaluates to [s].
+ */
+ String literalString(String s) {
+ return JSON.encode(s);
+ }
+
+ /**
+ * Convert the given [TypeDecl] to a Dart type.
+ */
+ String dartType(TypeDecl type) {
+ if (type is TypeReference) {
+ String typeName = type.typeName;
+ TypeDefinition referencedDefinition = api.types[typeName];
+ if (_typeRenames.containsKey(typeName)) {
+ return _typeRenames[typeName];
+ }
+ if (referencedDefinition == null) {
+ return typeName;
+ }
+ TypeDecl referencedType = referencedDefinition.type;
+ if (referencedType is TypeObject || referencedType is TypeEnum) {
+ return typeName;
+ }
+ return dartType(referencedType);
+ } else if (type is TypeList) {
+ return 'List<${dartType(type.itemType)}>';
+ } else if (type is TypeMap) {
+ return 'Map<${dartType(type.keyType)}, ${dartType(type.valueType)}>';
+ } else if (type is TypeUnion) {
+ return 'dynamic';
+ } else {
+ throw new Exception("Can't convert to a dart type");
+ }
+ }
+}
+
+final GeneratedFile target = new GeneratedFile(
+ '../../lib/src/generated_protocol.dart', () {
+ CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi());
+ return visitor.collectCode(visitor.visitApi);
+});
+
+/**
+ * Translate spec_input.html into protocol_matchers.dart.
+ */
+main() {
+ target.generate();
+}

Powered by Google App Engine
This is Rietveld 408576698