OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino 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 library immic.plugins.dart; | |
6 | |
7 import 'dart:core' hide Type; | |
8 import 'dart:io' show Platform, File; | |
9 | |
10 import 'package:path/path.dart' show withoutExtension, join, dirname; | |
11 import 'package:strings/strings.dart' as strings; | |
12 | |
13 import 'shared.dart'; | |
14 import '../emitter.dart'; | |
15 import '../struct_layout.dart'; | |
16 import '../primitives.dart' as primitives; | |
17 | |
18 const List<String> RESOURCES = const [ | |
19 "immi.dart", | |
20 ]; | |
21 | |
22 const COPYRIGHT = """ | |
23 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
24 // for details. All rights reserved. Use of this source code is governed by a | |
25 // BSD-style license that can be found in the LICENSE.md file. | |
26 """; | |
27 | |
28 void generate(String path, | |
29 Map<String, Unit> units, | |
30 String outputDirectory) { | |
31 String directory = join(outputDirectory, 'dart'); | |
32 units.forEach((path, unit) => _generateNodeFile(path, unit, directory)); | |
33 _generateServiceFile(path, units, directory); | |
34 | |
35 String resourcesDirectory = join(dirname(Platform.script.path), | |
36 '..', 'lib', 'src', 'resources', 'dart'); | |
37 for (String resource in RESOURCES) { | |
38 String resourcePath = join(resourcesDirectory, resource); | |
39 File file = new File(resourcePath); | |
40 String contents = file.readAsStringSync(); | |
41 writeToFile(directory, resource, contents); | |
42 } | |
43 } | |
44 | |
45 String generateNodeString(String unitPath, Unit unit) { | |
46 _DartVisitor visitor = new _DartVisitor(unitPath); | |
47 visitor.visit(unit); | |
48 return visitor.buffer.toString(); | |
49 } | |
50 | |
51 String generateServiceString(String path, Map units) { | |
52 _DartVisitor visitor = new _DartVisitor(path); | |
53 units.values.forEach(visitor.collectMethodSignatures); | |
54 visitor._writeServiceImpl(); | |
55 return visitor.buffer.toString(); | |
56 } | |
57 | |
58 void _generateNodeFile(String unitPath, Unit unit, String directory) { | |
59 String content = generateNodeString(unitPath, unit); | |
60 writeToFile(directory, unitPath, content, extension: 'dart'); | |
61 } | |
62 | |
63 void _generateServiceFile(String path, | |
64 Map<String, Unit> units, | |
65 String directory) { | |
66 _DartVisitor visitor = new _DartVisitor(path); | |
67 String content = generateServiceString(path, units); | |
68 String file = visitor.serviceImplFile; | |
69 writeToFile(directory, file, content, extension: 'dart'); | |
70 } | |
71 | |
72 class _DartVisitor extends CodeGenerationVisitor { | |
73 _DartVisitor(String path) : super(path); | |
74 | |
75 visitUnit(Unit node) { | |
76 _writeHeader(); | |
77 _writeLibrary(); | |
78 _writeImports(); | |
79 node.imports.forEach(visit); | |
80 if (node.imports.isNotEmpty) writeln(); | |
81 node.structs.forEach(visit); | |
82 } | |
83 | |
84 visitImport(Import import) { | |
85 writeln("import '${withoutExtension(import.import)}.dart';"); | |
86 } | |
87 | |
88 getPatchType(Type type) { | |
89 if (type.isList) return 'ListPatch'; | |
90 if (type.isNode) return 'NodePatch'; | |
91 if (type.resolved != null) return '${type.identifier}Patch'; | |
92 return _types[type.identifier]; | |
93 } | |
94 | |
95 getSerializeMethodName(Type type) { | |
96 if (type.isList) return 'serializeList'; | |
97 if (type.isNode) return 'serializeNode'; | |
98 if (type.resolved != null) return 'serialize${type.identifier}'; | |
99 throw 'Unserializable type ${type.identifier}'; | |
100 } | |
101 | |
102 visitStruct(Struct node) { | |
103 String nodeName = '${node.name}Node'; | |
104 String patchName = '${node.name}Patch'; | |
105 String replacePatch = '${node.name}ReplacePatch'; | |
106 String updatePatch = '${node.name}UpdatePatch'; | |
107 String nodeBuilderName = '${node.name}NodeDataBuilder'; | |
108 String patchBuilderName = '${node.name}PatchDataBuilder'; | |
109 String updateBuilderName = '${node.name}UpdateDataBuilder'; | |
110 | |
111 bool hasFields = node.layout.slots.isNotEmpty; | |
112 bool hasMethods = node.methods.isNotEmpty; | |
113 bool hasSlotsAndMethods = hasFields && hasMethods; | |
114 | |
115 writeln('class $nodeName extends Node {'); | |
116 // Final fields. | |
117 forEachSlot(node, null, (Type slotType, String slotName) { | |
118 write(' final '); | |
119 writeType(slotType); | |
120 writeln(' $slotName;'); | |
121 }); | |
122 for (var method in node.methods) { | |
123 writeln(' final Function ${method.name};'); | |
124 } | |
125 // Public keyword constructor. | |
126 if (hasFields || hasMethods) { | |
127 write(' factory $nodeName({'); | |
128 forEachSlot(node, writeComma, (Type slotType, String slotName) { | |
129 writeType(slotType); | |
130 write(' $slotName'); | |
131 }); | |
132 if (hasSlotsAndMethods) write(', '); | |
133 write(node.methods.map((method) => 'Function ${method.name}').join(', ')); | |
134 writeln('}) =>'); | |
135 write(' new ${nodeName}._internal('); | |
136 forEachSlot(node, writeComma, (_, String slotName) { | |
137 write('$slotName'); | |
138 }); | |
139 if (hasSlotsAndMethods) write(', '); | |
140 write(node.methods.map((method) => method.name).join(', ')); | |
141 writeln(');'); | |
142 // Positional constructor. | |
143 write(' ${nodeName}._internal('); | |
144 forEachSlot(node, writeComma, (_, String slotName) { | |
145 write('this.${slotName}'); | |
146 }); | |
147 if (hasSlotsAndMethods) write(', '); | |
148 write(node.methods.map((method) => 'this.${method.name}').join(', ')); | |
149 writeln(');'); | |
150 } | |
151 | |
152 // Serialization | |
153 String serializeSelf = 'serialize${node.name}'; | |
154 writeln(' void serializeNode(NodeDataBuilder builder, ResourceManager manag
er) {'); | |
155 writeln(' $serializeSelf(builder.init${node.name}(), manager);'); | |
156 writeln(' }'); | |
157 writeln(' void $serializeSelf(${nodeName}DataBuilder builder, ResourceManag
er manager) {'); | |
158 forEachSlot(node, null, (Type slotType, String slotName) { | |
159 String slotNameCamel = camelize(slotName); | |
160 if (slotType.isList) { | |
161 String localSlotLength = "${slotName}Length"; | |
162 String localSlotBuilder = "${slotName}Builder"; | |
163 // TODO(zerny): Support list of primitives. | |
164 String serialize = slotType.elementType.isNode ? | |
165 'serializeNode' : | |
166 'serialize${slotType.elementType.identifier}'; | |
167 writeln(' var $localSlotLength = $slotName.length;'); | |
168 writeln(' List $localSlotBuilder ='); | |
169 writeln(' builder.init$slotNameCamel($localSlotLength);'); | |
170 writeln(' for (var i = 0; i < $localSlotLength; ++i) {'); | |
171 writeln(' $slotName[i].$serialize($localSlotBuilder[i], manager);')
; | |
172 writeln(' }'); | |
173 } else if (slotType.isNode || slotType.resolved != null) { | |
174 String serialize = getSerializeMethodName(slotType); | |
175 writeln(' $slotName.$serialize(builder.init$slotNameCamel(), manager)
;'); | |
176 } else { | |
177 writeln(' builder.$slotName = $slotName;'); | |
178 } | |
179 }); | |
180 for (var method in node.methods) { | |
181 String methodName = method.name; | |
182 writeln(' builder.$methodName = manager.addHandler($methodName);'); | |
183 } | |
184 writeln(' }'); | |
185 | |
186 // Event handlers | |
187 writeln(' void unregisterHandlers(ResourceManager manager) {'); | |
188 for (var method in node.methods) { | |
189 writeln(' manager.removeHandler(${method.name});'); | |
190 } | |
191 writeln(' }'); | |
192 | |
193 // Difference. | |
194 writeln(' ${node.name}Patch diff(Node previousNode) {'); | |
195 if (!hasFields && !hasMethods) { | |
196 writeln(' if (previousNode is $nodeName) return null;'); | |
197 writeln(' return new $replacePatch(this, previousNode);'); | |
198 } else { | |
199 writeln(' if (identical(this, previousNode)) return null;'); | |
200 writeln(' if (previousNode is! $nodeName) {'); | |
201 writeln(' return new $replacePatch(this, previousNode);'); | |
202 writeln(' }'); | |
203 writeln(' $nodeName previous = previousNode;'); | |
204 writeln(' $updatePatch updates = null;'); | |
205 String ensureUpdatePatch = | |
206 'if (updates == null) updates = new $updatePatch(previous);'; | |
207 forEachSlot(node, null, (Type slotType, String slotName) { | |
208 if (slotType.isList) { | |
209 // TODO(zerny): Support lists of primitives. | |
210 String type = slotType.elementType.isNode ? | |
211 'ListPatchType.AnyNode' : | |
212 'ListPatchType.SpecificNode'; | |
213 writeln(' ${getPatchType(slotType)} ${slotName}Patch ='); | |
214 writeln(' diffList($slotName, previous.$slotName, $type);'); | |
215 writeln(' if (${slotName}Patch != null) {'); | |
216 writeln(' $ensureUpdatePatch'); | |
217 writeln(' updates.$slotName = ${slotName}Patch;'); | |
218 writeln(' }'); | |
219 } else if (slotType.isNode || slotType.resolved != null) { | |
220 writeln(' ${getPatchType(slotType)} ${slotName}Patch ='); | |
221 writeln(' $slotName.diff(previous.$slotName);'); | |
222 writeln(' if (${slotName}Patch != null) {'); | |
223 writeln(' $ensureUpdatePatch'); | |
224 writeln(' updates.$slotName = ${slotName}Patch;'); | |
225 writeln(' }'); | |
226 } else { | |
227 writeln(' if ($slotName != previous.$slotName) {'); | |
228 writeln(' $ensureUpdatePatch'); | |
229 writeln(' updates.$slotName = $slotName;'); | |
230 writeln(' }'); | |
231 } | |
232 }); | |
233 for (Method method in node.methods) { | |
234 String name = method.name; | |
235 writeln(' if ($name != previous.$name) {'); | |
236 writeln(' $ensureUpdatePatch'); | |
237 writeln(' updates.$name = $name;'); | |
238 writeln(' }'); | |
239 } | |
240 writeln(' return updates;'); | |
241 } | |
242 writeln(' }'); | |
243 // Difference end. | |
244 | |
245 writeln('}'); | |
246 writeln(); | |
247 // Node class end. | |
248 | |
249 // Node specific patches. | |
250 String serializeSelfNode = 'serialize${node.name}'; | |
251 String serializeSelfPatch = 'serialize${node.name}'; | |
252 writeln('abstract class $patchName extends NodePatch {'); | |
253 writeln(' void serializeNode(NodePatchDataBuilder builder, ResourceManager
manager) {'); | |
254 writeln(' $serializeSelfPatch(builder.init${node.name}(), manager);'); | |
255 writeln(' }'); | |
256 writeln(' void $serializeSelfPatch($patchBuilderName builder, ResourceManag
er manager);'); | |
257 writeln('}'); | |
258 writeln(); | |
259 writeln('class $replacePatch extends $patchName {'); | |
260 writeln(' final $nodeName replacement;'); | |
261 writeln(' final Node previous;'); | |
262 writeln(' $replacePatch(this.replacement, this.previous);'); | |
263 writeln(' void $serializeSelfPatch($patchBuilderName builder, ResourceManag
er manager) {'); | |
264 writeln(' if (previous != null) previous.unregisterHandlers(manager);'); | |
265 writeln(' replacement.$serializeSelfNode(builder.initReplace(), manager);
'); | |
266 writeln(' }'); | |
267 writeln('}'); | |
268 writeln(); | |
269 if (hasFields || hasMethods) { | |
270 writeln('class $updatePatch extends $patchName {'); | |
271 writeln(' final $nodeName previous;'); | |
272 writeln(' $updatePatch(this.previous);'); | |
273 writeln(' int _count = 0;'); | |
274 forEachSlot(node, null, (Type slotType, String slotName) { | |
275 writeln(' ${getPatchType(slotType)} _$slotName;'); | |
276 writeln(' set $slotName(${getPatchType(slotType)} $slotName) {'); | |
277 writeln(' ++_count;'); | |
278 writeln(' _$slotName = $slotName;'); | |
279 writeln(' }'); | |
280 }); | |
281 for (Method method in node.methods) { | |
282 String name = method.name; | |
283 writeln(' Function _$name;'); | |
284 writeln(' set $name(Function $name) {'); | |
285 writeln(' ++_count;'); | |
286 writeln(' _$name = $name;'); | |
287 writeln(' }'); | |
288 } | |
289 writeln(' void $serializeSelfPatch($patchBuilderName builder, ResourceMan
ager manager) {'); | |
290 writeln(' List<${updateBuilderName}> builders = builder.initUpdates(_co
unt);'); | |
291 writeln(' int index = 0;'); | |
292 forEachSlot(node, null, (Type slotType, String slotName) { | |
293 writeln(' if (_$slotName != null) {'); | |
294 if (slotType.isList || slotType.isNode || slotType.resolved != null) { | |
295 String slotNameCamel = camelize(slotName); | |
296 String serializeSlot = getSerializeMethodName(slotType); | |
297 writeln(' _$slotName.$serializeSlot(builders[index++].init$slotNa
meCamel(), manager);'); | |
298 } else { | |
299 writeln(' builders[index++].$slotName = _$slotName;'); | |
300 } | |
301 writeln(' }'); | |
302 }); | |
303 for (Method method in node.methods) { | |
304 String name = method.name; | |
305 writeln(' if (_$name != null) {'); | |
306 writeln(' manager.removeHandler(previous.$name);'); | |
307 writeln(' builders[index++].$name = manager.addHandler(_$name);'); | |
308 writeln(' }'); | |
309 } | |
310 writeln(' assert(index == _count);'); | |
311 writeln(' }'); | |
312 writeln('}'); | |
313 writeln(); | |
314 } | |
315 } | |
316 | |
317 visitUnion(Union node) { | |
318 // Ignored for now. | |
319 } | |
320 | |
321 visitMethod(Method node) { | |
322 // Ignored for now. | |
323 } | |
324 | |
325 void _writeServiceImpl() { | |
326 _writeHeader(); | |
327 write(""" | |
328 library ${serviceImplLib}; | |
329 | |
330 import '${immiGenPkg}/dart/immi.dart'; | |
331 import '${serviceGenPkg}/dart/${serviceFile}.dart'; | |
332 | |
333 class ${serviceImplName} extends ${serviceName} { | |
334 var _nextPresenterId = 1; | |
335 var _presenters = [null]; | |
336 var _presenterGraphs = [null]; | |
337 var _presenterNameToId = {}; | |
338 | |
339 // TODO(zerny): Implement per-graph resource management. | |
340 ResourceManager _manager = new ResourceManager(); | |
341 | |
342 ${serviceImplName}(); | |
343 void add(String name, presenter) { | |
344 assert(!_presenterNameToId.containsKey(name)); | |
345 _presenterNameToId[name] = _addPresenter(presenter); | |
346 } | |
347 | |
348 int _addPresenter(presenter) { | |
349 assert(_presenters.length == _nextPresenterId); | |
350 assert(_presenterGraphs.length == _nextPresenterId); | |
351 _presenters.add(presenter); | |
352 _presenterGraphs.add(null); | |
353 return _nextPresenterId++; | |
354 } | |
355 | |
356 int getPresenter(PresenterData data) { | |
357 String name = data.name; | |
358 int pid = _presenterNameToId[name]; | |
359 return pid == null ? 0 : pid; | |
360 } | |
361 | |
362 void reset(int pid) { | |
363 int length = _presenterGraphs.length; | |
364 for (int i = 0; i < length; ++i) { | |
365 _presenterGraphs[i] = null; | |
366 } | |
367 _manager.clear(); | |
368 } | |
369 | |
370 void refresh(int pid, PatchDataBuilder builder) { | |
371 assert(0 < pid && pid < _nextPresenterId); | |
372 Node previous = _presenterGraphs[pid]; | |
373 Node current = _presenters[pid].present(previous); | |
374 NodePatch patch = current.diff(previous); | |
375 if (patch == null) { | |
376 builder.setNoPatch(); | |
377 } else { | |
378 _presenterGraphs[pid] = current; | |
379 patch.serializeNode(builder.initNode(), _manager); | |
380 } | |
381 } | |
382 | |
383 void run() { | |
384 ${serviceName}.initialize(this); | |
385 while (${serviceName}.hasNextEvent()) { | |
386 ${serviceName}.handleNextEvent(); | |
387 } | |
388 } | |
389 | |
390 """); | |
391 for (List<Type> formals in methodSignatures.values) { | |
392 String suffix = actionTypeSuffix(formals); | |
393 bool boxedArguments = formals.any((t) => t.isString); | |
394 if (boxedArguments) { | |
395 writeln(' void dispatch$suffix(Action${suffix}Args args) {'); | |
396 writeln(' var handler = _manager.getHandler(args.id);'); | |
397 writeln(' if (handler != null) handler('); | |
398 for (int j = 0; j <= formals.length - 1; ++j) { | |
399 if (j != 0) write(','); | |
400 write('args.arg$j'); | |
401 } | |
402 writeln(');'); | |
403 writeln(' }'); | |
404 continue; | |
405 } | |
406 write(' void dispatch$suffix(int id'); | |
407 int i = 0; | |
408 for (var formal in formals) { | |
409 write(', '); | |
410 writeType(formal); | |
411 write(' arg${i++}'); | |
412 } | |
413 writeln(') {'); | |
414 writeln(' var handler = _manager.getHandler(id);'); | |
415 write(' if (handler != null) handler('); | |
416 for (int j = 0; j <= i - 1; ++j) { | |
417 if (j != 0) write(', '); | |
418 write('arg$j'); | |
419 } | |
420 writeln(');'); | |
421 | |
422 writeln(' }'); | |
423 writeln(); | |
424 } | |
425 writeln('}'); | |
426 writeln(); | |
427 } | |
428 | |
429 void _writeHeader() { | |
430 writeln(COPYRIGHT); | |
431 writeln('// Generated file. Do not edit.'); | |
432 writeln(); | |
433 } | |
434 | |
435 void _writeLibrary() { | |
436 writeln('library $libraryName;'); | |
437 writeln(); | |
438 } | |
439 | |
440 void _writeImports() { | |
441 writeln('import "$immiGenPkg/dart/immi.dart";'); | |
442 writeln('import "$serviceGenPkg/dart/$serviceFile.dart";'); | |
443 writeln(); | |
444 } | |
445 | |
446 static const Map<String, String> _types = const { | |
447 'void' : 'void', | |
448 'bool' : 'bool', | |
449 | |
450 'uint8' : 'int', | |
451 'uint16' : 'int', | |
452 | |
453 'int8' : 'int', | |
454 'int16' : 'int', | |
455 'int32' : 'int', | |
456 'int64' : 'int', | |
457 | |
458 'float32' : 'double', | |
459 'float64' : 'double', | |
460 | |
461 'String' : 'String', | |
462 }; | |
463 | |
464 void writeType(Type node) { | |
465 if (node.isList) write('List<'); | |
466 if (node.isNode || (node.isList && node.elementType.isNode)) { | |
467 write('Node'); | |
468 } else if (node.resolved != null) { | |
469 write("${node.identifier}Node"); | |
470 } else { | |
471 String type = _types[node.identifier]; | |
472 write(type); | |
473 } | |
474 if (node.isList) write('>'); | |
475 } | |
476 } | |
OLD | NEW |