| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2015, 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 * This file contains code to generate serialization/deserialization logic for |
| 7 * summaries based on an "IDL" description of the summary format (written in |
| 8 * stylized Dart). |
| 9 * |
| 10 * For each class in the "IDL" input, two corresponding classes are generated: |
| 11 * - A class with the same name which represents deserialized summary data in |
| 12 * memory. This class has read-only semantics. |
| 13 * - A "builder" class which can be used to generate serialized summary data. |
| 14 * This class has write-only semantics. |
| 15 * |
| 16 * Each of the "builder" classes has a single `finish` method which writes |
| 17 * the entity being built into the given FlatBuffer and returns the `Offset` |
| 18 * reference to it. |
| 19 */ |
| 20 library analyzer.tool.summary.generate; |
| 21 |
| 22 import 'dart:convert'; |
| 23 import 'dart:io' hide File; |
| 24 |
| 25 import 'package:analyzer/analyzer.dart'; |
| 26 import 'package:analyzer/dart/ast/token.dart'; |
| 27 import 'package:analyzer/error/listener.dart'; |
| 28 import 'package:analyzer/file_system/file_system.dart'; |
| 29 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 30 import 'package:analyzer/src/codegen/tools.dart'; |
| 31 import 'package:analyzer/src/dart/scanner/reader.dart'; |
| 32 import 'package:analyzer/src/dart/scanner/scanner.dart'; |
| 33 import 'package:analyzer/src/generated/parser.dart'; |
| 34 import 'package:analyzer/src/generated/source.dart'; |
| 35 import 'package:path/path.dart'; |
| 36 |
| 37 import 'idl_model.dart' as idlModel; |
| 38 |
| 39 main() { |
| 40 String script = Platform.script.toFilePath(windows: Platform.isWindows); |
| 41 String pkgPath = normalize(join(dirname(script), '..', '..')); |
| 42 GeneratedContent.generateAll(pkgPath, allTargets); |
| 43 } |
| 44 |
| 45 final List<GeneratedContent> allTargets = <GeneratedContent>[ |
| 46 formatTarget, |
| 47 schemaTarget |
| 48 ]; |
| 49 |
| 50 final GeneratedFile formatTarget = |
| 51 new GeneratedFile('lib/src/summary/format.dart', (String pkgPath) { |
| 52 _CodeGenerator codeGenerator = new _CodeGenerator(pkgPath); |
| 53 codeGenerator.generateFormatCode(); |
| 54 return codeGenerator._outBuffer.toString(); |
| 55 }); |
| 56 |
| 57 final GeneratedFile schemaTarget = |
| 58 new GeneratedFile('lib/src/summary/format.fbs', (String pkgPath) { |
| 59 _CodeGenerator codeGenerator = new _CodeGenerator(pkgPath); |
| 60 codeGenerator.generateFlatBufferSchema(); |
| 61 return codeGenerator._outBuffer.toString(); |
| 62 }); |
| 63 |
| 64 typedef String _StringToString(String s); |
| 65 |
| 66 class _CodeGenerator { |
| 67 static const String _throwDeprecated = |
| 68 "throw new UnimplementedError('attempt to access deprecated field')"; |
| 69 |
| 70 /** |
| 71 * Buffer in which generated code is accumulated. |
| 72 */ |
| 73 final StringBuffer _outBuffer = new StringBuffer(); |
| 74 |
| 75 /** |
| 76 * Current indentation level. |
| 77 */ |
| 78 String _indentation = ''; |
| 79 |
| 80 /** |
| 81 * Semantic model of the "IDL" input file. |
| 82 */ |
| 83 idlModel.Idl _idl; |
| 84 |
| 85 _CodeGenerator(String pkgPath) { |
| 86 // Parse the input "IDL" file. |
| 87 PhysicalResourceProvider provider = new PhysicalResourceProvider( |
| 88 PhysicalResourceProvider.NORMALIZE_EOL_ALWAYS); |
| 89 String idlPath = join(pkgPath, 'lib', 'src', 'summary', 'idl.dart'); |
| 90 File idlFile = provider.getFile(idlPath); |
| 91 Source idlSource = provider.getFile(idlPath).createSource(); |
| 92 String idlText = idlFile.readAsStringSync(); |
| 93 BooleanErrorListener errorListener = new BooleanErrorListener(); |
| 94 CharacterReader idlReader = new CharSequenceReader(idlText); |
| 95 Scanner scanner = new Scanner(idlSource, idlReader, errorListener); |
| 96 Token tokenStream = scanner.tokenize(); |
| 97 LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
| 98 Parser parser = new Parser(idlSource, new BooleanErrorListener()); |
| 99 CompilationUnit idlParsed = parser.parseCompilationUnit(tokenStream); |
| 100 // Extract a description of the IDL and make sure it is valid. |
| 101 extractIdl(lineInfo, idlParsed); |
| 102 checkIdl(); |
| 103 } |
| 104 |
| 105 /** |
| 106 * Perform basic sanity checking of the IDL (over and above that done by |
| 107 * [extractIdl]). |
| 108 */ |
| 109 void checkIdl() { |
| 110 _idl.classes.forEach((String name, idlModel.ClassDeclaration cls) { |
| 111 if (cls.fileIdentifier != null) { |
| 112 if (cls.fileIdentifier.length != 4) { |
| 113 throw new Exception('$name: file identifier must be 4 characters'); |
| 114 } |
| 115 for (int i = 0; i < cls.fileIdentifier.length; i++) { |
| 116 if (cls.fileIdentifier.codeUnitAt(i) >= 256) { |
| 117 throw new Exception( |
| 118 '$name: file identifier must be encodable as Latin-1'); |
| 119 } |
| 120 } |
| 121 } |
| 122 Map<int, String> idsUsed = <int, String>{}; |
| 123 for (idlModel.FieldDeclaration field in cls.allFields) { |
| 124 String fieldName = field.name; |
| 125 idlModel.FieldType type = field.type; |
| 126 if (type.isList) { |
| 127 if (_idl.classes.containsKey(type.typeName)) { |
| 128 // List of classes is ok |
| 129 } else if (_idl.enums.containsKey(type.typeName)) { |
| 130 // List of enums is ok |
| 131 } else if (type.typeName == 'bool') { |
| 132 // List of booleans is ok |
| 133 } else if (type.typeName == 'int') { |
| 134 // List of ints is ok |
| 135 } else if (type.typeName == 'double') { |
| 136 // List of doubles is ok |
| 137 } else if (type.typeName == 'String') { |
| 138 // List of strings is ok |
| 139 } else { |
| 140 throw new Exception( |
| 141 '$name.$fieldName: illegal type (list of ${type.typeName})'); |
| 142 } |
| 143 } |
| 144 if (idsUsed.containsKey(field.id)) { |
| 145 throw new Exception('$name.$fieldName: id ${field.id} already used by' |
| 146 ' ${idsUsed[field.id]}'); |
| 147 } |
| 148 idsUsed[field.id] = fieldName; |
| 149 } |
| 150 for (int i = 0; i < idsUsed.length; i++) { |
| 151 if (!idsUsed.containsKey(i)) { |
| 152 throw new Exception('$name: no field uses id $i'); |
| 153 } |
| 154 } |
| 155 }); |
| 156 } |
| 157 |
| 158 /** |
| 159 * Generate a string representing the Dart type which should be used to |
| 160 * represent [type] when deserialized. |
| 161 */ |
| 162 String dartType(idlModel.FieldType type) { |
| 163 String baseType = idlPrefix(type.typeName); |
| 164 if (type.isList) { |
| 165 return 'List<$baseType>'; |
| 166 } else { |
| 167 return baseType; |
| 168 } |
| 169 } |
| 170 |
| 171 /** |
| 172 * Generate a Dart expression representing the default value for a field |
| 173 * having the given [type], or `null` if there is no default value. |
| 174 * |
| 175 * If [builder] is `true`, the returned type should be appropriate for use in |
| 176 * a builder class. |
| 177 */ |
| 178 String defaultValue(idlModel.FieldType type, bool builder) { |
| 179 if (type.isList) { |
| 180 if (builder) { |
| 181 idlModel.FieldType elementType = |
| 182 new idlModel.FieldType(type.typeName, false); |
| 183 return '<${encodedType(elementType)}>[]'; |
| 184 } else { |
| 185 return 'const <${idlPrefix(type.typeName)}>[]'; |
| 186 } |
| 187 } else if (_idl.enums.containsKey(type.typeName)) { |
| 188 return '${idlPrefix(type.typeName)}.' |
| 189 '${_idl.enums[type.typeName].values[0].name}'; |
| 190 } else if (type.typeName == 'int') { |
| 191 return '0'; |
| 192 } else if (type.typeName == 'String') { |
| 193 return "''"; |
| 194 } else if (type.typeName == 'bool') { |
| 195 return 'false'; |
| 196 } else { |
| 197 return null; |
| 198 } |
| 199 } |
| 200 |
| 201 /** |
| 202 * Generate a string representing the Dart type which should be used to |
| 203 * represent [type] while building a serialized data structure. |
| 204 */ |
| 205 String encodedType(idlModel.FieldType type) { |
| 206 String typeStr; |
| 207 if (_idl.classes.containsKey(type.typeName)) { |
| 208 typeStr = '${type.typeName}Builder'; |
| 209 } else { |
| 210 typeStr = idlPrefix(type.typeName); |
| 211 } |
| 212 if (type.isList) { |
| 213 return 'List<$typeStr>'; |
| 214 } else { |
| 215 return typeStr; |
| 216 } |
| 217 } |
| 218 |
| 219 /** |
| 220 * Process the AST in [idlParsed] and store the resulting semantic model in |
| 221 * [_idl]. Also perform some error checking. |
| 222 */ |
| 223 void extractIdl(LineInfo lineInfo, CompilationUnit idlParsed) { |
| 224 _idl = new idlModel.Idl(); |
| 225 for (CompilationUnitMember decl in idlParsed.declarations) { |
| 226 if (decl is ClassDeclaration) { |
| 227 bool isTopLevel = false; |
| 228 String fileIdentifier; |
| 229 String clsName = decl.name.name; |
| 230 for (Annotation annotation in decl.metadata) { |
| 231 if (annotation.arguments != null && |
| 232 annotation.name.name == 'TopLevel' && |
| 233 annotation.constructorName == null) { |
| 234 isTopLevel = true; |
| 235 if (annotation.arguments == null) { |
| 236 throw new Exception( |
| 237 'Class `$clsName`: TopLevel requires parenthesis'); |
| 238 } |
| 239 if (annotation.constructorName != null) { |
| 240 throw new Exception( |
| 241 "Class `$clsName`: TopLevel doesn't have named constructors"); |
| 242 } |
| 243 if (annotation.arguments.arguments.length == 1) { |
| 244 Expression arg = annotation.arguments.arguments[0]; |
| 245 if (arg is StringLiteral) { |
| 246 fileIdentifier = arg.stringValue; |
| 247 } else { |
| 248 throw new Exception( |
| 249 'Class `$clsName`: TopLevel argument must be a string' |
| 250 ' literal'); |
| 251 } |
| 252 } else if (annotation.arguments.arguments.length != 0) { |
| 253 throw new Exception( |
| 254 'Class `$clsName`: TopLevel requires 0 or 1 arguments'); |
| 255 } |
| 256 } |
| 257 } |
| 258 String doc = _getNodeDoc(lineInfo, decl); |
| 259 idlModel.ClassDeclaration cls = new idlModel.ClassDeclaration( |
| 260 doc, clsName, isTopLevel, fileIdentifier); |
| 261 _idl.classes[clsName] = cls; |
| 262 String expectedBase = 'base.SummaryClass'; |
| 263 if (decl.extendsClause == null || |
| 264 decl.extendsClause.superclass.name.name != expectedBase) { |
| 265 throw new Exception( |
| 266 'Class `$clsName` needs to extend `$expectedBase`'); |
| 267 } |
| 268 for (ClassMember classMember in decl.members) { |
| 269 if (classMember is MethodDeclaration && classMember.isGetter) { |
| 270 String desc = '$clsName.${classMember.name.name}'; |
| 271 TypeName type = classMember.returnType; |
| 272 if (type == null) { |
| 273 throw new Exception('Class member needs a type: $desc'); |
| 274 } |
| 275 bool isList = false; |
| 276 if (type.name.name == 'List' && |
| 277 type.typeArguments != null && |
| 278 type.typeArguments.arguments.length == 1) { |
| 279 isList = true; |
| 280 type = type.typeArguments.arguments[0]; |
| 281 } |
| 282 if (type.typeArguments != null) { |
| 283 throw new Exception('Cannot handle type arguments in `$type`'); |
| 284 } |
| 285 int id; |
| 286 bool isDeprecated = false; |
| 287 bool isInformative = false; |
| 288 for (Annotation annotation in classMember.metadata) { |
| 289 if (annotation.name.name == 'Id') { |
| 290 if (id != null) { |
| 291 throw new Exception( |
| 292 'Duplicate @id annotation ($classMember)'); |
| 293 } |
| 294 if (annotation.arguments.arguments.length != 1) { |
| 295 throw new Exception( |
| 296 '@Id must be passed exactly one argument ($desc)'); |
| 297 } |
| 298 Expression expression = annotation.arguments.arguments[0]; |
| 299 if (expression is IntegerLiteral) { |
| 300 id = expression.value; |
| 301 } else { |
| 302 throw new Exception( |
| 303 '@Id parameter must be an integer literal ($desc)'); |
| 304 } |
| 305 } else if (annotation.name.name == 'deprecated') { |
| 306 if (annotation.arguments != null) { |
| 307 throw new Exception('@deprecated does not take args ($desc)'); |
| 308 } |
| 309 isDeprecated = true; |
| 310 } else if (annotation.name.name == 'informative') { |
| 311 isInformative = true; |
| 312 } |
| 313 } |
| 314 if (id == null) { |
| 315 throw new Exception('Missing @id annotation ($desc)'); |
| 316 } |
| 317 String doc = _getNodeDoc(lineInfo, classMember); |
| 318 idlModel.FieldType fieldType = |
| 319 new idlModel.FieldType(type.name.name, isList); |
| 320 cls.allFields.add(new idlModel.FieldDeclaration( |
| 321 doc, |
| 322 classMember.name.name, |
| 323 fieldType, |
| 324 id, |
| 325 isDeprecated, |
| 326 isInformative)); |
| 327 } else if (classMember is ConstructorDeclaration && |
| 328 classMember.name.name == 'fromBuffer') { |
| 329 // Ignore `fromBuffer` declarations; they simply forward to the |
| 330 // read functions generated by [_generateReadFunction]. |
| 331 } else { |
| 332 throw new Exception('Unexpected class member `$classMember`'); |
| 333 } |
| 334 } |
| 335 } else if (decl is EnumDeclaration) { |
| 336 String doc = _getNodeDoc(lineInfo, decl); |
| 337 idlModel.EnumDeclaration enm = |
| 338 new idlModel.EnumDeclaration(doc, decl.name.name); |
| 339 _idl.enums[enm.name] = enm; |
| 340 for (EnumConstantDeclaration constDecl in decl.constants) { |
| 341 String doc = _getNodeDoc(lineInfo, constDecl); |
| 342 enm.values |
| 343 .add(new idlModel.EnumValueDeclaration(doc, constDecl.name.name)); |
| 344 } |
| 345 } else if (decl is TopLevelVariableDeclaration) { |
| 346 // Ignore top level variable declarations; they are present just to make |
| 347 // the IDL analyze without warnings. |
| 348 } else { |
| 349 throw new Exception('Unexpected declaration `$decl`'); |
| 350 } |
| 351 } |
| 352 } |
| 353 |
| 354 /** |
| 355 * Generate a string representing the FlatBuffer schema type which should be |
| 356 * used to represent [type]. |
| 357 */ |
| 358 String fbsType(idlModel.FieldType type) { |
| 359 String typeStr; |
| 360 switch (type.typeName) { |
| 361 case 'bool': |
| 362 typeStr = 'bool'; |
| 363 break; |
| 364 case 'double': |
| 365 typeStr = 'double'; |
| 366 break; |
| 367 case 'int': |
| 368 typeStr = 'uint'; |
| 369 break; |
| 370 case 'String': |
| 371 typeStr = 'string'; |
| 372 break; |
| 373 default: |
| 374 typeStr = type.typeName; |
| 375 break; |
| 376 } |
| 377 if (type.isList) { |
| 378 // FlatBuffers don't natively support a packed list of booleans, so we |
| 379 // treat it as a list of unsigned bytes, which is a compatible data |
| 380 // structure. |
| 381 if (typeStr == 'bool') { |
| 382 typeStr = 'ubyte'; |
| 383 } |
| 384 return '[$typeStr]'; |
| 385 } else { |
| 386 return typeStr; |
| 387 } |
| 388 } |
| 389 |
| 390 /** |
| 391 * Entry point to the code generator when generating the "format.fbs" file. |
| 392 */ |
| 393 void generateFlatBufferSchema() { |
| 394 outputHeader(); |
| 395 for (idlModel.EnumDeclaration enm in _idl.enums.values) { |
| 396 out(); |
| 397 outDoc(enm.documentation); |
| 398 out('enum ${enm.name} : byte {'); |
| 399 indent(() { |
| 400 for (int i = 0; i < enm.values.length; i++) { |
| 401 idlModel.EnumValueDeclaration value = enm.values[i]; |
| 402 if (i != 0) { |
| 403 out(); |
| 404 } |
| 405 String suffix = i < enm.values.length - 1 ? ',' : ''; |
| 406 outDoc(value.documentation); |
| 407 out('${value.name}$suffix'); |
| 408 } |
| 409 }); |
| 410 out('}'); |
| 411 } |
| 412 for (idlModel.ClassDeclaration cls in _idl.classes.values) { |
| 413 out(); |
| 414 outDoc(cls.documentation); |
| 415 out('table ${cls.name} {'); |
| 416 indent(() { |
| 417 for (int i = 0; i < cls.allFields.length; i++) { |
| 418 idlModel.FieldDeclaration field = cls.allFields[i]; |
| 419 if (i != 0) { |
| 420 out(); |
| 421 } |
| 422 outDoc(field.documentation); |
| 423 List<String> attributes = <String>['id: ${field.id}']; |
| 424 if (field.isDeprecated) { |
| 425 attributes.add('deprecated'); |
| 426 } |
| 427 String attrText = attributes.join(', '); |
| 428 out('${field.name}:${fbsType(field.type)} ($attrText);'); |
| 429 } |
| 430 }); |
| 431 out('}'); |
| 432 } |
| 433 out(); |
| 434 // Standard flatbuffers only support one root type. We support multiple |
| 435 // root types. For now work around this by forcing PackageBundle to be the |
| 436 // root type. TODO(paulberry): come up with a better solution. |
| 437 idlModel.ClassDeclaration rootType = _idl.classes['PackageBundle']; |
| 438 out('root_type ${rootType.name};'); |
| 439 if (rootType.fileIdentifier != null) { |
| 440 out(); |
| 441 out('file_identifier ${quoted(rootType.fileIdentifier)};'); |
| 442 } |
| 443 } |
| 444 |
| 445 /** |
| 446 * Entry point to the code generator when generating the "format.dart" file. |
| 447 */ |
| 448 void generateFormatCode() { |
| 449 outputHeader(); |
| 450 out('library analyzer.src.summary.format;'); |
| 451 out(); |
| 452 out("import 'flat_buffers.dart' as fb;"); |
| 453 out("import 'idl.dart' as idl;"); |
| 454 out("import 'dart:convert' as convert;"); |
| 455 out("import 'api_signature.dart' as api_sig;"); |
| 456 out(); |
| 457 for (idlModel.EnumDeclaration enm in _idl.enums.values) { |
| 458 _generateEnumReader(enm); |
| 459 out(); |
| 460 } |
| 461 for (idlModel.ClassDeclaration cls in _idl.classes.values) { |
| 462 _generateBuilder(cls); |
| 463 out(); |
| 464 if (cls.isTopLevel) { |
| 465 _generateReadFunction(cls); |
| 466 out(); |
| 467 } |
| 468 _generateReader(cls); |
| 469 out(); |
| 470 _generateImpl(cls); |
| 471 out(); |
| 472 _generateMixin(cls); |
| 473 out(); |
| 474 } |
| 475 } |
| 476 |
| 477 /** |
| 478 * Add the prefix `idl.` to a type name, unless that type name is the name of |
| 479 * a built-in type. |
| 480 */ |
| 481 String idlPrefix(String s) { |
| 482 switch (s) { |
| 483 case 'bool': |
| 484 case 'double': |
| 485 case 'int': |
| 486 case 'String': |
| 487 return s; |
| 488 default: |
| 489 return 'idl.$s'; |
| 490 } |
| 491 } |
| 492 |
| 493 /** |
| 494 * Execute [callback] with two spaces added to [_indentation]. |
| 495 */ |
| 496 void indent(void callback()) { |
| 497 String oldIndentation = _indentation; |
| 498 try { |
| 499 _indentation += ' '; |
| 500 callback(); |
| 501 } finally { |
| 502 _indentation = oldIndentation; |
| 503 } |
| 504 } |
| 505 |
| 506 /** |
| 507 * Add the string [s] to the output as a single line, indenting as |
| 508 * appropriate. |
| 509 */ |
| 510 void out([String s = '']) { |
| 511 if (s == '') { |
| 512 _outBuffer.writeln(''); |
| 513 } else { |
| 514 _outBuffer.writeln('$_indentation$s'); |
| 515 } |
| 516 } |
| 517 |
| 518 void outDoc(String documentation) { |
| 519 if (documentation != null) { |
| 520 documentation.split('\n').forEach(out); |
| 521 } |
| 522 } |
| 523 |
| 524 void outputHeader() { |
| 525 out('// Copyright (c) 2015, the Dart project authors. Please see the AUTHOR
S file'); |
| 526 out('// for details. All rights reserved. Use of this source code is governe
d by a'); |
| 527 out('// BSD-style license that can be found in the LICENSE file.'); |
| 528 out('//'); |
| 529 out('// This file has been automatically generated. Please do not edit it m
anually.'); |
| 530 out('// To regenerate the file, use the script "pkg/analyzer/tool/generate_f
iles".'); |
| 531 out(); |
| 532 } |
| 533 |
| 534 /** |
| 535 * Enclose [s] in quotes, escaping as necessary. |
| 536 */ |
| 537 String quoted(String s) { |
| 538 return JSON.encode(s); |
| 539 } |
| 540 |
| 541 void _generateBuilder(idlModel.ClassDeclaration cls) { |
| 542 String name = cls.name; |
| 543 String builderName = name + 'Builder'; |
| 544 String mixinName = '_${name}Mixin'; |
| 545 List<String> constructorParams = <String>[]; |
| 546 out('class $builderName extends Object with $mixinName ' |
| 547 'implements ${idlPrefix(name)} {'); |
| 548 indent(() { |
| 549 // Generate fields. |
| 550 for (idlModel.FieldDeclaration field in cls.fields) { |
| 551 String fieldName = field.name; |
| 552 idlModel.FieldType type = field.type; |
| 553 String typeStr = encodedType(type); |
| 554 out('$typeStr _$fieldName;'); |
| 555 } |
| 556 // Generate getters and setters. |
| 557 for (idlModel.FieldDeclaration field in cls.allFields) { |
| 558 String fieldName = field.name; |
| 559 idlModel.FieldType fieldType = field.type; |
| 560 String typeStr = encodedType(fieldType); |
| 561 String def = defaultValue(fieldType, true); |
| 562 String defSuffix = def == null ? '' : ' ??= $def'; |
| 563 out(); |
| 564 out('@override'); |
| 565 if (field.isDeprecated) { |
| 566 out('$typeStr get $fieldName => $_throwDeprecated;'); |
| 567 } else { |
| 568 out('$typeStr get $fieldName => _$fieldName$defSuffix;'); |
| 569 out(); |
| 570 outDoc(field.documentation); |
| 571 constructorParams.add('$typeStr $fieldName'); |
| 572 out('void set $fieldName($typeStr value) {'); |
| 573 indent(() { |
| 574 String stateFieldName = '_' + fieldName; |
| 575 // Validate that int(s) are non-negative. |
| 576 if (fieldType.typeName == 'int') { |
| 577 if (!fieldType.isList) { |
| 578 out('assert(value == null || value >= 0);'); |
| 579 } else { |
| 580 out('assert(value == null || value.every((e) => e >= 0));'); |
| 581 } |
| 582 } |
| 583 // Set the value. |
| 584 out('this.$stateFieldName = value;'); |
| 585 }); |
| 586 out('}'); |
| 587 } |
| 588 } |
| 589 // Generate constructor. |
| 590 out(); |
| 591 out('$builderName({${constructorParams.join(', ')}})'); |
| 592 List<idlModel.FieldDeclaration> fields = cls.fields.toList(); |
| 593 for (int i = 0; i < fields.length; i++) { |
| 594 idlModel.FieldDeclaration field = fields[i]; |
| 595 String prefix = i == 0 ? ' : ' : ' '; |
| 596 String suffix = i == fields.length - 1 ? ';' : ','; |
| 597 out('${prefix}_${field.name} = ${field.name}$suffix'); |
| 598 } |
| 599 // Generate flushInformative(). |
| 600 { |
| 601 out(); |
| 602 out('/**'); |
| 603 out(' * Flush [informative] data recursively.'); |
| 604 out(' */'); |
| 605 out('void flushInformative() {'); |
| 606 indent(() { |
| 607 for (idlModel.FieldDeclaration field in cls.fields) { |
| 608 idlModel.FieldType fieldType = field.type; |
| 609 String valueName = '_' + field.name; |
| 610 if (field.isInformative) { |
| 611 out('$valueName = null;'); |
| 612 } else if (_idl.classes.containsKey(fieldType.typeName)) { |
| 613 if (fieldType.isList) { |
| 614 out('$valueName?.forEach((b) => b.flushInformative());'); |
| 615 } else { |
| 616 out('$valueName?.flushInformative();'); |
| 617 } |
| 618 } |
| 619 } |
| 620 }); |
| 621 out('}'); |
| 622 } |
| 623 // Generate collectApiSignature(). |
| 624 { |
| 625 out(); |
| 626 out('/**'); |
| 627 out(' * Accumulate non-[informative] data into [signature].'); |
| 628 out(' */'); |
| 629 out('void collectApiSignature(api_sig.ApiSignature signature) {'); |
| 630 indent(() { |
| 631 List<idlModel.FieldDeclaration> sortedFields = cls.fields.toList() |
| 632 ..sort((idlModel.FieldDeclaration a, idlModel.FieldDeclaration b) => |
| 633 a.id.compareTo(b.id)); |
| 634 for (idlModel.FieldDeclaration field in sortedFields) { |
| 635 if (field.isInformative) { |
| 636 continue; |
| 637 } |
| 638 String ref = 'this._${field.name}'; |
| 639 if (field.type.isList) { |
| 640 out('if ($ref == null) {'); |
| 641 indent(() { |
| 642 out('signature.addInt(0);'); |
| 643 }); |
| 644 out('} else {'); |
| 645 indent(() { |
| 646 out('signature.addInt($ref.length);'); |
| 647 out('for (var x in $ref) {'); |
| 648 indent(() { |
| 649 _generateSignatureCall(field.type.typeName, 'x', false); |
| 650 }); |
| 651 out('}'); |
| 652 }); |
| 653 out('}'); |
| 654 } else { |
| 655 _generateSignatureCall(field.type.typeName, ref, true); |
| 656 } |
| 657 } |
| 658 }); |
| 659 out('}'); |
| 660 } |
| 661 // Generate finish. |
| 662 if (cls.isTopLevel) { |
| 663 out(); |
| 664 out('List<int> toBuffer() {'); |
| 665 indent(() { |
| 666 out('fb.Builder fbBuilder = new fb.Builder();'); |
| 667 String fileId = cls.fileIdentifier == null |
| 668 ? '' |
| 669 : ', ${quoted(cls.fileIdentifier)}'; |
| 670 out('return fbBuilder.finish(finish(fbBuilder)$fileId);'); |
| 671 }); |
| 672 out('}'); |
| 673 } |
| 674 out(); |
| 675 out('fb.Offset finish(fb.Builder fbBuilder) {'); |
| 676 indent(() { |
| 677 // Write objects and remember Offset(s). |
| 678 for (idlModel.FieldDeclaration field in cls.fields) { |
| 679 idlModel.FieldType fieldType = field.type; |
| 680 String offsetName = 'offset_' + field.name; |
| 681 if (fieldType.isList || |
| 682 fieldType.typeName == 'String' || |
| 683 _idl.classes.containsKey(fieldType.typeName)) { |
| 684 out('fb.Offset $offsetName;'); |
| 685 } |
| 686 } |
| 687 for (idlModel.FieldDeclaration field in cls.fields) { |
| 688 idlModel.FieldType fieldType = field.type; |
| 689 String valueName = '_' + field.name; |
| 690 String offsetName = 'offset_' + field.name; |
| 691 String condition; |
| 692 String writeCode; |
| 693 if (fieldType.isList) { |
| 694 condition = ' || $valueName.isEmpty'; |
| 695 if (_idl.classes.containsKey(fieldType.typeName)) { |
| 696 String itemCode = 'b.finish(fbBuilder)'; |
| 697 String listCode = '$valueName.map((b) => $itemCode).toList()'; |
| 698 writeCode = '$offsetName = fbBuilder.writeList($listCode);'; |
| 699 } else if (_idl.enums.containsKey(fieldType.typeName)) { |
| 700 String itemCode = 'b.index'; |
| 701 String listCode = '$valueName.map((b) => $itemCode).toList()'; |
| 702 writeCode = '$offsetName = fbBuilder.writeListUint8($listCode);'; |
| 703 } else if (fieldType.typeName == 'bool') { |
| 704 writeCode = '$offsetName = fbBuilder.writeListBool($valueName);'; |
| 705 } else if (fieldType.typeName == 'int') { |
| 706 writeCode = |
| 707 '$offsetName = fbBuilder.writeListUint32($valueName);'; |
| 708 } else if (fieldType.typeName == 'double') { |
| 709 writeCode = |
| 710 '$offsetName = fbBuilder.writeListFloat64($valueName);'; |
| 711 } else { |
| 712 assert(fieldType.typeName == 'String'); |
| 713 String itemCode = 'fbBuilder.writeString(b)'; |
| 714 String listCode = '$valueName.map((b) => $itemCode).toList()'; |
| 715 writeCode = '$offsetName = fbBuilder.writeList($listCode);'; |
| 716 } |
| 717 } else if (fieldType.typeName == 'String') { |
| 718 writeCode = '$offsetName = fbBuilder.writeString($valueName);'; |
| 719 } else if (_idl.classes.containsKey(fieldType.typeName)) { |
| 720 writeCode = '$offsetName = $valueName.finish(fbBuilder);'; |
| 721 } |
| 722 if (writeCode != null) { |
| 723 if (condition == null) { |
| 724 out('if ($valueName != null) {'); |
| 725 } else { |
| 726 out('if (!($valueName == null$condition)) {'); |
| 727 } |
| 728 indent(() { |
| 729 out(writeCode); |
| 730 }); |
| 731 out('}'); |
| 732 } |
| 733 } |
| 734 // Write the table. |
| 735 out('fbBuilder.startTable();'); |
| 736 for (idlModel.FieldDeclaration field in cls.fields) { |
| 737 int index = field.id; |
| 738 idlModel.FieldType fieldType = field.type; |
| 739 String valueName = '_' + field.name; |
| 740 String condition = '$valueName != null'; |
| 741 String writeCode; |
| 742 if (fieldType.isList || |
| 743 fieldType.typeName == 'String' || |
| 744 _idl.classes.containsKey(fieldType.typeName)) { |
| 745 String offsetName = 'offset_' + field.name; |
| 746 condition = '$offsetName != null'; |
| 747 writeCode = 'fbBuilder.addOffset($index, $offsetName);'; |
| 748 } else if (fieldType.typeName == 'bool') { |
| 749 condition = '$valueName == true'; |
| 750 writeCode = 'fbBuilder.addBool($index, true);'; |
| 751 } else if (fieldType.typeName == 'int') { |
| 752 condition += ' && $valueName != ${defaultValue(fieldType, true)}'; |
| 753 writeCode = 'fbBuilder.addUint32($index, $valueName);'; |
| 754 } else if (_idl.enums.containsKey(fieldType.typeName)) { |
| 755 condition += ' && $valueName != ${defaultValue(fieldType, true)}'; |
| 756 writeCode = 'fbBuilder.addUint8($index, $valueName.index);'; |
| 757 } |
| 758 if (writeCode == null) { |
| 759 throw new UnimplementedError('Writing type ${fieldType.typeName}'); |
| 760 } |
| 761 out('if ($condition) {'); |
| 762 indent(() { |
| 763 out(writeCode); |
| 764 }); |
| 765 out('}'); |
| 766 } |
| 767 out('return fbBuilder.endTable();'); |
| 768 }); |
| 769 out('}'); |
| 770 }); |
| 771 out('}'); |
| 772 } |
| 773 |
| 774 void _generateEnumReader(idlModel.EnumDeclaration enm) { |
| 775 String name = enm.name; |
| 776 String readerName = '_${name}Reader'; |
| 777 String count = '${idlPrefix(name)}.values.length'; |
| 778 String def = '${idlPrefix(name)}.${enm.values[0].name}'; |
| 779 out('class $readerName extends fb.Reader<${idlPrefix(name)}> {'); |
| 780 indent(() { |
| 781 out('const $readerName() : super();'); |
| 782 out(); |
| 783 out('@override'); |
| 784 out('int get size => 1;'); |
| 785 out(); |
| 786 out('@override'); |
| 787 out('${idlPrefix(name)} read(fb.BufferContext bc, int offset) {'); |
| 788 indent(() { |
| 789 out('int index = const fb.Uint8Reader().read(bc, offset);'); |
| 790 out('return index < $count ? ${idlPrefix(name)}.values[index] : $def;'); |
| 791 }); |
| 792 out('}'); |
| 793 }); |
| 794 out('}'); |
| 795 } |
| 796 |
| 797 void _generateImpl(idlModel.ClassDeclaration cls) { |
| 798 String name = cls.name; |
| 799 String implName = '_${name}Impl'; |
| 800 String mixinName = '_${name}Mixin'; |
| 801 out('class $implName extends Object with $mixinName' |
| 802 ' implements ${idlPrefix(name)} {'); |
| 803 indent(() { |
| 804 out('final fb.BufferContext _bc;'); |
| 805 out('final int _bcOffset;'); |
| 806 out(); |
| 807 out('$implName(this._bc, this._bcOffset);'); |
| 808 out(); |
| 809 // Write cache fields. |
| 810 for (idlModel.FieldDeclaration field in cls.fields) { |
| 811 String returnType = dartType(field.type); |
| 812 String fieldName = field.name; |
| 813 out('$returnType _$fieldName;'); |
| 814 } |
| 815 // Write getters. |
| 816 for (idlModel.FieldDeclaration field in cls.allFields) { |
| 817 int index = field.id; |
| 818 String fieldName = field.name; |
| 819 idlModel.FieldType type = field.type; |
| 820 String typeName = type.typeName; |
| 821 // Prepare "readCode" + "def" |
| 822 String readCode; |
| 823 String def = defaultValue(type, false); |
| 824 if (type.isList) { |
| 825 if (typeName == 'bool') { |
| 826 readCode = 'const fb.BoolListReader()'; |
| 827 } else if (typeName == 'int') { |
| 828 readCode = 'const fb.Uint32ListReader()'; |
| 829 } else if (typeName == 'double') { |
| 830 readCode = 'const fb.Float64ListReader()'; |
| 831 } else if (typeName == 'String') { |
| 832 String itemCode = 'const fb.StringReader()'; |
| 833 readCode = 'const fb.ListReader<String>($itemCode)'; |
| 834 } else if (_idl.classes.containsKey(typeName)) { |
| 835 String itemCode = 'const _${typeName}Reader()'; |
| 836 readCode = 'const fb.ListReader<${idlPrefix(typeName)}>($itemCode)'; |
| 837 } else { |
| 838 assert(_idl.enums.containsKey(typeName)); |
| 839 String itemCode = 'const _${typeName}Reader()'; |
| 840 readCode = 'const fb.ListReader<${idlPrefix(typeName)}>($itemCode)'; |
| 841 } |
| 842 } else if (typeName == 'bool') { |
| 843 readCode = 'const fb.BoolReader()'; |
| 844 } else if (typeName == 'int') { |
| 845 readCode = 'const fb.Uint32Reader()'; |
| 846 } else if (typeName == 'String') { |
| 847 readCode = 'const fb.StringReader()'; |
| 848 } else if (_idl.enums.containsKey(typeName)) { |
| 849 readCode = 'const _${typeName}Reader()'; |
| 850 } else if (_idl.classes.containsKey(typeName)) { |
| 851 readCode = 'const _${typeName}Reader()'; |
| 852 } |
| 853 assert(readCode != null); |
| 854 // Write the getter implementation. |
| 855 out(); |
| 856 out('@override'); |
| 857 String returnType = dartType(type); |
| 858 if (field.isDeprecated) { |
| 859 out('$returnType get $fieldName => $_throwDeprecated;'); |
| 860 } else { |
| 861 out('$returnType get $fieldName {'); |
| 862 indent(() { |
| 863 String readExpr = |
| 864 '$readCode.vTableGet(_bc, _bcOffset, $index, $def)'; |
| 865 out('_$fieldName ??= $readExpr;'); |
| 866 out('return _$fieldName;'); |
| 867 }); |
| 868 out('}'); |
| 869 } |
| 870 } |
| 871 }); |
| 872 out('}'); |
| 873 } |
| 874 |
| 875 void _generateMixin(idlModel.ClassDeclaration cls) { |
| 876 String name = cls.name; |
| 877 String mixinName = '_${name}Mixin'; |
| 878 out('abstract class $mixinName implements ${idlPrefix(name)} {'); |
| 879 indent(() { |
| 880 // Write toJson(). |
| 881 out('@override'); |
| 882 out('Map<String, Object> toJson() {'); |
| 883 indent(() { |
| 884 out('Map<String, Object> _result = <String, Object>{};'); |
| 885 for (idlModel.FieldDeclaration field in cls.fields) { |
| 886 String condition; |
| 887 if (field.type.isList) { |
| 888 condition = '${field.name}.isNotEmpty'; |
| 889 } else { |
| 890 condition = '${field.name} != ${defaultValue(field.type, false)}'; |
| 891 } |
| 892 _StringToString convertItem; |
| 893 if (_idl.classes.containsKey(field.type.typeName)) { |
| 894 convertItem = (String name) => '$name.toJson()'; |
| 895 } else if (_idl.enums.containsKey(field.type.typeName)) { |
| 896 // TODO(paulberry): it would be better to generate a const list of |
| 897 // strings so that we don't have to do this kludge. |
| 898 convertItem = (String name) => "$name.toString().split('.')[1]"; |
| 899 } else if (field.type.typeName == 'double') { |
| 900 convertItem = |
| 901 (String name) => '$name.isFinite ? $name : $name.toString()'; |
| 902 } |
| 903 String convertField; |
| 904 if (convertItem == null) { |
| 905 convertField = field.name; |
| 906 } else if (field.type.isList) { |
| 907 convertField = '${field.name}.map((_value) =>' |
| 908 ' ${convertItem('_value')}).toList()'; |
| 909 } else { |
| 910 convertField = convertItem(field.name); |
| 911 } |
| 912 String storeField = '_result[${quoted(field.name)}] = $convertField'; |
| 913 out('if ($condition) $storeField;'); |
| 914 } |
| 915 out('return _result;'); |
| 916 }); |
| 917 out('}'); |
| 918 out(); |
| 919 // Write toMap(). |
| 920 out('@override'); |
| 921 out('Map<String, Object> toMap() => {'); |
| 922 indent(() { |
| 923 for (idlModel.FieldDeclaration field in cls.fields) { |
| 924 String fieldName = field.name; |
| 925 out('${quoted(fieldName)}: $fieldName,'); |
| 926 } |
| 927 }); |
| 928 out('};'); |
| 929 out(); |
| 930 // Write toString(). |
| 931 out('@override'); |
| 932 out('String toString() => convert.JSON.encode(toJson());'); |
| 933 }); |
| 934 out('}'); |
| 935 } |
| 936 |
| 937 void _generateReader(idlModel.ClassDeclaration cls) { |
| 938 String name = cls.name; |
| 939 String readerName = '_${name}Reader'; |
| 940 String implName = '_${name}Impl'; |
| 941 out('class $readerName extends fb.TableReader<$implName> {'); |
| 942 indent(() { |
| 943 out('const $readerName();'); |
| 944 out(); |
| 945 out('@override'); |
| 946 out('$implName createObject(fb.BufferContext bc, int offset) => new $implN
ame(bc, offset);'); |
| 947 }); |
| 948 out('}'); |
| 949 } |
| 950 |
| 951 void _generateReadFunction(idlModel.ClassDeclaration cls) { |
| 952 String name = cls.name; |
| 953 out('${idlPrefix(name)} read$name(List<int> buffer) {'); |
| 954 indent(() { |
| 955 out('fb.BufferContext rootRef = new fb.BufferContext.fromBytes(buffer);'); |
| 956 out('return const _${name}Reader().read(rootRef, 0);'); |
| 957 }); |
| 958 out('}'); |
| 959 } |
| 960 |
| 961 /** |
| 962 * Generate a call to the appropriate method of [ApiSignature] for the type |
| 963 * [typeName], using the data named by [ref]. If [couldBeNull] is `true`, |
| 964 * generate code to handle the possibility that [ref] is `null` (substituting |
| 965 * in the appropriate default value). |
| 966 */ |
| 967 void _generateSignatureCall(String typeName, String ref, bool couldBeNull) { |
| 968 if (_idl.enums.containsKey(typeName)) { |
| 969 if (couldBeNull) { |
| 970 out('signature.addInt($ref == null ? 0 : $ref.index);'); |
| 971 } else { |
| 972 out('signature.addInt($ref.index);'); |
| 973 } |
| 974 } else if (_idl.classes.containsKey(typeName)) { |
| 975 if (couldBeNull) { |
| 976 out('signature.addBool($ref != null);'); |
| 977 } |
| 978 out('$ref?.collectApiSignature(signature);'); |
| 979 } else { |
| 980 switch (typeName) { |
| 981 case 'String': |
| 982 if (couldBeNull) { |
| 983 ref += " ?? ''"; |
| 984 } |
| 985 out("signature.addString($ref);"); |
| 986 break; |
| 987 case 'int': |
| 988 if (couldBeNull) { |
| 989 ref += ' ?? 0'; |
| 990 } |
| 991 out('signature.addInt($ref);'); |
| 992 break; |
| 993 case 'bool': |
| 994 if (couldBeNull) { |
| 995 ref += ' == true'; |
| 996 } |
| 997 out('signature.addBool($ref);'); |
| 998 break; |
| 999 case 'double': |
| 1000 if (couldBeNull) { |
| 1001 ref += ' ?? 0.0'; |
| 1002 } |
| 1003 out('signature.addDouble($ref);'); |
| 1004 break; |
| 1005 default: |
| 1006 throw "Don't know how to generate signature call for $typeName"; |
| 1007 } |
| 1008 } |
| 1009 } |
| 1010 |
| 1011 /** |
| 1012 * Return the documentation text of the given [node], or `null` if the [node] |
| 1013 * does not have a comment. Each line is `\n` separated. |
| 1014 */ |
| 1015 String _getNodeDoc(LineInfo lineInfo, AnnotatedNode node) { |
| 1016 Comment comment = node.documentationComment; |
| 1017 if (comment != null && |
| 1018 comment.isDocumentation && |
| 1019 comment.tokens.length == 1 && |
| 1020 comment.tokens.first.type == TokenType.MULTI_LINE_COMMENT) { |
| 1021 Token token = comment.tokens.first; |
| 1022 int column = lineInfo.getLocation(token.offset).columnNumber; |
| 1023 String indent = ' ' * (column - 1); |
| 1024 return token.lexeme.split('\n').map((String line) { |
| 1025 if (line.startsWith(indent)) { |
| 1026 line = line.substring(indent.length); |
| 1027 } |
| 1028 return line; |
| 1029 }).join('\n'); |
| 1030 } |
| 1031 return null; |
| 1032 } |
| 1033 } |
| OLD | NEW |