OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library codegen.protocol; | 5 library codegen.protocol; |
6 | 6 |
7 import 'dart:convert'; | 7 import 'dart:convert'; |
8 | 8 |
| 9 import 'package:html5lib/dom.dart' as dom; |
| 10 |
9 import 'api.dart'; | 11 import 'api.dart'; |
10 import 'codegen_dart.dart'; | 12 import 'codegen_dart.dart'; |
11 import 'codegen_tools.dart'; | 13 import 'codegen_tools.dart'; |
12 import 'from_html.dart'; | 14 import 'from_html.dart'; |
13 import 'implied_types.dart'; | 15 import 'implied_types.dart'; |
14 import 'to_html.dart'; | 16 import 'to_html.dart'; |
15 | 17 |
16 import 'package:html5lib/dom.dart' as dom; | |
17 | |
18 /** | |
19 * Container for code that can be used to translate a data type from JSON. | |
20 */ | |
21 abstract class FromJsonCode { | |
22 /** | |
23 * True if the data type is already in JSON form, so the translation is the | |
24 * identity function. | |
25 */ | |
26 bool get isIdentity; | |
27 | |
28 /** | |
29 * Get the translation code in the form of a closure. | |
30 */ | |
31 String get asClosure; | |
32 | |
33 /** | |
34 * Get the translation code in the form of a code snippet, where [jsonPath] | |
35 * is the variable holding the JSON path, and [json] is the variable holding | |
36 * the raw JSON. | |
37 */ | |
38 String asSnippet(String jsonPath, String json); | |
39 } | |
40 | |
41 /** | |
42 * Representation of FromJsonCode for a function defined elsewhere. | |
43 */ | |
44 class FromJsonFunction extends FromJsonCode { | |
45 final String asClosure; | |
46 | |
47 FromJsonFunction(this.asClosure); | |
48 | |
49 @override | |
50 bool get isIdentity => false; | |
51 | |
52 @override | |
53 String asSnippet(String jsonPath, String json) => | |
54 '$asClosure($jsonPath, $json)'; | |
55 } | |
56 | |
57 typedef String FromJsonSnippetCallback(String jsonPath, String json); | |
58 | |
59 /** | |
60 * Representation of FromJsonCode for a snippet of inline code. | |
61 */ | |
62 class FromJsonSnippet extends FromJsonCode { | |
63 /** | |
64 * Callback that can be used to generate the code snippet, once the names | |
65 * of the [jsonPath] and [json] variables are known. | |
66 */ | |
67 final FromJsonSnippetCallback callback; | |
68 | |
69 FromJsonSnippet(this.callback); | |
70 | |
71 @override | |
72 bool get isIdentity => false; | |
73 | |
74 @override | |
75 String get asClosure => | |
76 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; | |
77 | |
78 @override | |
79 String asSnippet(String jsonPath, String json) => callback(jsonPath, json); | |
80 } | |
81 | |
82 /** | |
83 * Representation of FromJsonCode for the identity transformation. | |
84 */ | |
85 class FromJsonIdentity extends FromJsonSnippet { | |
86 FromJsonIdentity() : super((String jsonPath, String json) => json); | |
87 | |
88 @override | |
89 bool get isIdentity => true; | |
90 } | |
91 | |
92 /** | |
93 * Container for code that can be used to translate a data type to JSON. | |
94 */ | |
95 abstract class ToJsonCode { | |
96 /** | |
97 * True if the data type is already in JSON form, so the translation is the | |
98 * identity function. | |
99 */ | |
100 bool get isIdentity; | |
101 | |
102 /** | |
103 * Get the translation code in the form of a closure. | |
104 */ | |
105 String get asClosure; | |
106 | |
107 /** | |
108 * Get the translation code in the form of a code snippet, where [value] | |
109 * is the variable holding the object to be translated. | |
110 */ | |
111 String asSnippet(String value); | |
112 } | |
113 | |
114 /** | |
115 * Representation of ToJsonCode for a function defined elsewhere. | |
116 */ | |
117 class ToJsonFunction extends ToJsonCode { | |
118 final String asClosure; | |
119 | |
120 ToJsonFunction(this.asClosure); | |
121 | |
122 @override | |
123 bool get isIdentity => false; | |
124 | |
125 @override | |
126 String asSnippet(String value) => '$asClosure($value)'; | |
127 } | |
128 | |
129 typedef String ToJsonSnippetCallback(String value); | |
130 | |
131 /** | |
132 * Representation of ToJsonCode for a snippet of inline code. | |
133 */ | |
134 class ToJsonSnippet extends ToJsonCode { | |
135 /** | |
136 * Callback that can be used to generate the code snippet, once the name | |
137 * of the [value] variable is known. | |
138 */ | |
139 final ToJsonSnippetCallback callback; | |
140 | |
141 /** | |
142 * Dart type of the [value] variable. | |
143 */ | |
144 final String type; | |
145 | |
146 ToJsonSnippet(this.type, this.callback); | |
147 | |
148 @override | |
149 bool get isIdentity => false; | |
150 | |
151 @override | |
152 String get asClosure => '($type value) => ${callback('value')}'; | |
153 | |
154 @override | |
155 String asSnippet(String value) => callback(value); | |
156 } | |
157 | |
158 /** | |
159 * Representation of FromJsonCode for the identity transformation. | |
160 */ | |
161 class ToJsonIdentity extends ToJsonSnippet { | |
162 ToJsonIdentity(String type) : super(type, (String value) => value); | |
163 | |
164 @override | |
165 bool get isIdentity => true; | |
166 } | |
167 | |
168 /** | 18 /** |
169 * Special flags that need to be inserted into the declaration of the Element | 19 * Special flags that need to be inserted into the declaration of the Element |
170 * class. | 20 * class. |
171 */ | 21 */ |
172 const Map<String, String> specialElementFlags = const { | 22 const Map<String, String> specialElementFlags = const { |
173 'abstract': '0x01', | 23 'abstract': '0x01', |
174 'const': '0x02', | 24 'const': '0x02', |
175 'final': '0x04', | 25 'final': '0x04', |
176 'static': '0x08', | 26 'static': '0x08', |
177 'private': '0x10', | 27 'private': '0x10', |
178 'deprecated': '0x20' | 28 'deprecated': '0x20' |
179 }; | 29 }; |
180 | 30 |
| 31 final GeneratedFile target = |
| 32 new GeneratedFile('../../lib/src/generated_protocol.dart', () { |
| 33 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); |
| 34 return visitor.collectCode(visitor.visitApi); |
| 35 }); |
| 36 |
| 37 /** |
| 38 * Translate spec_input.html into protocol_matchers.dart. |
| 39 */ |
| 40 main() { |
| 41 target.generate(); |
| 42 } |
| 43 |
181 /** | 44 /** |
182 * Callback type used to represent arbitrary code generation. | 45 * Callback type used to represent arbitrary code generation. |
183 */ | 46 */ |
184 typedef void CodegenCallback(); | 47 typedef void CodegenCallback(); |
185 | 48 |
| 49 typedef String FromJsonSnippetCallback(String jsonPath, String json); |
| 50 |
| 51 typedef String ToJsonSnippetCallback(String value); |
| 52 |
186 /** | 53 /** |
187 * Visitor which produces Dart code representing the API. | 54 * Visitor which produces Dart code representing the API. |
188 */ | 55 */ |
189 class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator { | 56 class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator { |
190 /** | 57 /** |
191 * Class members for which the constructor argument should be optional, even | 58 * Class members for which the constructor argument should be optional, even |
192 * if the member is not an optional part of the protocol. For list types, | 59 * if the member is not an optional part of the protocol. For list types, |
193 * the constructor will default the member to the empty list. | 60 * the constructor will default the member to the empty list. |
194 */ | 61 */ |
195 static const Map<String, List<String>> _optionalConstructorArguments = const { | 62 static const Map<String, List<String>> _optionalConstructorArguments = const { |
(...skipping 13 matching lines...) Expand all Loading... |
209 * API as well as those implied by the definitions of requests, responses, | 76 * API as well as those implied by the definitions of requests, responses, |
210 * notifications, etc. | 77 * notifications, etc. |
211 */ | 78 */ |
212 final Map<String, ImpliedType> impliedTypes; | 79 final Map<String, ImpliedType> impliedTypes; |
213 | 80 |
214 CodegenProtocolVisitor(Api api) | 81 CodegenProtocolVisitor(Api api) |
215 : super(api), | 82 : super(api), |
216 toHtmlVisitor = new ToHtmlVisitor(api), | 83 toHtmlVisitor = new ToHtmlVisitor(api), |
217 impliedTypes = computeImpliedTypes(api); | 84 impliedTypes = computeImpliedTypes(api); |
218 | 85 |
219 @override | 86 /** |
220 visitApi() { | 87 * Compute the code necessary to compare two objects for equality. |
221 outputHeader(); | 88 */ |
222 writeln(); | 89 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) { |
223 writeln('part of protocol;'); | 90 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
224 emitClasses(); | 91 if (resolvedType is TypeReference || |
| 92 resolvedType is TypeEnum || |
| 93 resolvedType is TypeObject || |
| 94 resolvedType is TypeUnion) { |
| 95 return '$thisVar == $otherVar'; |
| 96 } else if (resolvedType is TypeList) { |
| 97 String itemTypeName = dartType(resolvedType.itemType); |
| 98 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b'); |
| 99 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison'; |
| 100 return '_listEqual($thisVar, $otherVar, $closure)'; |
| 101 } else if (resolvedType is TypeMap) { |
| 102 String valueTypeName = dartType(resolvedType.valueType); |
| 103 String subComparison = |
| 104 compareEqualsCode(resolvedType.valueType, 'a', 'b'); |
| 105 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison'; |
| 106 return '_mapEqual($thisVar, $otherVar, $closure)'; |
| 107 } |
| 108 throw new Exception( |
| 109 "Don't know how to compare for equality: $resolvedType"); |
225 } | 110 } |
226 | 111 |
227 /** | 112 /** |
228 * Translate each type implied by the API to a class. | 113 * Translate each type implied by the API to a class. |
229 */ | 114 */ |
230 void emitClasses() { | 115 void emitClasses() { |
231 for (ImpliedType impliedType in impliedTypes.values) { | 116 for (ImpliedType impliedType in impliedTypes.values) { |
232 TypeDecl type = impliedType.type; | 117 TypeDecl type = impliedType.type; |
233 String dartTypeName = capitalize(impliedType.camelName); | 118 String dartTypeName = capitalize(impliedType.camelName); |
234 if (type == null) { | 119 if (type == null) { |
235 emitEmptyObjectClass(dartTypeName, impliedType); | 120 emitEmptyObjectClass(dartTypeName, impliedType); |
236 } else if (type is TypeObject || type == null) { | 121 } else if (type is TypeObject || type == null) { |
237 writeln(); | 122 writeln(); |
238 emitObjectClass(dartTypeName, type, impliedType); | 123 emitObjectClass(dartTypeName, type, impliedType); |
239 } else if (type is TypeEnum) { | 124 } else if (type is TypeEnum) { |
240 writeln(); | 125 writeln(); |
241 emitEnumClass(dartTypeName, type, impliedType); | 126 emitEnumClass(dartTypeName, type, impliedType); |
242 } | 127 } |
243 } | 128 } |
244 } | 129 } |
245 | 130 |
246 /** | 131 /** |
| 132 * Emit a convenience constructor for decoding a piece of protocol, if |
| 133 * appropriate. Return true if a constructor was emitted. |
| 134 */ |
| 135 bool emitConvenienceConstructor(String className, ImpliedType impliedType) { |
| 136 // The type of object from which this piece of protocol should be decoded. |
| 137 String inputType; |
| 138 // The name of the input object. |
| 139 String inputName; |
| 140 // The field within the input object to decode. |
| 141 String fieldName; |
| 142 // Constructor call to create the JsonDecoder object. |
| 143 String makeDecoder; |
| 144 // Name of the constructor to create. |
| 145 String constructorName; |
| 146 // Extra arguments for the constructor. |
| 147 List<String> extraArgs = <String>[]; |
| 148 switch (impliedType.kind) { |
| 149 case 'requestParams': |
| 150 inputType = 'Request'; |
| 151 inputName = 'request'; |
| 152 fieldName = '_params'; |
| 153 makeDecoder = 'new RequestDecoder(request)'; |
| 154 constructorName = 'fromRequest'; |
| 155 break; |
| 156 case 'requestResult': |
| 157 inputType = 'Response'; |
| 158 inputName = 'response'; |
| 159 fieldName = '_result'; |
| 160 makeDecoder = |
| 161 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id
))'; |
| 162 constructorName = 'fromResponse'; |
| 163 break; |
| 164 case 'notificationParams': |
| 165 inputType = 'Notification'; |
| 166 inputName = 'notification'; |
| 167 fieldName = '_params'; |
| 168 makeDecoder = 'new ResponseDecoder(null)'; |
| 169 constructorName = 'fromNotification'; |
| 170 break; |
| 171 case 'refactoringOptions': |
| 172 inputType = 'EditGetRefactoringParams'; |
| 173 inputName = 'refactoringParams'; |
| 174 fieldName = 'options'; |
| 175 makeDecoder = 'new RequestDecoder(request)'; |
| 176 constructorName = 'fromRefactoringParams'; |
| 177 extraArgs.add('Request request'); |
| 178 break; |
| 179 default: |
| 180 return false; |
| 181 } |
| 182 List<String> args = ['$inputType $inputName']; |
| 183 args.addAll(extraArgs); |
| 184 writeln('factory $className.$constructorName(${args.join(', ')}) {'); |
| 185 indent(() { |
| 186 String fieldNameString = |
| 187 literalString(fieldName.replaceFirst(new RegExp('^_'), '')); |
| 188 if (className == 'EditGetRefactoringParams') { |
| 189 writeln('var params = new $className.fromJson('); |
| 190 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); |
| 191 writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;'); |
| 192 writeln('return params;'); |
| 193 } else { |
| 194 writeln('return new $className.fromJson('); |
| 195 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); |
| 196 } |
| 197 }); |
| 198 writeln('}'); |
| 199 return true; |
| 200 } |
| 201 |
| 202 /** |
247 * Emit a class representing an data structure that doesn't exist in the | 203 * Emit a class representing an data structure that doesn't exist in the |
248 * protocol because it is empty (e.g. the "params" object for a request that | 204 * protocol because it is empty (e.g. the "params" object for a request that |
249 * doesn't have any parameters). | 205 * doesn't have any parameters). |
250 */ | 206 */ |
251 void emitEmptyObjectClass(String className, ImpliedType impliedType) { | 207 void emitEmptyObjectClass(String className, ImpliedType impliedType) { |
252 docComment(toHtmlVisitor.collectHtml(() { | 208 docComment(toHtmlVisitor.collectHtml(() { |
253 toHtmlVisitor.p(() { | 209 toHtmlVisitor.p(() { |
254 toHtmlVisitor.write(impliedType.humanReadableName); | 210 toHtmlVisitor.write(impliedType.humanReadableName); |
255 }); | 211 }); |
256 })); | 212 })); |
257 writeln('class $className {'); | 213 writeln('class $className {'); |
258 indent(() { | 214 indent(() { |
259 if (emitToRequestMember(impliedType)) { | 215 if (emitToRequestMember(impliedType)) { |
260 writeln(); | 216 writeln(); |
261 } | 217 } |
262 if (emitToResponseMember(impliedType)) { | 218 if (emitToResponseMember(impliedType)) { |
263 writeln(); | 219 writeln(); |
264 } | 220 } |
265 if (emitToNotificationMember(impliedType)) { | 221 if (emitToNotificationMember(impliedType)) { |
266 writeln(); | 222 writeln(); |
267 } | 223 } |
268 emitObjectEqualsMember(null, className); | 224 emitObjectEqualsMember(null, className); |
269 writeln(); | 225 writeln(); |
270 emitObjectHashCode(null, className); | 226 emitObjectHashCode(null, className); |
271 }); | 227 }); |
272 writeln('}'); | 228 writeln('}'); |
273 } | 229 } |
274 | 230 |
275 /** | 231 /** |
| 232 * Emit a class to encapsulate an enum. |
| 233 */ |
| 234 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { |
| 235 docComment(toHtmlVisitor.collectHtml(() { |
| 236 toHtmlVisitor.p(() { |
| 237 toHtmlVisitor.write(impliedType.humanReadableName); |
| 238 }); |
| 239 if (impliedType.type != null) { |
| 240 toHtmlVisitor.showType(null, impliedType.type); |
| 241 } |
| 242 })); |
| 243 writeln('class $className implements Enum {'); |
| 244 indent(() { |
| 245 if (emitSpecialStaticMembers(className)) { |
| 246 writeln(); |
| 247 } |
| 248 for (TypeEnumValue value in type.values) { |
| 249 docComment(toHtmlVisitor.collectHtml(() { |
| 250 toHtmlVisitor.translateHtml(value.html); |
| 251 })); |
| 252 String valueString = literalString(value.value); |
| 253 writeln( |
| 254 'static const ${value.value} = const $className._($valueString);'); |
| 255 writeln(); |
| 256 } |
| 257 writeln('final String name;'); |
| 258 writeln(); |
| 259 writeln('const $className._(this.name);'); |
| 260 writeln(); |
| 261 emitEnumClassConstructor(className, type); |
| 262 writeln(); |
| 263 emitEnumFromJsonConstructor(className, type, impliedType); |
| 264 writeln(); |
| 265 if (emitSpecialConstructors(className)) { |
| 266 writeln(); |
| 267 } |
| 268 if (emitSpecialGetters(className)) { |
| 269 writeln(); |
| 270 } |
| 271 if (emitSpecialMethods(className)) { |
| 272 writeln(); |
| 273 } |
| 274 writeln('@override'); |
| 275 writeln('String toString() => "$className.\$name";'); |
| 276 writeln(); |
| 277 writeln('String toJson() => name;'); |
| 278 }); |
| 279 writeln('}'); |
| 280 } |
| 281 |
| 282 /** |
| 283 * Emit the constructor for an enum class. |
| 284 */ |
| 285 void emitEnumClassConstructor(String className, TypeEnum type) { |
| 286 writeln('factory $className(String name) {'); |
| 287 indent(() { |
| 288 writeln('switch (name) {'); |
| 289 indent(() { |
| 290 for (TypeEnumValue value in type.values) { |
| 291 String valueString = literalString(value.value); |
| 292 writeln('case $valueString:'); |
| 293 indent(() { |
| 294 writeln('return ${value.value};'); |
| 295 }); |
| 296 } |
| 297 }); |
| 298 writeln('}'); |
| 299 writeln(r"throw new Exception('Illegal enum value: $name');"); |
| 300 }); |
| 301 writeln('}'); |
| 302 } |
| 303 |
| 304 /** |
| 305 * Emit the method for decoding an enum from JSON. |
| 306 */ |
| 307 void emitEnumFromJsonConstructor(String className, TypeEnum type, |
| 308 ImpliedType impliedType) { |
| 309 writeln( |
| 310 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); |
| 311 indent(() { |
| 312 writeln('if (json is String) {'); |
| 313 indent(() { |
| 314 writeln('try {'); |
| 315 indent(() { |
| 316 writeln('return new $className(json);'); |
| 317 }); |
| 318 writeln('} catch(_) {'); |
| 319 indent(() { |
| 320 writeln('// Fall through'); |
| 321 }); |
| 322 writeln('}'); |
| 323 }); |
| 324 writeln('}'); |
| 325 String humanReadableNameString = |
| 326 literalString(impliedType.humanReadableName); |
| 327 writeln( |
| 328 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); |
| 329 }); |
| 330 writeln('}'); |
| 331 } |
| 332 |
| 333 /** |
276 * Emit the class to encapsulate an object type. | 334 * Emit the class to encapsulate an object type. |
277 */ | 335 */ |
278 void emitObjectClass(String className, TypeObject type, | 336 void emitObjectClass(String className, TypeObject type, |
279 ImpliedType impliedType) { | 337 ImpliedType impliedType) { |
280 docComment(toHtmlVisitor.collectHtml(() { | 338 docComment(toHtmlVisitor.collectHtml(() { |
281 toHtmlVisitor.p(() { | 339 toHtmlVisitor.p(() { |
282 toHtmlVisitor.write(impliedType.humanReadableName); | 340 toHtmlVisitor.write(impliedType.humanReadableName); |
283 }); | 341 }); |
284 if (impliedType.type != null) { | 342 if (impliedType.type != null) { |
285 toHtmlVisitor.showType(null, impliedType.type); | 343 toHtmlVisitor.showType(null, impliedType.type); |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
338 writeln('String toString() => JSON.encode(toJson());'); | 396 writeln('String toString() => JSON.encode(toJson());'); |
339 writeln(); | 397 writeln(); |
340 emitObjectEqualsMember(type, className); | 398 emitObjectEqualsMember(type, className); |
341 writeln(); | 399 writeln(); |
342 emitObjectHashCode(type, className); | 400 emitObjectHashCode(type, className); |
343 }); | 401 }); |
344 writeln('}'); | 402 writeln('}'); |
345 } | 403 } |
346 | 404 |
347 /** | 405 /** |
348 * If the class named [className] requires special static members, emit them | 406 * Emit the constructor for an object class. |
349 * and return true. | 407 */ |
350 */ | 408 void emitObjectConstructor(TypeObject type, String className) { |
351 bool emitSpecialStaticMembers(String className) { | 409 List<String> args = <String>[]; |
352 switch (className) { | 410 List<String> optionalArgs = <String>[]; |
353 case 'Element': | 411 List<CodegenCallback> extraInitCode = <CodegenCallback>[]; |
354 List<String> makeFlagsArgs = <String>[]; | 412 for (TypeObjectField field in type.fields) { |
355 List<String> makeFlagsStatements = <String>[]; | 413 if (field.value != null) { |
356 specialElementFlags.forEach((String name, String value) { | 414 continue; |
357 String flag = 'FLAG_${name.toUpperCase()}'; | 415 } |
358 String camelName = camelJoin(['is', name]); | 416 String arg = 'this.${field.name}'; |
359 writeln('static const int $flag = $value;'); | 417 if (isOptionalConstructorArg(className, field)) { |
360 makeFlagsArgs.add('$camelName: false'); | 418 optionalArgs.add(arg); |
361 makeFlagsStatements.add('if ($camelName) flags |= $flag;'); | 419 if (!field.optional) { |
362 }); | 420 // Optional constructor arg, but non-optional field. If no arg is |
363 writeln(); | 421 // given, the constructor should populate with the empty list. |
364 writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {'); | 422 TypeDecl fieldType = field.type; |
365 indent(() { | 423 if (fieldType is TypeList) { |
366 writeln('int flags = 0;'); | 424 extraInitCode.add(() { |
367 for (String statement in makeFlagsStatements) { | 425 writeln('if (${field.name} == null) {'); |
368 writeln(statement); | 426 indent(() { |
369 } | 427 writeln('${field.name} = <${dartType(fieldType.itemType)}>[];'); |
370 writeln('return flags;'); | 428 }); |
371 }); | 429 writeln('}'); |
372 writeln('}'); | 430 }); |
373 return true; | 431 } else { |
374 case 'SourceEdit': | 432 throw new Exception( |
375 docComment( | 433 "Don't know how to create default field value."); |
376 [ | 434 } |
377 new dom.Text( | 435 } |
378 'Get the result of applying a set of ' + | 436 } else { |
379 '[edits] to the given [code]. Edits are applied in the
order ' + | 437 args.add(arg); |
380 'they appear in [edits].')]); | 438 } |
| 439 } |
| 440 if (optionalArgs.isNotEmpty) { |
| 441 args.add('{${optionalArgs.join(', ')}}'); |
| 442 } |
| 443 write('$className(${args.join(', ')})'); |
| 444 if (extraInitCode.isEmpty) { |
| 445 writeln(';'); |
| 446 } else { |
| 447 writeln(' {'); |
| 448 indent(() { |
| 449 for (CodegenCallback callback in extraInitCode) { |
| 450 callback(); |
| 451 } |
| 452 }); |
| 453 writeln('}'); |
| 454 } |
| 455 } |
| 456 |
| 457 /** |
| 458 * Emit the operator== code for an object class. |
| 459 */ |
| 460 void emitObjectEqualsMember(TypeObject type, String className) { |
| 461 writeln('@override'); |
| 462 writeln('bool operator==(other) {'); |
| 463 indent(() { |
| 464 writeln('if (other is $className) {'); |
| 465 indent(() { |
| 466 var comparisons = <String>[]; |
| 467 if (type != null) { |
| 468 for (TypeObjectField field in type.fields) { |
| 469 if (field.value != null) { |
| 470 continue; |
| 471 } |
| 472 comparisons.add( |
| 473 compareEqualsCode(field.type, field.name, 'other.${field.name}')
); |
| 474 } |
| 475 } |
| 476 if (comparisons.isEmpty) { |
| 477 writeln('return true;'); |
| 478 } else { |
| 479 String concatenated = comparisons.join(' &&\n '); |
| 480 writeln('return $concatenated;'); |
| 481 } |
| 482 }); |
| 483 writeln('}'); |
| 484 writeln('return false;'); |
| 485 }); |
| 486 writeln('}'); |
| 487 } |
| 488 |
| 489 /** |
| 490 * Emit the method for decoding an object from JSON. |
| 491 */ |
| 492 void emitObjectFromJsonConstructor(String className, TypeObject type, |
| 493 ImpliedType impliedType) { |
| 494 String humanReadableNameString = |
| 495 literalString(impliedType.humanReadableName); |
| 496 if (className == 'RefactoringFeedback') { |
| 497 writeln( |
| 498 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, ' |
| 499 'String jsonPath, Object json, Map responseJson) {'); |
| 500 indent(() { |
381 writeln( | 501 writeln( |
382 'static String applySequence(String code, Iterable<SourceEdit> edits
) =>'); | 502 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, ' |
383 writeln(' _applySequence(code, edits);'); | 503 'json, responseJson);'); |
384 return true; | 504 }); |
385 default: | 505 writeln('}'); |
386 return false; | 506 return; |
387 } | 507 } |
388 } | 508 if (className == 'RefactoringOptions') { |
389 | 509 writeln( |
390 /** | 510 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, ' |
| 511 'String jsonPath, Object json, RefactoringKind kind) {'); |
| 512 indent(() { |
| 513 writeln( |
| 514 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json,
kind);'); |
| 515 }); |
| 516 writeln('}'); |
| 517 return; |
| 518 } |
| 519 writeln( |
| 520 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); |
| 521 indent(() { |
| 522 writeln('if (json == null) {'); |
| 523 indent(() { |
| 524 writeln('json = {};'); |
| 525 }); |
| 526 writeln('}'); |
| 527 writeln('if (json is Map) {'); |
| 528 indent(() { |
| 529 List<String> args = <String>[]; |
| 530 List<String> optionalArgs = <String>[]; |
| 531 for (TypeObjectField field in type.fields) { |
| 532 String fieldNameString = literalString(field.name); |
| 533 String fieldAccessor = 'json[$fieldNameString]'; |
| 534 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; |
| 535 if (field.value != null) { |
| 536 String valueString = literalString(field.value); |
| 537 writeln('if ($fieldAccessor != $valueString) {'); |
| 538 indent(() { |
| 539 writeln( |
| 540 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString)
;'); |
| 541 }); |
| 542 writeln('}'); |
| 543 continue; |
| 544 } |
| 545 if (isOptionalConstructorArg(className, field)) { |
| 546 optionalArgs.add('${field.name}: ${field.name}'); |
| 547 } else { |
| 548 args.add(field.name); |
| 549 } |
| 550 TypeDecl fieldType = field.type; |
| 551 String fieldDartType = dartType(fieldType); |
| 552 writeln('$fieldDartType ${field.name};'); |
| 553 writeln('if (json.containsKey($fieldNameString)) {'); |
| 554 indent(() { |
| 555 String fromJson = |
| 556 fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor); |
| 557 writeln('${field.name} = $fromJson;'); |
| 558 }); |
| 559 write('}'); |
| 560 if (!field.optional) { |
| 561 writeln(' else {'); |
| 562 indent(() { |
| 563 writeln( |
| 564 "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);"); |
| 565 }); |
| 566 writeln('}'); |
| 567 } else { |
| 568 writeln(); |
| 569 } |
| 570 } |
| 571 args.addAll(optionalArgs); |
| 572 writeln('return new $className(${args.join(', ')});'); |
| 573 }); |
| 574 writeln('} else {'); |
| 575 indent(() { |
| 576 writeln( |
| 577 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); |
| 578 }); |
| 579 writeln('}'); |
| 580 }); |
| 581 writeln('}'); |
| 582 } |
| 583 |
| 584 /** |
| 585 * Emit the hashCode getter for an object class. |
| 586 */ |
| 587 void emitObjectHashCode(TypeObject type, String className) { |
| 588 writeln('@override'); |
| 589 writeln('int get hashCode {'); |
| 590 indent(() { |
| 591 if (type == null) { |
| 592 writeln('return ${className.hashCode};'); |
| 593 } else { |
| 594 writeln('int hash = 0;'); |
| 595 for (TypeObjectField field in type.fields) { |
| 596 String valueToCombine; |
| 597 if (field.value != null) { |
| 598 valueToCombine = field.value.hashCode.toString(); |
| 599 } else { |
| 600 valueToCombine = '${field.name}.hashCode'; |
| 601 } |
| 602 writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); |
| 603 } |
| 604 writeln('return _JenkinsSmiHash.finish(hash);'); |
| 605 } |
| 606 }); |
| 607 writeln('}'); |
| 608 } |
| 609 |
| 610 /** |
391 * If the class named [className] requires special constructors, emit them | 611 * If the class named [className] requires special constructors, emit them |
392 * and return true. | 612 * and return true. |
393 */ | 613 */ |
394 bool emitSpecialConstructors(String className) { | 614 bool emitSpecialConstructors(String className) { |
395 switch (className) { | 615 switch (className) { |
396 case 'LinkedEditGroup': | 616 case 'LinkedEditGroup': |
397 docComment([new dom.Text('Construct an empty LinkedEditGroup.')]); | 617 docComment([new dom.Text('Construct an empty LinkedEditGroup.')]); |
398 writeln( | 618 writeln( |
399 'LinkedEditGroup.empty() : this(<Position>[], 0, <LinkedEditSuggesti
on>[]);'); | 619 'LinkedEditGroup.empty() : this(<Position>[], 0, <LinkedEditSuggesti
on>[]);'); |
400 return true; | 620 return true; |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
493 docComment([new dom.Text('Adds the given [Edit]s.')]); | 713 docComment([new dom.Text('Adds the given [Edit]s.')]); |
494 writeln('void addAll(Iterable<SourceEdit> edits) =>'); | 714 writeln('void addAll(Iterable<SourceEdit> edits) =>'); |
495 writeln(' _addAllEditsForSource(this, edits);'); | 715 writeln(' _addAllEditsForSource(this, edits);'); |
496 return true; | 716 return true; |
497 default: | 717 default: |
498 return false; | 718 return false; |
499 } | 719 } |
500 } | 720 } |
501 | 721 |
502 /** | 722 /** |
503 * Emit the constructor for an object class. | 723 * If the class named [className] requires special static members, emit them |
| 724 * and return true. |
504 */ | 725 */ |
505 void emitObjectConstructor(TypeObject type, String className) { | 726 bool emitSpecialStaticMembers(String className) { |
506 List<String> args = <String>[]; | 727 switch (className) { |
507 List<String> optionalArgs = <String>[]; | 728 case 'Element': |
508 List<CodegenCallback> extraInitCode = <CodegenCallback>[]; | 729 List<String> makeFlagsArgs = <String>[]; |
509 for (TypeObjectField field in type.fields) { | 730 List<String> makeFlagsStatements = <String>[]; |
510 if (field.value != null) { | 731 specialElementFlags.forEach((String name, String value) { |
511 continue; | 732 String flag = 'FLAG_${name.toUpperCase()}'; |
512 } | 733 String camelName = camelJoin(['is', name]); |
513 String arg = 'this.${field.name}'; | 734 writeln('static const int $flag = $value;'); |
514 if (isOptionalConstructorArg(className, field)) { | 735 makeFlagsArgs.add('$camelName: false'); |
515 optionalArgs.add(arg); | 736 makeFlagsStatements.add('if ($camelName) flags |= $flag;'); |
516 if (!field.optional) { | 737 }); |
517 // Optional constructor arg, but non-optional field. If no arg is | 738 writeln(); |
518 // given, the constructor should populate with the empty list. | 739 writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {'); |
519 TypeDecl fieldType = field.type; | 740 indent(() { |
520 if (fieldType is TypeList) { | 741 writeln('int flags = 0;'); |
521 extraInitCode.add(() { | 742 for (String statement in makeFlagsStatements) { |
522 writeln('if (${field.name} == null) {'); | 743 writeln(statement); |
523 indent(() { | |
524 writeln('${field.name} = <${dartType(fieldType.itemType)}>[];'); | |
525 }); | |
526 writeln('}'); | |
527 }); | |
528 } else { | |
529 throw new Exception( | |
530 "Don't know how to create default field value."); | |
531 } | 744 } |
532 } | 745 writeln('return flags;'); |
533 } else { | 746 }); |
534 args.add(arg); | 747 writeln('}'); |
535 } | 748 return true; |
536 } | 749 case 'SourceEdit': |
537 if (optionalArgs.isNotEmpty) { | 750 docComment( |
538 args.add('{${optionalArgs.join(', ')}}'); | 751 [ |
539 } | 752 new dom.Text( |
540 write('$className(${args.join(', ')})'); | 753 'Get the result of applying a set of ' + |
541 if (extraInitCode.isEmpty) { | 754 '[edits] to the given [code]. Edits are applied in the
order ' + |
542 writeln(';'); | 755 'they appear in [edits].')]); |
543 } else { | 756 writeln( |
544 writeln(' {'); | 757 'static String applySequence(String code, Iterable<SourceEdit> edits
) =>'); |
545 indent(() { | 758 writeln(' _applySequence(code, edits);'); |
546 for (CodegenCallback callback in extraInitCode) { | 759 return true; |
547 callback(); | 760 default: |
548 } | 761 return false; |
549 }); | |
550 writeln('}'); | |
551 } | 762 } |
552 } | 763 } |
553 | 764 |
554 /** | 765 /** |
555 * True if the constructor argument for the given field should be optional. | |
556 */ | |
557 bool isOptionalConstructorArg(String className, TypeObjectField field) { | |
558 if (field.optional) { | |
559 return true; | |
560 } | |
561 List<String> forceOptional = _optionalConstructorArguments[className]; | |
562 if (forceOptional != null && forceOptional.contains(field.name)) { | |
563 return true; | |
564 } | |
565 return false; | |
566 } | |
567 | |
568 /** | |
569 * Emit the toJson() code for an object class. | 766 * Emit the toJson() code for an object class. |
570 */ | 767 */ |
571 void emitToJsonMember(TypeObject type) { | 768 void emitToJsonMember(TypeObject type) { |
572 writeln('Map<String, dynamic> toJson() {'); | 769 writeln('Map<String, dynamic> toJson() {'); |
573 indent(() { | 770 indent(() { |
574 writeln('Map<String, dynamic> result = {};'); | 771 writeln('Map<String, dynamic> result = {};'); |
575 for (TypeObjectField field in type.fields) { | 772 for (TypeObjectField field in type.fields) { |
576 String fieldNameString = literalString(field.name); | 773 String fieldNameString = literalString(field.name); |
577 if (field.value != null) { | 774 if (field.value != null) { |
578 writeln('result[$fieldNameString] = ${literalString(field.value)};'); | 775 writeln('result[$fieldNameString] = ${literalString(field.value)};'); |
(...skipping 10 matching lines...) Expand all Loading... |
589 } else { | 786 } else { |
590 writeln(populateField); | 787 writeln(populateField); |
591 } | 788 } |
592 } | 789 } |
593 writeln('return result;'); | 790 writeln('return result;'); |
594 }); | 791 }); |
595 writeln('}'); | 792 writeln('}'); |
596 } | 793 } |
597 | 794 |
598 /** | 795 /** |
| 796 * Emit the toNotification() code for a class, if appropriate. Returns true |
| 797 * if code was emitted. |
| 798 */ |
| 799 bool emitToNotificationMember(ImpliedType impliedType) { |
| 800 if (impliedType.kind == 'notificationParams') { |
| 801 writeln('Notification toNotification() {'); |
| 802 indent(() { |
| 803 String eventString = |
| 804 literalString((impliedType.apiNode as Notification).longEvent); |
| 805 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
| 806 writeln('return new Notification($eventString, $jsonPart);'); |
| 807 }); |
| 808 writeln('}'); |
| 809 return true; |
| 810 } |
| 811 return false; |
| 812 } |
| 813 |
| 814 /** |
599 * Emit the toRequest() code for a class, if appropriate. Returns true if | 815 * Emit the toRequest() code for a class, if appropriate. Returns true if |
600 * code was emitted. | 816 * code was emitted. |
601 */ | 817 */ |
602 bool emitToRequestMember(ImpliedType impliedType) { | 818 bool emitToRequestMember(ImpliedType impliedType) { |
603 if (impliedType.kind == 'requestParams') { | 819 if (impliedType.kind == 'requestParams') { |
604 writeln('Request toRequest(String id) {'); | 820 writeln('Request toRequest(String id) {'); |
605 indent(() { | 821 indent(() { |
606 String methodString = | 822 String methodString = |
607 literalString((impliedType.apiNode as Request).longMethod); | 823 literalString((impliedType.apiNode as Request).longMethod); |
608 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 824 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
(...skipping 16 matching lines...) Expand all Loading... |
625 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 841 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
626 writeln('return new Response(id, result: $jsonPart);'); | 842 writeln('return new Response(id, result: $jsonPart);'); |
627 }); | 843 }); |
628 writeln('}'); | 844 writeln('}'); |
629 return true; | 845 return true; |
630 } | 846 } |
631 return false; | 847 return false; |
632 } | 848 } |
633 | 849 |
634 /** | 850 /** |
635 * Emit the toNotification() code for a class, if appropriate. Returns true | 851 * Compute the code necessary to translate [type] from JSON. |
636 * if code was emitted. | |
637 */ | 852 */ |
638 bool emitToNotificationMember(ImpliedType impliedType) { | 853 FromJsonCode fromJsonCode(TypeDecl type) { |
639 if (impliedType.kind == 'notificationParams') { | 854 if (type is TypeReference) { |
640 writeln('Notification toNotification() {'); | 855 TypeDefinition referencedDefinition = api.types[type.typeName]; |
641 indent(() { | 856 if (referencedDefinition != null) { |
642 String eventString = | 857 TypeDecl referencedType = referencedDefinition.type; |
643 literalString((impliedType.apiNode as Notification).longEvent); | 858 if (referencedType is TypeObject || referencedType is TypeEnum) { |
644 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 859 return new FromJsonSnippet((String jsonPath, String json) { |
645 writeln('return new Notification($eventString, $jsonPart);'); | 860 String typeName = dartType(type); |
646 }); | 861 if (typeName == 'RefactoringFeedback') { |
647 writeln('}'); | 862 return |
| 863 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)'; |
| 864 } else if (typeName == 'RefactoringOptions') { |
| 865 return |
| 866 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)'; |
| 867 } else { |
| 868 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)'; |
| 869 } |
| 870 }); |
| 871 } else { |
| 872 return fromJsonCode(referencedType); |
| 873 } |
| 874 } else { |
| 875 switch (type.typeName) { |
| 876 case 'String': |
| 877 return new FromJsonFunction('jsonDecoder._decodeString'); |
| 878 case 'bool': |
| 879 return new FromJsonFunction('jsonDecoder._decodeBool'); |
| 880 case 'int': |
| 881 case 'long': |
| 882 return new FromJsonFunction('jsonDecoder._decodeInt'); |
| 883 case 'object': |
| 884 return new FromJsonIdentity(); |
| 885 default: |
| 886 throw new Exception('Unexpected type name ${type.typeName}'); |
| 887 } |
| 888 } |
| 889 } else if (type is TypeMap) { |
| 890 FromJsonCode keyCode; |
| 891 if (dartType(type.keyType) != 'String') { |
| 892 keyCode = fromJsonCode(type.keyType); |
| 893 } else { |
| 894 keyCode = new FromJsonIdentity(); |
| 895 } |
| 896 FromJsonCode valueCode = fromJsonCode(type.valueType); |
| 897 if (keyCode.isIdentity && valueCode.isIdentity) { |
| 898 return new FromJsonFunction('jsonDecoder._decodeMap'); |
| 899 } else { |
| 900 return new FromJsonSnippet((String jsonPath, String json) { |
| 901 StringBuffer result = new StringBuffer(); |
| 902 result.write('jsonDecoder._decodeMap($jsonPath, $json'); |
| 903 if (!keyCode.isIdentity) { |
| 904 result.write(', keyDecoder: ${keyCode.asClosure}'); |
| 905 } |
| 906 if (!valueCode.isIdentity) { |
| 907 result.write(', valueDecoder: ${valueCode.asClosure}'); |
| 908 } |
| 909 result.write(')'); |
| 910 return result.toString(); |
| 911 }); |
| 912 } |
| 913 } else if (type is TypeList) { |
| 914 FromJsonCode itemCode = fromJsonCode(type.itemType); |
| 915 if (itemCode.isIdentity) { |
| 916 return new FromJsonFunction('jsonDecoder._decodeList'); |
| 917 } else { |
| 918 return new FromJsonSnippet( |
| 919 (String jsonPath, String json) => |
| 920 'jsonDecoder._decodeList($jsonPath, $json, ${itemCode.asClosure}
)'); |
| 921 } |
| 922 } else if (type is TypeUnion) { |
| 923 List<String> decoders = <String>[]; |
| 924 for (TypeDecl choice in type.choices) { |
| 925 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); |
| 926 if (resolvedChoice is TypeObject) { |
| 927 TypeObjectField field = resolvedChoice.getField(type.field); |
| 928 if (field == null) { |
| 929 throw new Exception( |
| 930 'Each choice in the union needs a field named ${type.field}'); |
| 931 } |
| 932 if (field.value == null) { |
| 933 throw new Exception( |
| 934 'Each choice in the union needs a constant value for the field $
{type.field}'); |
| 935 } |
| 936 String closure = fromJsonCode(choice).asClosure; |
| 937 decoders.add('${literalString(field.value)}: $closure'); |
| 938 } else { |
| 939 throw new Exception('Union types must be unions of objects.'); |
| 940 } |
| 941 } |
| 942 return new FromJsonSnippet( |
| 943 (String jsonPath, String json) => |
| 944 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.f
ield)}, {${decoders.join(', ')}})'); |
| 945 } else { |
| 946 throw new Exception("Can't convert $type from JSON"); |
| 947 } |
| 948 } |
| 949 |
| 950 /** |
| 951 * True if the constructor argument for the given field should be optional. |
| 952 */ |
| 953 bool isOptionalConstructorArg(String className, TypeObjectField field) { |
| 954 if (field.optional) { |
| 955 return true; |
| 956 } |
| 957 List<String> forceOptional = _optionalConstructorArguments[className]; |
| 958 if (forceOptional != null && forceOptional.contains(field.name)) { |
648 return true; | 959 return true; |
649 } | 960 } |
650 return false; | 961 return false; |
651 } | 962 } |
652 | 963 |
653 /** | 964 /** |
654 * Emit the operator== code for an object class. | 965 * Create a string literal that evaluates to [s]. |
655 */ | 966 */ |
656 void emitObjectEqualsMember(TypeObject type, String className) { | 967 String literalString(String s) { |
657 writeln('@override'); | 968 return JSON.encode(s); |
658 writeln('bool operator==(other) {'); | |
659 indent(() { | |
660 writeln('if (other is $className) {'); | |
661 indent(() { | |
662 var comparisons = <String>[]; | |
663 if (type != null) { | |
664 for (TypeObjectField field in type.fields) { | |
665 if (field.value != null) { | |
666 continue; | |
667 } | |
668 comparisons.add( | |
669 compareEqualsCode(field.type, field.name, 'other.${field.name}')
); | |
670 } | |
671 } | |
672 if (comparisons.isEmpty) { | |
673 writeln('return true;'); | |
674 } else { | |
675 String concatenated = comparisons.join(' &&\n '); | |
676 writeln('return $concatenated;'); | |
677 } | |
678 }); | |
679 writeln('}'); | |
680 writeln('return false;'); | |
681 }); | |
682 writeln('}'); | |
683 } | |
684 | |
685 /** | |
686 * Emit the hashCode getter for an object class. | |
687 */ | |
688 void emitObjectHashCode(TypeObject type, String className) { | |
689 writeln('@override'); | |
690 writeln('int get hashCode {'); | |
691 indent(() { | |
692 if (type == null) { | |
693 writeln('return ${className.hashCode};'); | |
694 } else { | |
695 writeln('int hash = 0;'); | |
696 for (TypeObjectField field in type.fields) { | |
697 String valueToCombine; | |
698 if (field.value != null) { | |
699 valueToCombine = field.value.hashCode.toString(); | |
700 } else { | |
701 valueToCombine = '${field.name}.hashCode'; | |
702 } | |
703 writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); | |
704 } | |
705 writeln('return _JenkinsSmiHash.finish(hash);'); | |
706 } | |
707 }); | |
708 writeln('}'); | |
709 } | |
710 | |
711 /** | |
712 * Emit a class to encapsulate an enum. | |
713 */ | |
714 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { | |
715 docComment(toHtmlVisitor.collectHtml(() { | |
716 toHtmlVisitor.p(() { | |
717 toHtmlVisitor.write(impliedType.humanReadableName); | |
718 }); | |
719 if (impliedType.type != null) { | |
720 toHtmlVisitor.showType(null, impliedType.type); | |
721 } | |
722 })); | |
723 writeln('class $className implements Enum {'); | |
724 indent(() { | |
725 if (emitSpecialStaticMembers(className)) { | |
726 writeln(); | |
727 } | |
728 for (TypeEnumValue value in type.values) { | |
729 docComment(toHtmlVisitor.collectHtml(() { | |
730 toHtmlVisitor.translateHtml(value.html); | |
731 })); | |
732 String valueString = literalString(value.value); | |
733 writeln( | |
734 'static const ${value.value} = const $className._($valueString);'); | |
735 writeln(); | |
736 } | |
737 writeln('final String name;'); | |
738 writeln(); | |
739 writeln('const $className._(this.name);'); | |
740 writeln(); | |
741 emitEnumClassConstructor(className, type); | |
742 writeln(); | |
743 emitEnumFromJsonConstructor(className, type, impliedType); | |
744 writeln(); | |
745 if (emitSpecialConstructors(className)) { | |
746 writeln(); | |
747 } | |
748 if (emitSpecialGetters(className)) { | |
749 writeln(); | |
750 } | |
751 if (emitSpecialMethods(className)) { | |
752 writeln(); | |
753 } | |
754 writeln('@override'); | |
755 writeln('String toString() => "$className.\$name";'); | |
756 writeln(); | |
757 writeln('String toJson() => name;'); | |
758 }); | |
759 writeln('}'); | |
760 } | |
761 | |
762 /** | |
763 * Emit the constructor for an enum class. | |
764 */ | |
765 void emitEnumClassConstructor(String className, TypeEnum type) { | |
766 writeln('factory $className(String name) {'); | |
767 indent(() { | |
768 writeln('switch (name) {'); | |
769 indent(() { | |
770 for (TypeEnumValue value in type.values) { | |
771 String valueString = literalString(value.value); | |
772 writeln('case $valueString:'); | |
773 indent(() { | |
774 writeln('return ${value.value};'); | |
775 }); | |
776 } | |
777 }); | |
778 writeln('}'); | |
779 writeln(r"throw new Exception('Illegal enum value: $name');"); | |
780 }); | |
781 writeln('}'); | |
782 } | 969 } |
783 | 970 |
784 /** | 971 /** |
785 * Compute the code necessary to convert [type] to JSON. | 972 * Compute the code necessary to convert [type] to JSON. |
786 */ | 973 */ |
787 ToJsonCode toJsonCode(TypeDecl type) { | 974 ToJsonCode toJsonCode(TypeDecl type) { |
788 TypeDecl resolvedType = resolveTypeReferenceChain(type); | 975 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
789 if (resolvedType is TypeReference) { | 976 if (resolvedType is TypeReference) { |
790 return new ToJsonIdentity(dartType(type)); | 977 return new ToJsonIdentity(dartType(type)); |
791 } else if (resolvedType is TypeList) { | 978 } else if (resolvedType is TypeList) { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
832 (String value) => '$value.toJson()'); | 1019 (String value) => '$value.toJson()'); |
833 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { | 1020 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { |
834 return new ToJsonSnippet( | 1021 return new ToJsonSnippet( |
835 dartType(type), | 1022 dartType(type), |
836 (String value) => '$value.toJson()'); | 1023 (String value) => '$value.toJson()'); |
837 } else { | 1024 } else { |
838 throw new Exception("Can't convert $resolvedType from JSON"); | 1025 throw new Exception("Can't convert $resolvedType from JSON"); |
839 } | 1026 } |
840 } | 1027 } |
841 | 1028 |
842 /** | 1029 @override |
843 * Compute the code necessary to compare two objects for equality. | 1030 visitApi() { |
844 */ | 1031 outputHeader(); |
845 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) { | 1032 writeln(); |
846 TypeDecl resolvedType = resolveTypeReferenceChain(type); | 1033 writeln('part of protocol;'); |
847 if (resolvedType is TypeReference || | 1034 emitClasses(); |
848 resolvedType is TypeEnum || | |
849 resolvedType is TypeObject || | |
850 resolvedType is TypeUnion) { | |
851 return '$thisVar == $otherVar'; | |
852 } else if (resolvedType is TypeList) { | |
853 String itemTypeName = dartType(resolvedType.itemType); | |
854 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b'); | |
855 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison'; | |
856 return '_listEqual($thisVar, $otherVar, $closure)'; | |
857 } else if (resolvedType is TypeMap) { | |
858 String valueTypeName = dartType(resolvedType.valueType); | |
859 String subComparison = | |
860 compareEqualsCode(resolvedType.valueType, 'a', 'b'); | |
861 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison'; | |
862 return '_mapEqual($thisVar, $otherVar, $closure)'; | |
863 } | |
864 throw new Exception( | |
865 "Don't know how to compare for equality: $resolvedType"); | |
866 } | |
867 | |
868 /** | |
869 * Emit the method for decoding an object from JSON. | |
870 */ | |
871 void emitObjectFromJsonConstructor(String className, TypeObject type, | |
872 ImpliedType impliedType) { | |
873 String humanReadableNameString = | |
874 literalString(impliedType.humanReadableName); | |
875 if (className == 'RefactoringFeedback') { | |
876 writeln( | |
877 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, ' | |
878 'String jsonPath, Object json, Map responseJson) {'); | |
879 indent(() { | |
880 writeln( | |
881 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, ' | |
882 'json, responseJson);'); | |
883 }); | |
884 writeln('}'); | |
885 return; | |
886 } | |
887 if (className == 'RefactoringOptions') { | |
888 writeln( | |
889 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, ' | |
890 'String jsonPath, Object json, RefactoringKind kind) {'); | |
891 indent(() { | |
892 writeln( | |
893 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json,
kind);'); | |
894 }); | |
895 writeln('}'); | |
896 return; | |
897 } | |
898 writeln( | |
899 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); | |
900 indent(() { | |
901 writeln('if (json == null) {'); | |
902 indent(() { | |
903 writeln('json = {};'); | |
904 }); | |
905 writeln('}'); | |
906 writeln('if (json is Map) {'); | |
907 indent(() { | |
908 List<String> args = <String>[]; | |
909 List<String> optionalArgs = <String>[]; | |
910 for (TypeObjectField field in type.fields) { | |
911 String fieldNameString = literalString(field.name); | |
912 String fieldAccessor = 'json[$fieldNameString]'; | |
913 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; | |
914 if (field.value != null) { | |
915 String valueString = literalString(field.value); | |
916 writeln('if ($fieldAccessor != $valueString) {'); | |
917 indent(() { | |
918 writeln( | |
919 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString)
;'); | |
920 }); | |
921 writeln('}'); | |
922 continue; | |
923 } | |
924 if (isOptionalConstructorArg(className, field)) { | |
925 optionalArgs.add('${field.name}: ${field.name}'); | |
926 } else { | |
927 args.add(field.name); | |
928 } | |
929 TypeDecl fieldType = field.type; | |
930 String fieldDartType = dartType(fieldType); | |
931 writeln('$fieldDartType ${field.name};'); | |
932 writeln('if (json.containsKey($fieldNameString)) {'); | |
933 indent(() { | |
934 String fromJson = | |
935 fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor); | |
936 writeln('${field.name} = $fromJson;'); | |
937 }); | |
938 write('}'); | |
939 if (!field.optional) { | |
940 writeln(' else {'); | |
941 indent(() { | |
942 writeln( | |
943 "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);"); | |
944 }); | |
945 writeln('}'); | |
946 } else { | |
947 writeln(); | |
948 } | |
949 } | |
950 args.addAll(optionalArgs); | |
951 writeln('return new $className(${args.join(', ')});'); | |
952 }); | |
953 writeln('} else {'); | |
954 indent(() { | |
955 writeln( | |
956 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); | |
957 }); | |
958 writeln('}'); | |
959 }); | |
960 writeln('}'); | |
961 } | |
962 | |
963 /** | |
964 * Emit the method for decoding an enum from JSON. | |
965 */ | |
966 void emitEnumFromJsonConstructor(String className, TypeEnum type, | |
967 ImpliedType impliedType) { | |
968 writeln( | |
969 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); | |
970 indent(() { | |
971 writeln('if (json is String) {'); | |
972 indent(() { | |
973 writeln('try {'); | |
974 indent(() { | |
975 writeln('return new $className(json);'); | |
976 }); | |
977 writeln('} catch(_) {'); | |
978 indent(() { | |
979 writeln('// Fall through'); | |
980 }); | |
981 writeln('}'); | |
982 }); | |
983 writeln('}'); | |
984 String humanReadableNameString = | |
985 literalString(impliedType.humanReadableName); | |
986 writeln( | |
987 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); | |
988 }); | |
989 writeln('}'); | |
990 } | |
991 | |
992 /** | |
993 * Compute the code necessary to translate [type] from JSON. | |
994 */ | |
995 FromJsonCode fromJsonCode(TypeDecl type) { | |
996 if (type is TypeReference) { | |
997 TypeDefinition referencedDefinition = api.types[type.typeName]; | |
998 if (referencedDefinition != null) { | |
999 TypeDecl referencedType = referencedDefinition.type; | |
1000 if (referencedType is TypeObject || referencedType is TypeEnum) { | |
1001 return new FromJsonSnippet((String jsonPath, String json) { | |
1002 String typeName = dartType(type); | |
1003 if (typeName == 'RefactoringFeedback') { | |
1004 return | |
1005 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)'; | |
1006 } else if (typeName == 'RefactoringOptions') { | |
1007 return | |
1008 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)'; | |
1009 } else { | |
1010 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)'; | |
1011 } | |
1012 }); | |
1013 } else { | |
1014 return fromJsonCode(referencedType); | |
1015 } | |
1016 } else { | |
1017 switch (type.typeName) { | |
1018 case 'String': | |
1019 return new FromJsonFunction('jsonDecoder._decodeString'); | |
1020 case 'bool': | |
1021 return new FromJsonFunction('jsonDecoder._decodeBool'); | |
1022 case 'int': | |
1023 case 'long': | |
1024 return new FromJsonFunction('jsonDecoder._decodeInt'); | |
1025 case 'object': | |
1026 return new FromJsonIdentity(); | |
1027 default: | |
1028 throw new Exception('Unexpected type name ${type.typeName}'); | |
1029 } | |
1030 } | |
1031 } else if (type is TypeMap) { | |
1032 FromJsonCode keyCode; | |
1033 if (dartType(type.keyType) != 'String') { | |
1034 keyCode = fromJsonCode(type.keyType); | |
1035 } else { | |
1036 keyCode = new FromJsonIdentity(); | |
1037 } | |
1038 FromJsonCode valueCode = fromJsonCode(type.valueType); | |
1039 if (keyCode.isIdentity && valueCode.isIdentity) { | |
1040 return new FromJsonFunction('jsonDecoder._decodeMap'); | |
1041 } else { | |
1042 return new FromJsonSnippet((String jsonPath, String json) { | |
1043 StringBuffer result = new StringBuffer(); | |
1044 result.write('jsonDecoder._decodeMap($jsonPath, $json'); | |
1045 if (!keyCode.isIdentity) { | |
1046 result.write(', keyDecoder: ${keyCode.asClosure}'); | |
1047 } | |
1048 if (!valueCode.isIdentity) { | |
1049 result.write(', valueDecoder: ${valueCode.asClosure}'); | |
1050 } | |
1051 result.write(')'); | |
1052 return result.toString(); | |
1053 }); | |
1054 } | |
1055 } else if (type is TypeList) { | |
1056 FromJsonCode itemCode = fromJsonCode(type.itemType); | |
1057 if (itemCode.isIdentity) { | |
1058 return new FromJsonFunction('jsonDecoder._decodeList'); | |
1059 } else { | |
1060 return new FromJsonSnippet( | |
1061 (String jsonPath, String json) => | |
1062 'jsonDecoder._decodeList($jsonPath, $json, ${itemCode.asClosure}
)'); | |
1063 } | |
1064 } else if (type is TypeUnion) { | |
1065 List<String> decoders = <String>[]; | |
1066 for (TypeDecl choice in type.choices) { | |
1067 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); | |
1068 if (resolvedChoice is TypeObject) { | |
1069 TypeObjectField field = resolvedChoice.getField(type.field); | |
1070 if (field == null) { | |
1071 throw new Exception( | |
1072 'Each choice in the union needs a field named ${type.field}'); | |
1073 } | |
1074 if (field.value == null) { | |
1075 throw new Exception( | |
1076 'Each choice in the union needs a constant value for the field $
{type.field}'); | |
1077 } | |
1078 String closure = fromJsonCode(choice).asClosure; | |
1079 decoders.add('${literalString(field.value)}: $closure'); | |
1080 } else { | |
1081 throw new Exception('Union types must be unions of objects.'); | |
1082 } | |
1083 } | |
1084 return new FromJsonSnippet( | |
1085 (String jsonPath, String json) => | |
1086 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.f
ield)}, {${decoders.join(', ')}})'); | |
1087 } else { | |
1088 throw new Exception("Can't convert $type from JSON"); | |
1089 } | |
1090 } | |
1091 | |
1092 /** | |
1093 * Emit a convenience constructor for decoding a piece of protocol, if | |
1094 * appropriate. Return true if a constructor was emitted. | |
1095 */ | |
1096 bool emitConvenienceConstructor(String className, ImpliedType impliedType) { | |
1097 // The type of object from which this piece of protocol should be decoded. | |
1098 String inputType; | |
1099 // The name of the input object. | |
1100 String inputName; | |
1101 // The field within the input object to decode. | |
1102 String fieldName; | |
1103 // Constructor call to create the JsonDecoder object. | |
1104 String makeDecoder; | |
1105 // Name of the constructor to create. | |
1106 String constructorName; | |
1107 // Extra arguments for the constructor. | |
1108 List<String> extraArgs = <String>[]; | |
1109 switch (impliedType.kind) { | |
1110 case 'requestParams': | |
1111 inputType = 'Request'; | |
1112 inputName = 'request'; | |
1113 fieldName = '_params'; | |
1114 makeDecoder = 'new RequestDecoder(request)'; | |
1115 constructorName = 'fromRequest'; | |
1116 break; | |
1117 case 'requestResult': | |
1118 inputType = 'Response'; | |
1119 inputName = 'response'; | |
1120 fieldName = '_result'; | |
1121 makeDecoder = | |
1122 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id
))'; | |
1123 constructorName = 'fromResponse'; | |
1124 break; | |
1125 case 'notificationParams': | |
1126 inputType = 'Notification'; | |
1127 inputName = 'notification'; | |
1128 fieldName = '_params'; | |
1129 makeDecoder = 'new ResponseDecoder(null)'; | |
1130 constructorName = 'fromNotification'; | |
1131 break; | |
1132 case 'refactoringOptions': | |
1133 inputType = 'EditGetRefactoringParams'; | |
1134 inputName = 'refactoringParams'; | |
1135 fieldName = 'options'; | |
1136 makeDecoder = 'new RequestDecoder(request)'; | |
1137 constructorName = 'fromRefactoringParams'; | |
1138 extraArgs.add('Request request'); | |
1139 break; | |
1140 default: | |
1141 return false; | |
1142 } | |
1143 List<String> args = ['$inputType $inputName']; | |
1144 args.addAll(extraArgs); | |
1145 writeln('factory $className.$constructorName(${args.join(', ')}) {'); | |
1146 indent(() { | |
1147 String fieldNameString = | |
1148 literalString(fieldName.replaceFirst(new RegExp('^_'), '')); | |
1149 if (className == 'EditGetRefactoringParams') { | |
1150 writeln('var params = new $className.fromJson('); | |
1151 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); | |
1152 writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;'); | |
1153 writeln('return params;'); | |
1154 } else { | |
1155 writeln('return new $className.fromJson('); | |
1156 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); | |
1157 } | |
1158 }); | |
1159 writeln('}'); | |
1160 return true; | |
1161 } | |
1162 | |
1163 /** | |
1164 * Create a string literal that evaluates to [s]. | |
1165 */ | |
1166 String literalString(String s) { | |
1167 return JSON.encode(s); | |
1168 } | 1035 } |
1169 } | 1036 } |
1170 | 1037 |
1171 final GeneratedFile target = | 1038 /** |
1172 new GeneratedFile('../../lib/src/generated_protocol.dart', () { | 1039 * Container for code that can be used to translate a data type from JSON. |
1173 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); | 1040 */ |
1174 return visitor.collectCode(visitor.visitApi); | 1041 abstract class FromJsonCode { |
1175 }); | 1042 /** |
| 1043 * Get the translation code in the form of a closure. |
| 1044 */ |
| 1045 String get asClosure; |
| 1046 |
| 1047 /** |
| 1048 * True if the data type is already in JSON form, so the translation is the |
| 1049 * identity function. |
| 1050 */ |
| 1051 bool get isIdentity; |
| 1052 |
| 1053 /** |
| 1054 * Get the translation code in the form of a code snippet, where [jsonPath] |
| 1055 * is the variable holding the JSON path, and [json] is the variable holding |
| 1056 * the raw JSON. |
| 1057 */ |
| 1058 String asSnippet(String jsonPath, String json); |
| 1059 } |
1176 | 1060 |
1177 /** | 1061 /** |
1178 * Translate spec_input.html into protocol_matchers.dart. | 1062 * Representation of FromJsonCode for a function defined elsewhere. |
1179 */ | 1063 */ |
1180 main() { | 1064 class FromJsonFunction extends FromJsonCode { |
1181 target.generate(); | 1065 final String asClosure; |
| 1066 |
| 1067 FromJsonFunction(this.asClosure); |
| 1068 |
| 1069 @override |
| 1070 bool get isIdentity => false; |
| 1071 |
| 1072 @override |
| 1073 String asSnippet(String jsonPath, String json) => |
| 1074 '$asClosure($jsonPath, $json)'; |
1182 } | 1075 } |
| 1076 |
| 1077 /** |
| 1078 * Representation of FromJsonCode for the identity transformation. |
| 1079 */ |
| 1080 class FromJsonIdentity extends FromJsonSnippet { |
| 1081 FromJsonIdentity() : super((String jsonPath, String json) => json); |
| 1082 |
| 1083 @override |
| 1084 bool get isIdentity => true; |
| 1085 } |
| 1086 |
| 1087 /** |
| 1088 * Representation of FromJsonCode for a snippet of inline code. |
| 1089 */ |
| 1090 class FromJsonSnippet extends FromJsonCode { |
| 1091 /** |
| 1092 * Callback that can be used to generate the code snippet, once the names |
| 1093 * of the [jsonPath] and [json] variables are known. |
| 1094 */ |
| 1095 final FromJsonSnippetCallback callback; |
| 1096 |
| 1097 FromJsonSnippet(this.callback); |
| 1098 |
| 1099 @override |
| 1100 String get asClosure => |
| 1101 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; |
| 1102 |
| 1103 @override |
| 1104 bool get isIdentity => false; |
| 1105 |
| 1106 @override |
| 1107 String asSnippet(String jsonPath, String json) => callback(jsonPath, json); |
| 1108 } |
| 1109 |
| 1110 /** |
| 1111 * Container for code that can be used to translate a data type to JSON. |
| 1112 */ |
| 1113 abstract class ToJsonCode { |
| 1114 /** |
| 1115 * Get the translation code in the form of a closure. |
| 1116 */ |
| 1117 String get asClosure; |
| 1118 |
| 1119 /** |
| 1120 * True if the data type is already in JSON form, so the translation is the |
| 1121 * identity function. |
| 1122 */ |
| 1123 bool get isIdentity; |
| 1124 |
| 1125 /** |
| 1126 * Get the translation code in the form of a code snippet, where [value] |
| 1127 * is the variable holding the object to be translated. |
| 1128 */ |
| 1129 String asSnippet(String value); |
| 1130 } |
| 1131 |
| 1132 /** |
| 1133 * Representation of ToJsonCode for a function defined elsewhere. |
| 1134 */ |
| 1135 class ToJsonFunction extends ToJsonCode { |
| 1136 final String asClosure; |
| 1137 |
| 1138 ToJsonFunction(this.asClosure); |
| 1139 |
| 1140 @override |
| 1141 bool get isIdentity => false; |
| 1142 |
| 1143 @override |
| 1144 String asSnippet(String value) => '$asClosure($value)'; |
| 1145 } |
| 1146 |
| 1147 /** |
| 1148 * Representation of FromJsonCode for the identity transformation. |
| 1149 */ |
| 1150 class ToJsonIdentity extends ToJsonSnippet { |
| 1151 ToJsonIdentity(String type) : super(type, (String value) => value); |
| 1152 |
| 1153 @override |
| 1154 bool get isIdentity => true; |
| 1155 } |
| 1156 |
| 1157 /** |
| 1158 * Representation of ToJsonCode for a snippet of inline code. |
| 1159 */ |
| 1160 class ToJsonSnippet extends ToJsonCode { |
| 1161 /** |
| 1162 * Callback that can be used to generate the code snippet, once the name |
| 1163 * of the [value] variable is known. |
| 1164 */ |
| 1165 final ToJsonSnippetCallback callback; |
| 1166 |
| 1167 /** |
| 1168 * Dart type of the [value] variable. |
| 1169 */ |
| 1170 final String type; |
| 1171 |
| 1172 ToJsonSnippet(this.type, this.callback); |
| 1173 |
| 1174 @override |
| 1175 String get asClosure => '($type value) => ${callback('value')}'; |
| 1176 |
| 1177 @override |
| 1178 bool get isIdentity => false; |
| 1179 |
| 1180 @override |
| 1181 String asSnippet(String value) => callback(value); |
| 1182 } |
OLD | NEW |