| 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 |