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

Side by Side Diff: pkg/analysis_server/tool/spec/codegen_tools.dart

Issue 1431683002: Refactor analysis server code generation to re-use logic from analyzer. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 1 month 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) 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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Tools for code generation.
7 */
8 library codegen.tools;
9
10 import 'dart:io';
11
12 import 'package:html/dom.dart' as dom;
13 import 'package:path/path.dart';
14
15 import 'html_tools.dart';
16 import 'text_formatter.dart';
17
18 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$');
19 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true);
20
21 /**
22 * Join the given strings using camelCase. If [doCapitalize] is true, the first
23 * part will be capitalized as well.
24 */
25 String camelJoin(List<String> parts, {bool doCapitalize: false}) {
26 List<String> upcasedParts = <String>[];
27 for (int i = 0; i < parts.length; i++) {
28 if (i == 0 && !doCapitalize) {
29 upcasedParts.add(parts[i]);
30 } else {
31 upcasedParts.add(capitalize(parts[i]));
32 }
33 }
34 return upcasedParts.join();
35 }
36
37 /**
38 * Capitalize and return the passed String.
39 */
40 String capitalize(String string) {
41 return string[0].toUpperCase() + string.substring(1);
42 }
43
44 /**
45 * Type of functions used to compute the contents of a set of generated files.
46 */
47 typedef Map<String, FileContentsComputer> DirectoryContentsComputer();
48
49 /**
50 * Type of functions used to compute the contents of a generated file.
51 */
52 typedef String FileContentsComputer();
53
54 /**
55 * Mixin class for generating code.
56 */
57 class CodeGenerator {
58 _CodeGeneratorState _state;
59
60 /**
61 * Settings that specialize code generation behavior for a given
62 * programming language.
63 */
64 CodeGeneratorSettings codeGeneratorSettings = new CodeGeneratorSettings();
65
66 /**
67 * Measure the width of the current indentation level.
68 */
69 int get indentWidth => _state.nextIndent.length;
70
71 /**
72 * Execute [callback], collecting any code that is output using [write]
73 * or [writeln], and return the result as a string.
74 */
75 String collectCode(void callback(), {bool removeTrailingNewLine: false}) {
76 _CodeGeneratorState oldState = _state;
77 try {
78 _state = new _CodeGeneratorState();
79 callback();
80 var text =
81 _state.buffer.toString().replaceAll(trailingSpacesInLineRegExp, '');
82 if (!removeTrailingNewLine) {
83 return text;
84 } else {
85 return text.replaceAll(trailingWhitespaceRegExp, '');
86 }
87 } finally {
88 _state = oldState;
89 }
90 }
91
92 /**
93 * Generate a doc comment based on the HTML in [docs].
94 *
95 * When generating java code, the output is compatible with Javadoc, which
96 * understands certain HTML constructs.
97 */
98 void docComment(List<dom.Node> docs, {bool removeTrailingNewLine: false}) {
99 if (containsOnlyWhitespace(docs)) return;
100 writeln(codeGeneratorSettings.docCommentStartMarker);
101 int width = codeGeneratorSettings.commentLineLength;
102 bool javadocStyle = codeGeneratorSettings.languageName == 'java';
103 indentBy(codeGeneratorSettings.docCommentLineLeader, () {
104 write(nodesToText(docs, width - _state.indent.length, javadocStyle,
105 removeTrailingNewLine: removeTrailingNewLine));
106 });
107 writeln(codeGeneratorSettings.docCommentEndMarker);
108 }
109
110 /**
111 * Execute [callback], indenting any code it outputs.
112 */
113 void indent(void callback()) {
114 indentSpecial(
115 codeGeneratorSettings.indent, codeGeneratorSettings.indent, callback);
116 }
117
118 /**
119 * Execute [callback], using [additionalIndent] to indent any code it outputs.
120 */
121 void indentBy(String additionalIndent, void callback()) =>
122 indentSpecial(additionalIndent, additionalIndent, callback);
123
124 /**
125 * Execute [callback], using [additionalIndent] to indent any code it outputs.
126 * The first line of output is indented by [firstAdditionalIndent] instead of
127 * [additionalIndent].
128 */
129 void indentSpecial(
130 String firstAdditionalIndent, String additionalIndent, void callback()) {
131 String oldNextIndent = _state.nextIndent;
132 String oldIndent = _state.indent;
133 try {
134 _state.nextIndent += firstAdditionalIndent;
135 _state.indent += additionalIndent;
136 callback();
137 } finally {
138 _state.nextIndent = oldNextIndent;
139 _state.indent = oldIndent;
140 }
141 }
142
143 void lineComment(List<dom.Node> docs) {
144 if (containsOnlyWhitespace(docs)) {
145 return;
146 }
147 write(codeGeneratorSettings.lineCommentLineLeader);
148 int width = codeGeneratorSettings.commentLineLength;
149 indentBy(codeGeneratorSettings.lineCommentLineLeader, () {
150 write(nodesToText(docs, width - _state.indent.length, false));
151 });
152 }
153
154 void outputHeader({bool javaStyle: false}) {
155 String header;
156 if (codeGeneratorSettings.languageName == 'java') {
157 header = '''
158 /*
159 * Copyright (c) 2014, the Dart project authors.
160 *
161 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u se this file except
162 * in compliance with the License. You may obtain a copy of the License at
163 *
164 * http://www.eclipse.org/legal/epl-v10.html
165 *
166 * Unless required by applicable law or agreed to in writing, software distribut ed under the License
167 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY K IND, either express
168 * or implied. See the License for the specific language governing permissions a nd limitations under
169 * the License.
170 *
171 * This file has been automatically generated. Please do not edit it manually.
172 * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generat e_files".
173 */''';
174 } else if (codeGeneratorSettings.languageName == 'python') {
175 header = '''
176 # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
177 # for details. All rights reserved. Use of this source code is governed by a
178 # BSD-style license that can be found in the LICENSE file.
179 #
180 # This file has been automatically generated. Please do not edit it manually.
181 # To regenerate the file, use the script
182 # "pkg/analysis_server/tool/spec/generate_files".
183 ''';
184 } else {
185 header = '''
186 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
187 // for details. All rights reserved. Use of this source code is governed by a
188 // BSD-style license that can be found in the LICENSE file.
189 //
190 // This file has been automatically generated. Please do not edit it manually.
191 // To regenerate the file, use the script
192 // "pkg/analysis_server/tool/spec/generate_files".
193 ''';
194 }
195 writeln(header.trim());
196 }
197
198 /**
199 * Output text without ending the current line.
200 */
201 void write(Object obj) {
202 _state.write(obj.toString());
203 }
204
205 /**
206 * Output text, ending the current line.
207 */
208 void writeln([Object obj = '']) {
209 _state.write('$obj\n');
210 }
211 }
212
213 /**
214 * Controls several settings of [CodeGenerator].
215 *
216 * The default settings are valid for generating Java and Dart code.
217 */
218 class CodeGeneratorSettings {
219 /**
220 * Name of the language being generated. Lowercase.
221 */
222 String languageName;
223
224 /**
225 * Marker used in line comments.
226 */
227 String lineCommentLineLeader;
228
229 /**
230 * Start marker for doc comments.
231 */
232 String docCommentStartMarker;
233
234 /**
235 * Line leader for body lines in doc comments.
236 */
237 String docCommentLineLeader;
238
239 /**
240 * End marker for doc comments.
241 */
242 String docCommentEndMarker;
243
244 /**
245 * Line length for doc comment lines.
246 */
247 int commentLineLength;
248
249 /**
250 * String used for indenting code.
251 */
252 String indent;
253
254 CodeGeneratorSettings(
255 {this.languageName: 'java',
256 this.lineCommentLineLeader: '// ',
257 this.docCommentStartMarker: '/**',
258 this.docCommentLineLeader: ' * ',
259 this.docCommentEndMarker: ' */',
260 this.commentLineLength: 99,
261 this.indent: ' '});
262 }
263
264 abstract class GeneratedContent {
265 FileSystemEntity get outputFile;
266 bool check();
267 void generate();
268 }
269
270 /**
271 * Class representing a single output directory (either generated code or
272 * generated HTML). No other content should exist in the directory.
273 */
274 class GeneratedDirectory extends GeneratedContent {
275 /**
276 * The path to the directory that will have the generated content.
277 */
278 final String outputDirPath;
279
280 /**
281 * Callback function that computes the directory contents.
282 */
283 final DirectoryContentsComputer directoryContentsComputer;
284
285 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer);
286
287 /**
288 * Get a Directory object representing the output directory.
289 */
290 Directory get outputFile =>
291 new Directory(joinAll(posix.split(outputDirPath)));
292
293 /**
294 * Check whether the directory has the correct contents, and return true if it
295 * does.
296 */
297 @override
298 bool check() {
299 Map<String, FileContentsComputer> map = directoryContentsComputer();
300 try {
301 for (String file in map.keys) {
302 FileContentsComputer fileContentsComputer = map[file];
303 String expectedContents = fileContentsComputer();
304 File outputFile =
305 new File(joinAll(posix.split(posix.join(outputDirPath, file))));
306 String actualContents = outputFile.readAsStringSync();
307 // Normalize Windows line endings to Unix line endings so that the
308 // comparison doesn't fail on Windows.
309 actualContents = actualContents.replaceAll('\r\n', '\n');
310 if (expectedContents != actualContents) {
311 return false;
312 }
313 }
314 int nonHiddenFileCount = 0;
315 outputFile
316 .listSync(recursive: false, followLinks: false)
317 .forEach((FileSystemEntity fileSystemEntity) {
318 if (fileSystemEntity is File &&
319 !basename(fileSystemEntity.path).startsWith('.')) {
320 nonHiddenFileCount++;
321 }
322 });
323 if (nonHiddenFileCount != map.length) {
324 // The number of files generated doesn't match the number we expected to
325 // generate.
326 return false;
327 }
328 } catch (e) {
329 // There was a problem reading the file (most likely because it didn't
330 // exist). Treat that the same as if the file doesn't have the expected
331 // contents.
332 return false;
333 }
334 return true;
335 }
336
337 /**
338 * Replace the directory with the correct contents. [spec] is the "tool/spec"
339 * directory. If [spec] is unspecified, it is assumed to be the directory
340 * containing Platform.executable.
341 */
342 @override
343 void generate() {
344 try {
345 // delete the contents of the directory (and the directory itself)
346 outputFile.deleteSync(recursive: true);
347 } catch (e) {
348 // Error caught while trying to delete the directory, this can happen if
349 // it didn't yet exist.
350 }
351 // re-create the empty directory
352 outputFile.createSync(recursive: true);
353
354 // generate all of the files in the directory
355 Map<String, FileContentsComputer> map = directoryContentsComputer();
356 map.forEach((String file, FileContentsComputer fileContentsComputer) {
357 File outputFile = new File(joinAll(posix.split(outputDirPath + file)));
358 outputFile.writeAsStringSync(fileContentsComputer());
359 });
360 }
361 }
362
363 /**
364 * Class representing a single output file (either generated code or generated
365 * HTML).
366 */
367 class GeneratedFile extends GeneratedContent {
368 /**
369 * The output file to which generated output should be written, relative to
370 * the "tool/spec" directory. This filename uses the posix path separator
371 * ('/') regardless of the OS.
372 */
373 final String outputPath;
374
375 /**
376 * Callback function which computes the file.
377 */
378 final FileContentsComputer computeContents;
379
380 GeneratedFile(this.outputPath, this.computeContents);
381
382 /**
383 * Get a File object representing the output file.
384 */
385 File get outputFile => new File(joinAll(posix.split(outputPath)));
386
387 /**
388 * Check whether the file has the correct contents, and return true if it
389 * does.
390 */
391 @override
392 bool check() {
393 String expectedContents = computeContents();
394 try {
395 String actualContents = outputFile.readAsStringSync();
396 // Normalize Windows line endings to Unix line endings so that the
397 // comparison doesn't fail on Windows.
398 actualContents = actualContents.replaceAll('\r\n', '\n');
399 return expectedContents == actualContents;
400 } catch (e) {
401 // There was a problem reading the file (most likely because it didn't
402 // exist). Treat that the same as if the file doesn't have the expected
403 // contents.
404 return false;
405 }
406 }
407
408 /**
409 * Replace the file with the correct contents. [spec] is the "tool/spec"
410 * directory. If [spec] is unspecified, it is assumed to be the directory
411 * containing Platform.executable.
412 */
413 void generate() {
414 outputFile.writeAsStringSync(computeContents());
415 }
416 }
417
418 /**
419 * Mixin class for generating HTML representations of code that are suitable
420 * for enclosing inside a <pre> element.
421 */
422 abstract class HtmlCodeGenerator {
423 _HtmlCodeGeneratorState _state;
424
425 /**
426 * Add the given [node] to the HTML output.
427 */
428 void add(dom.Node node) {
429 _state.add(node);
430 }
431
432 /**
433 * Add the given [nodes] to the HTML output.
434 */
435 void addAll(Iterable<dom.Node> nodes) {
436 for (dom.Node node in nodes) {
437 _state.add(node);
438 }
439 }
440
441 /**
442 * Execute [callback], collecting any code that is output using [write],
443 * [writeln], [add], or [addAll], and return the result as a list of DOM
444 * nodes.
445 */
446 List<dom.Node> collectHtml(void callback()) {
447 _HtmlCodeGeneratorState oldState = _state;
448 try {
449 _state = new _HtmlCodeGeneratorState();
450 if (callback != null) {
451 callback();
452 }
453 return _state.buffer;
454 } finally {
455 _state = oldState;
456 }
457 }
458
459 /**
460 * Execute [callback], wrapping its output in an element with the given
461 * [name] and [attributes].
462 */
463 void element(String name, Map<String, String> attributes, [void callback()]) {
464 add(makeElement(name, attributes, collectHtml(callback)));
465 }
466
467 /**
468 * Execute [callback], indenting any code it outputs by two spaces.
469 */
470 void indent(void callback()) {
471 String oldIndent = _state.indent;
472 try {
473 _state.indent += ' ';
474 callback();
475 } finally {
476 _state.indent = oldIndent;
477 }
478 }
479
480 /**
481 * Output text without ending the current line.
482 */
483 void write(Object obj) {
484 _state.write(obj.toString());
485 }
486
487 /**
488 * Output text, ending the current line.
489 */
490 void writeln([Object obj = '']) {
491 _state.write('$obj\n');
492 }
493 }
494
495 /**
496 * State used by [CodeGenerator].
497 */
498 class _CodeGeneratorState {
499 StringBuffer buffer = new StringBuffer();
500 String nextIndent = '';
501 String indent = '';
502 bool indentNeeded = true;
503
504 void write(String text) {
505 List<String> lines = text.split('\n');
506 for (int i = 0; i < lines.length; i++) {
507 if (i == lines.length - 1 && lines[i].isEmpty) {
508 break;
509 }
510 if (indentNeeded) {
511 buffer.write(nextIndent);
512 nextIndent = indent;
513 }
514 indentNeeded = false;
515 buffer.write(lines[i]);
516 if (i != lines.length - 1) {
517 buffer.writeln();
518 indentNeeded = true;
519 }
520 }
521 }
522 }
523
524 /**
525 * State used by [HtmlCodeGenerator].
526 */
527 class _HtmlCodeGeneratorState {
528 List<dom.Node> buffer = <dom.Node>[];
529 String indent = '';
530 bool indentNeeded = true;
531
532 void add(dom.Node node) {
533 if (node is dom.Text) {
534 write(node.text);
535 } else {
536 buffer.add(node);
537 }
538 }
539
540 void write(String text) {
541 if (text.isEmpty) {
542 return;
543 }
544 if (indentNeeded) {
545 buffer.add(new dom.Text(indent));
546 }
547 List<String> lines = text.split('\n');
548 if (lines.last.isEmpty) {
549 lines.removeLast();
550 buffer.add(new dom.Text(lines.join('\n$indent') + '\n'));
551 indentNeeded = true;
552 } else {
553 buffer.add(new dom.Text(lines.join('\n$indent')));
554 indentNeeded = false;
555 }
556 }
557 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/tool/spec/codegen_matchers.dart ('k') | pkg/analysis_server/tool/spec/from_html.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698