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

Side by Side Diff: pkg/analyzer_plugin/tool/spec/from_html.dart

Issue 2664213003: Add the generator and the generated files (Closed)
Patch Set: add missed files Created 3 years, 10 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
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.
4
5 /**
6 * Code for reading an HTML API description.
7 */
8 import 'dart:io';
9
10 import 'package:analyzer/src/codegen/html.dart';
11 import 'package:html/dom.dart' as dom;
12 import 'package:html/parser.dart' as parser;
13 import 'package:path/path.dart';
14
15 import 'api.dart';
16
17 const List<String> specialElements = const [
18 'domain',
19 'feedback',
20 'object',
21 'refactorings',
22 'refactoring',
23 'type',
24 'types',
25 'request',
26 'notification',
27 'params',
28 'result',
29 'field',
30 'list',
31 'map',
32 'enum',
33 'key',
34 'value',
35 'options',
36 'ref',
37 'code',
38 'version',
39 'union',
40 'index'
41 ];
42
43 /**
44 * Create an [Api] object from an HTML representation such as:
45 *
46 * <html>
47 * ...
48 * <body>
49 * ... <version>1.0</version> ...
50 * <domain name="...">...</domain> <!-- zero or more -->
51 * <types>...</types>
52 * <refactorings>...</refactorings>
53 * </body>
54 * </html>
55 *
56 * Child elements of <api> can occur in any order.
57 */
58 Api apiFromHtml(dom.Element html) {
59 Api api;
60 List<String> versions = <String>[];
61 List<Domain> domains = <Domain>[];
62 Types types = null;
63 Refactorings refactorings = null;
64 recurse(html, 'api', {
65 'domain': (dom.Element element) {
66 domains.add(domainFromHtml(element));
67 },
68 'refactorings': (dom.Element element) {
69 refactorings = refactoringsFromHtml(element);
70 },
71 'types': (dom.Element element) {
72 types = typesFromHtml(element);
73 },
74 'version': (dom.Element element) {
75 versions.add(innerText(element));
76 },
77 'index': (dom.Element element) {
78 /* Ignore; generated dynamically. */
79 }
80 });
81 if (versions.length != 1) {
82 throw new Exception('The API must contain exactly one <version> element');
83 }
84 api = new Api(versions[0], domains, types, refactorings, html);
85 return api;
86 }
87
88 /**
89 * Check that the given [element] has all of the attributes in
90 * [requiredAttributes], possibly some of the attributes in
91 * [optionalAttributes], and no others.
92 */
93 void checkAttributes(
94 dom.Element element, List<String> requiredAttributes, String context,
95 {List<String> optionalAttributes: const []}) {
96 Set<String> attributesFound = new Set<String>();
97 element.attributes.forEach((String name, String value) {
98 if (!requiredAttributes.contains(name) &&
99 !optionalAttributes.contains(name)) {
100 throw new Exception(
101 '$context: Unexpected attribute in ${element.localName}: $name');
102 }
103 attributesFound.add(name);
104 });
105 for (String expectedAttribute in requiredAttributes) {
106 if (!attributesFound.contains(expectedAttribute)) {
107 throw new Exception(
108 '$context: ${element.localName} must contain attribute $expectedAttrib ute');
109 }
110 }
111 }
112
113 /**
114 * Check that the given [element] has the given [expectedName].
115 */
116 void checkName(dom.Element element, String expectedName, [String context]) {
117 if (element.localName != expectedName) {
118 if (context == null) {
119 context = element.localName;
120 }
121 throw new Exception(
122 '$context: Expected $expectedName, found ${element.localName}');
123 }
124 }
125
126 /**
127 * Create a [Domain] object from an HTML representation such as:
128 *
129 * <domain name="domainName">
130 * <request method="...">...</request> <!-- zero or more -->
131 * <notification event="...">...</notification> <!-- zero or more -->
132 * </domain>
133 *
134 * Child elements can occur in any order.
135 */
136 Domain domainFromHtml(dom.Element html) {
137 checkName(html, 'domain');
138 String name = html.attributes['name'];
139 String context = name ?? 'domain';
140 bool experimental = html.attributes['experimental'] == 'true';
141 checkAttributes(html, ['name'], context,
142 optionalAttributes: ['experimental']);
143 List<Request> requests = <Request>[];
144 List<Notification> notifications = <Notification>[];
145 recurse(html, context, {
146 'request': (dom.Element child) {
147 requests.add(requestFromHtml(child, context));
148 },
149 'notification': (dom.Element child) {
150 notifications.add(notificationFromHtml(child, context));
151 }
152 });
153 return new Domain(name, requests, notifications, html,
154 experimental: experimental);
155 }
156
157 dom.Element getAncestor(dom.Element html, String name, String context) {
158 dom.Element ancestor = html.parent;
159 while (ancestor != null) {
160 if (ancestor.localName == name) {
161 return ancestor;
162 }
163 ancestor = ancestor.parent;
164 }
165 throw new Exception(
166 '$context: <${html.localName}> must be nested within <$name>');
167 }
168
169 /**
170 * Create a [Notification] object from an HTML representation such as:
171 *
172 * <notification event="methodName">
173 * <params>...</params> <!-- optional -->
174 * </notification>
175 *
176 * Note that the event name should not include the domain name.
177 *
178 * <params> has the same form as <object>, as described in [typeDeclFromHtml].
179 *
180 * Child elements can occur in any order.
181 */
182 Notification notificationFromHtml(dom.Element html, String context) {
183 String domainName = getAncestor(html, 'domain', context).attributes['name'];
184 checkName(html, 'notification', context);
185 String event = html.attributes['event'];
186 context = '$context.${event != null ? event : 'event'}';
187 checkAttributes(html, ['event'], context);
188 TypeDecl params;
189 recurse(html, context, {
190 'params': (dom.Element child) {
191 params = typeObjectFromHtml(child, '$context.params');
192 }
193 });
194 return new Notification(domainName, event, params, html);
195 }
196
197 /**
198 * Create a single of [TypeDecl] corresponding to the type defined inside the
199 * given HTML element.
200 */
201 TypeDecl processContentsAsType(dom.Element html, String context) {
202 List<TypeDecl> types = processContentsAsTypes(html, context);
203 if (types.length != 1) {
204 throw new Exception('$context: Exactly one type must be specified');
205 }
206 return types[0];
207 }
208
209 /**
210 * Create a list of [TypeDecl]s corresponding to the types defined inside the
211 * given HTML element. The following forms are supported.
212 *
213 * To refer to a type declared elsewhere (or a built-in type):
214 *
215 * <ref>typeName</ref>
216 *
217 * For a list: <list>ItemType</list>
218 *
219 * For a map: <map><key>KeyType</key><value>ValueType</value></map>
220 *
221 * For a JSON object:
222 *
223 * <object>
224 * <field name="...">...</field> <!-- zero or more -->
225 * </object>
226 *
227 * For an enum:
228 *
229 * <enum>
230 * <value>...</value> <!-- zero or more -->
231 * </enum>
232 *
233 * For a union type:
234 * <union>
235 * TYPE <!-- zero or more -->
236 * </union>
237 */
238 List<TypeDecl> processContentsAsTypes(dom.Element html, String context) {
239 List<TypeDecl> types = <TypeDecl>[];
240 recurse(html, context, {
241 'object': (dom.Element child) {
242 types.add(typeObjectFromHtml(child, context));
243 },
244 'list': (dom.Element child) {
245 checkAttributes(child, [], context);
246 types.add(new TypeList(processContentsAsType(child, context), child));
247 },
248 'map': (dom.Element child) {
249 checkAttributes(child, [], context);
250 TypeDecl keyType;
251 TypeDecl valueType;
252 recurse(child, context, {
253 'key': (dom.Element child) {
254 if (keyType != null) {
255 throw new Exception('$context: Key type already specified');
256 }
257 keyType = processContentsAsType(child, '$context.key');
258 },
259 'value': (dom.Element child) {
260 if (valueType != null) {
261 throw new Exception('$context: Value type already specified');
262 }
263 valueType = processContentsAsType(child, '$context.value');
264 }
265 });
266 if (keyType == null) {
267 throw new Exception('$context: Key type not specified');
268 }
269 if (valueType == null) {
270 throw new Exception('$context: Value type not specified');
271 }
272 types.add(new TypeMap(keyType, valueType, child));
273 },
274 'enum': (dom.Element child) {
275 types.add(typeEnumFromHtml(child, context));
276 },
277 'ref': (dom.Element child) {
278 checkAttributes(child, [], context);
279 types.add(new TypeReference(innerText(child), child));
280 },
281 'union': (dom.Element child) {
282 checkAttributes(child, ['field'], context);
283 String field = child.attributes['field'];
284 types.add(
285 new TypeUnion(processContentsAsTypes(child, context), field, child));
286 }
287 });
288 return types;
289 }
290
291 /**
292 * Read the API description from the file 'spec_input.html'. [pkgPath] is the
293 * path to the current package.
294 */
295 Api readApi(String pkgPath) {
296 File htmlFile = new File(join(pkgPath, 'tool', 'spec', 'plugin_spec.html'));
297 String htmlContents = htmlFile.readAsStringSync();
298 dom.Document document = parser.parse(htmlContents);
299 dom.Element htmlElement = document.children
300 .singleWhere((element) => element.localName.toLowerCase() == 'html');
301 return apiFromHtml(htmlElement);
302 }
303
304 void recurse(dom.Element parent, String context,
305 Map<String, ElementProcessor> elementProcessors) {
306 for (String key in elementProcessors.keys) {
307 if (!specialElements.contains(key)) {
308 throw new Exception('$context: $key is not a special element');
309 }
310 }
311 for (dom.Node node in parent.nodes) {
312 if (node is dom.Element) {
313 if (elementProcessors.containsKey(node.localName)) {
314 elementProcessors[node.localName](node);
315 } else if (specialElements.contains(node.localName)) {
316 throw new Exception('$context: Unexpected use of <${node.localName}>');
317 } else {
318 recurse(node, context, elementProcessors);
319 }
320 }
321 }
322 }
323
324 /**
325 * Create a [Refactoring] object from an HTML representation such as:
326 *
327 * <refactoring kind="refactoringKind">
328 * <feedback>...</feedback> <!-- optional -->
329 * <options>...</options> <!-- optional -->
330 * </refactoring>
331 *
332 * <feedback> and <options> have the same form as <object>, as described in
333 * [typeDeclFromHtml].
334 *
335 * Child elements can occur in any order.
336 */
337 Refactoring refactoringFromHtml(dom.Element html) {
338 checkName(html, 'refactoring');
339 String kind = html.attributes['kind'];
340 String context = kind != null ? kind : 'refactoring';
341 checkAttributes(html, ['kind'], context);
342 TypeDecl feedback;
343 TypeDecl options;
344 recurse(html, context, {
345 'feedback': (dom.Element child) {
346 feedback = typeObjectFromHtml(child, '$context.feedback');
347 },
348 'options': (dom.Element child) {
349 options = typeObjectFromHtml(child, '$context.options');
350 }
351 });
352 return new Refactoring(kind, feedback, options, html);
353 }
354
355 /**
356 * Create a [Refactorings] object from an HTML representation such as:
357 *
358 * <refactorings>
359 * <refactoring kind="...">...</refactoring> <!-- zero or more -->
360 * </refactorings>
361 */
362 Refactorings refactoringsFromHtml(dom.Element html) {
363 checkName(html, 'refactorings');
364 String context = 'refactorings';
365 checkAttributes(html, [], context);
366 List<Refactoring> refactorings = <Refactoring>[];
367 recurse(html, context, {
368 'refactoring': (dom.Element child) {
369 refactorings.add(refactoringFromHtml(child));
370 }
371 });
372 return new Refactorings(refactorings, html);
373 }
374
375 /**
376 * Create a [Request] object from an HTML representation such as:
377 *
378 * <request method="methodName">
379 * <params>...</params> <!-- optional -->
380 * <result>...</result> <!-- optional -->
381 * </request>
382 *
383 * Note that the method name should not include the domain name.
384 *
385 * <params> and <result> have the same form as <object>, as described in
386 * [typeDeclFromHtml].
387 *
388 * Child elements can occur in any order.
389 */
390 Request requestFromHtml(dom.Element html, String context) {
391 String domainName = getAncestor(html, 'domain', context).attributes['name'];
392 checkName(html, 'request', context);
393 String method = html.attributes['method'];
394 context = '$context.${method != null ? method : 'method'}';
395 checkAttributes(html, ['method'], context);
396 TypeDecl params;
397 TypeDecl result;
398 recurse(html, context, {
399 'params': (dom.Element child) {
400 params = typeObjectFromHtml(child, '$context.params');
401 },
402 'result': (dom.Element child) {
403 result = typeObjectFromHtml(child, '$context.result');
404 }
405 });
406 return new Request(domainName, method, params, result, html);
407 }
408
409 /**
410 * Create a [TypeDefinition] object from an HTML representation such as:
411 *
412 * <type name="typeName">
413 * TYPE
414 * </type>
415 *
416 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
417 *
418 * Child elements can occur in any order.
419 */
420 TypeDefinition typeDefinitionFromHtml(dom.Element html) {
421 checkName(html, 'type');
422 String name = html.attributes['name'];
423 String context = name != null ? name : 'type';
424 checkAttributes(html, ['name'], context,
425 optionalAttributes: ['experimental']);
426 TypeDecl type = processContentsAsType(html, context);
427 bool experimental = html.attributes['experimental'] == 'true';
428 return new TypeDefinition(name, type, html, experimental: experimental);
429 }
430
431 /**
432 * Create a [TypeEnum] from an HTML description.
433 */
434 TypeEnum typeEnumFromHtml(dom.Element html, String context) {
435 checkName(html, 'enum', context);
436 checkAttributes(html, [], context);
437 List<TypeEnumValue> values = <TypeEnumValue>[];
438 recurse(html, context, {
439 'value': (dom.Element child) {
440 values.add(typeEnumValueFromHtml(child, context));
441 }
442 });
443 return new TypeEnum(values, html);
444 }
445
446 /**
447 * Create a [TypeEnumValue] from an HTML description such as:
448 *
449 * <enum>
450 * <code>VALUE</code>
451 * </enum>
452 *
453 * Where VALUE is the text of the enumerated value.
454 *
455 * Child elements can occur in any order.
456 */
457 TypeEnumValue typeEnumValueFromHtml(dom.Element html, String context) {
458 checkName(html, 'value', context);
459 checkAttributes(html, [], context);
460 List<String> values = <String>[];
461 recurse(html, context, {
462 'code': (dom.Element child) {
463 String text = innerText(child).trim();
464 values.add(text);
465 }
466 });
467 if (values.length != 1) {
468 throw new Exception('$context: Exactly one value must be specified');
469 }
470 return new TypeEnumValue(values[0], html);
471 }
472
473 /**
474 * Create a [TypeObjectField] from an HTML description such as:
475 *
476 * <field name="fieldName">
477 * TYPE
478 * </field>
479 *
480 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
481 *
482 * In addition, the attribute optional="true" may be used to specify that the
483 * field is optional, and the attribute value="..." may be used to specify that
484 * the field is required to have a certain value.
485 *
486 * Child elements can occur in any order.
487 */
488 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) {
489 checkName(html, 'field', context);
490 String name = html.attributes['name'];
491 context = '$context.${name != null ? name : 'field'}';
492 checkAttributes(html, ['name'], context,
493 optionalAttributes: ['optional', 'value']);
494 bool optional = false;
495 String optionalString = html.attributes['optional'];
496 if (optionalString != null) {
497 switch (optionalString) {
498 case 'true':
499 optional = true;
500 break;
501 case 'false':
502 optional = false;
503 break;
504 default:
505 throw new Exception(
506 '$context: field contains invalid "optional" attribute: "$optionalSt ring"');
507 }
508 }
509 String value = html.attributes['value'];
510 TypeDecl type = processContentsAsType(html, context);
511 return new TypeObjectField(name, type, html,
512 optional: optional, value: value);
513 }
514
515 /**
516 * Create a [TypeObject] from an HTML description.
517 */
518 TypeObject typeObjectFromHtml(dom.Element html, String context) {
519 checkAttributes(html, [], context, optionalAttributes: ['experimental']);
520 List<TypeObjectField> fields = <TypeObjectField>[];
521 recurse(html, context, {
522 'field': (dom.Element child) {
523 fields.add(typeObjectFieldFromHtml(child, context));
524 }
525 });
526 bool experimental = html.attributes['experimental'] == 'true';
527 return new TypeObject(fields, html, experimental: experimental);
528 }
529
530 /**
531 * Create a [Types] object from an HTML representation such as:
532 *
533 * <types>
534 * <type name="...">...</type> <!-- zero or more -->
535 * </types>
536 */
537 Types typesFromHtml(dom.Element html) {
538 checkName(html, 'types');
539 String context = 'types';
540 checkAttributes(html, [], context);
541 Map<String, TypeDefinition> types = <String, TypeDefinition>{};
542 recurse(html, context, {
543 'type': (dom.Element child) {
544 TypeDefinition typeDefinition = typeDefinitionFromHtml(child);
545 types[typeDefinition.name] = typeDefinition;
546 }
547 });
548 return new Types(types, html);
549 }
550
551 typedef void ElementProcessor(dom.Element element);
552
553 typedef void TextProcessor(dom.Text text);
OLDNEW
« no previous file with comments | « pkg/analyzer_plugin/tool/spec/codegen_protocol_constants.dart ('k') | pkg/analyzer_plugin/tool/spec/generate_all.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698