| 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();
|
| +}
|
|
|