| Index: packages/analyzer/tool/summary/generate.dart
|
| diff --git a/packages/analyzer/tool/summary/generate.dart b/packages/analyzer/tool/summary/generate.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d3fd86372118f1a537ee09e71d8f2b0e9be5a735
|
| --- /dev/null
|
| +++ b/packages/analyzer/tool/summary/generate.dart
|
| @@ -0,0 +1,1033 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +/**
|
| + * This file contains code to generate serialization/deserialization logic for
|
| + * summaries based on an "IDL" description of the summary format (written in
|
| + * stylized Dart).
|
| + *
|
| + * For each class in the "IDL" input, two corresponding classes are generated:
|
| + * - A class with the same name which represents deserialized summary data in
|
| + * memory. This class has read-only semantics.
|
| + * - A "builder" class which can be used to generate serialized summary data.
|
| + * This class has write-only semantics.
|
| + *
|
| + * Each of the "builder" classes has a single `finish` method which writes
|
| + * the entity being built into the given FlatBuffer and returns the `Offset`
|
| + * reference to it.
|
| + */
|
| +library analyzer.tool.summary.generate;
|
| +
|
| +import 'dart:convert';
|
| +import 'dart:io' hide File;
|
| +
|
| +import 'package:analyzer/analyzer.dart';
|
| +import 'package:analyzer/dart/ast/token.dart';
|
| +import 'package:analyzer/error/listener.dart';
|
| +import 'package:analyzer/file_system/file_system.dart';
|
| +import 'package:analyzer/file_system/physical_file_system.dart';
|
| +import 'package:analyzer/src/codegen/tools.dart';
|
| +import 'package:analyzer/src/dart/scanner/reader.dart';
|
| +import 'package:analyzer/src/dart/scanner/scanner.dart';
|
| +import 'package:analyzer/src/generated/parser.dart';
|
| +import 'package:analyzer/src/generated/source.dart';
|
| +import 'package:path/path.dart';
|
| +
|
| +import 'idl_model.dart' as idlModel;
|
| +
|
| +main() {
|
| + String script = Platform.script.toFilePath(windows: Platform.isWindows);
|
| + String pkgPath = normalize(join(dirname(script), '..', '..'));
|
| + GeneratedContent.generateAll(pkgPath, allTargets);
|
| +}
|
| +
|
| +final List<GeneratedContent> allTargets = <GeneratedContent>[
|
| + formatTarget,
|
| + schemaTarget
|
| +];
|
| +
|
| +final GeneratedFile formatTarget =
|
| + new GeneratedFile('lib/src/summary/format.dart', (String pkgPath) {
|
| + _CodeGenerator codeGenerator = new _CodeGenerator(pkgPath);
|
| + codeGenerator.generateFormatCode();
|
| + return codeGenerator._outBuffer.toString();
|
| +});
|
| +
|
| +final GeneratedFile schemaTarget =
|
| + new GeneratedFile('lib/src/summary/format.fbs', (String pkgPath) {
|
| + _CodeGenerator codeGenerator = new _CodeGenerator(pkgPath);
|
| + codeGenerator.generateFlatBufferSchema();
|
| + return codeGenerator._outBuffer.toString();
|
| +});
|
| +
|
| +typedef String _StringToString(String s);
|
| +
|
| +class _CodeGenerator {
|
| + static const String _throwDeprecated =
|
| + "throw new UnimplementedError('attempt to access deprecated field')";
|
| +
|
| + /**
|
| + * Buffer in which generated code is accumulated.
|
| + */
|
| + final StringBuffer _outBuffer = new StringBuffer();
|
| +
|
| + /**
|
| + * Current indentation level.
|
| + */
|
| + String _indentation = '';
|
| +
|
| + /**
|
| + * Semantic model of the "IDL" input file.
|
| + */
|
| + idlModel.Idl _idl;
|
| +
|
| + _CodeGenerator(String pkgPath) {
|
| + // Parse the input "IDL" file.
|
| + PhysicalResourceProvider provider = new PhysicalResourceProvider(
|
| + PhysicalResourceProvider.NORMALIZE_EOL_ALWAYS);
|
| + String idlPath = join(pkgPath, 'lib', 'src', 'summary', 'idl.dart');
|
| + File idlFile = provider.getFile(idlPath);
|
| + Source idlSource = provider.getFile(idlPath).createSource();
|
| + String idlText = idlFile.readAsStringSync();
|
| + BooleanErrorListener errorListener = new BooleanErrorListener();
|
| + CharacterReader idlReader = new CharSequenceReader(idlText);
|
| + Scanner scanner = new Scanner(idlSource, idlReader, errorListener);
|
| + Token tokenStream = scanner.tokenize();
|
| + LineInfo lineInfo = new LineInfo(scanner.lineStarts);
|
| + Parser parser = new Parser(idlSource, new BooleanErrorListener());
|
| + CompilationUnit idlParsed = parser.parseCompilationUnit(tokenStream);
|
| + // Extract a description of the IDL and make sure it is valid.
|
| + extractIdl(lineInfo, idlParsed);
|
| + checkIdl();
|
| + }
|
| +
|
| + /**
|
| + * Perform basic sanity checking of the IDL (over and above that done by
|
| + * [extractIdl]).
|
| + */
|
| + void checkIdl() {
|
| + _idl.classes.forEach((String name, idlModel.ClassDeclaration cls) {
|
| + if (cls.fileIdentifier != null) {
|
| + if (cls.fileIdentifier.length != 4) {
|
| + throw new Exception('$name: file identifier must be 4 characters');
|
| + }
|
| + for (int i = 0; i < cls.fileIdentifier.length; i++) {
|
| + if (cls.fileIdentifier.codeUnitAt(i) >= 256) {
|
| + throw new Exception(
|
| + '$name: file identifier must be encodable as Latin-1');
|
| + }
|
| + }
|
| + }
|
| + Map<int, String> idsUsed = <int, String>{};
|
| + for (idlModel.FieldDeclaration field in cls.allFields) {
|
| + String fieldName = field.name;
|
| + idlModel.FieldType type = field.type;
|
| + if (type.isList) {
|
| + if (_idl.classes.containsKey(type.typeName)) {
|
| + // List of classes is ok
|
| + } else if (_idl.enums.containsKey(type.typeName)) {
|
| + // List of enums is ok
|
| + } else if (type.typeName == 'bool') {
|
| + // List of booleans is ok
|
| + } else if (type.typeName == 'int') {
|
| + // List of ints is ok
|
| + } else if (type.typeName == 'double') {
|
| + // List of doubles is ok
|
| + } else if (type.typeName == 'String') {
|
| + // List of strings is ok
|
| + } else {
|
| + throw new Exception(
|
| + '$name.$fieldName: illegal type (list of ${type.typeName})');
|
| + }
|
| + }
|
| + if (idsUsed.containsKey(field.id)) {
|
| + throw new Exception('$name.$fieldName: id ${field.id} already used by'
|
| + ' ${idsUsed[field.id]}');
|
| + }
|
| + idsUsed[field.id] = fieldName;
|
| + }
|
| + for (int i = 0; i < idsUsed.length; i++) {
|
| + if (!idsUsed.containsKey(i)) {
|
| + throw new Exception('$name: no field uses id $i');
|
| + }
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Generate a string representing the Dart type which should be used to
|
| + * represent [type] when deserialized.
|
| + */
|
| + String dartType(idlModel.FieldType type) {
|
| + String baseType = idlPrefix(type.typeName);
|
| + if (type.isList) {
|
| + return 'List<$baseType>';
|
| + } else {
|
| + return baseType;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Generate a Dart expression representing the default value for a field
|
| + * having the given [type], or `null` if there is no default value.
|
| + *
|
| + * If [builder] is `true`, the returned type should be appropriate for use in
|
| + * a builder class.
|
| + */
|
| + String defaultValue(idlModel.FieldType type, bool builder) {
|
| + if (type.isList) {
|
| + if (builder) {
|
| + idlModel.FieldType elementType =
|
| + new idlModel.FieldType(type.typeName, false);
|
| + return '<${encodedType(elementType)}>[]';
|
| + } else {
|
| + return 'const <${idlPrefix(type.typeName)}>[]';
|
| + }
|
| + } else if (_idl.enums.containsKey(type.typeName)) {
|
| + return '${idlPrefix(type.typeName)}.'
|
| + '${_idl.enums[type.typeName].values[0].name}';
|
| + } else if (type.typeName == 'int') {
|
| + return '0';
|
| + } else if (type.typeName == 'String') {
|
| + return "''";
|
| + } else if (type.typeName == 'bool') {
|
| + return 'false';
|
| + } else {
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Generate a string representing the Dart type which should be used to
|
| + * represent [type] while building a serialized data structure.
|
| + */
|
| + String encodedType(idlModel.FieldType type) {
|
| + String typeStr;
|
| + if (_idl.classes.containsKey(type.typeName)) {
|
| + typeStr = '${type.typeName}Builder';
|
| + } else {
|
| + typeStr = idlPrefix(type.typeName);
|
| + }
|
| + if (type.isList) {
|
| + return 'List<$typeStr>';
|
| + } else {
|
| + return typeStr;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Process the AST in [idlParsed] and store the resulting semantic model in
|
| + * [_idl]. Also perform some error checking.
|
| + */
|
| + void extractIdl(LineInfo lineInfo, CompilationUnit idlParsed) {
|
| + _idl = new idlModel.Idl();
|
| + for (CompilationUnitMember decl in idlParsed.declarations) {
|
| + if (decl is ClassDeclaration) {
|
| + bool isTopLevel = false;
|
| + String fileIdentifier;
|
| + String clsName = decl.name.name;
|
| + for (Annotation annotation in decl.metadata) {
|
| + if (annotation.arguments != null &&
|
| + annotation.name.name == 'TopLevel' &&
|
| + annotation.constructorName == null) {
|
| + isTopLevel = true;
|
| + if (annotation.arguments == null) {
|
| + throw new Exception(
|
| + 'Class `$clsName`: TopLevel requires parenthesis');
|
| + }
|
| + if (annotation.constructorName != null) {
|
| + throw new Exception(
|
| + "Class `$clsName`: TopLevel doesn't have named constructors");
|
| + }
|
| + if (annotation.arguments.arguments.length == 1) {
|
| + Expression arg = annotation.arguments.arguments[0];
|
| + if (arg is StringLiteral) {
|
| + fileIdentifier = arg.stringValue;
|
| + } else {
|
| + throw new Exception(
|
| + 'Class `$clsName`: TopLevel argument must be a string'
|
| + ' literal');
|
| + }
|
| + } else if (annotation.arguments.arguments.length != 0) {
|
| + throw new Exception(
|
| + 'Class `$clsName`: TopLevel requires 0 or 1 arguments');
|
| + }
|
| + }
|
| + }
|
| + String doc = _getNodeDoc(lineInfo, decl);
|
| + idlModel.ClassDeclaration cls = new idlModel.ClassDeclaration(
|
| + doc, clsName, isTopLevel, fileIdentifier);
|
| + _idl.classes[clsName] = cls;
|
| + String expectedBase = 'base.SummaryClass';
|
| + if (decl.extendsClause == null ||
|
| + decl.extendsClause.superclass.name.name != expectedBase) {
|
| + throw new Exception(
|
| + 'Class `$clsName` needs to extend `$expectedBase`');
|
| + }
|
| + for (ClassMember classMember in decl.members) {
|
| + if (classMember is MethodDeclaration && classMember.isGetter) {
|
| + String desc = '$clsName.${classMember.name.name}';
|
| + TypeName type = classMember.returnType;
|
| + if (type == null) {
|
| + throw new Exception('Class member needs a type: $desc');
|
| + }
|
| + bool isList = false;
|
| + if (type.name.name == 'List' &&
|
| + type.typeArguments != null &&
|
| + type.typeArguments.arguments.length == 1) {
|
| + isList = true;
|
| + type = type.typeArguments.arguments[0];
|
| + }
|
| + if (type.typeArguments != null) {
|
| + throw new Exception('Cannot handle type arguments in `$type`');
|
| + }
|
| + int id;
|
| + bool isDeprecated = false;
|
| + bool isInformative = false;
|
| + for (Annotation annotation in classMember.metadata) {
|
| + if (annotation.name.name == 'Id') {
|
| + if (id != null) {
|
| + throw new Exception(
|
| + 'Duplicate @id annotation ($classMember)');
|
| + }
|
| + if (annotation.arguments.arguments.length != 1) {
|
| + throw new Exception(
|
| + '@Id must be passed exactly one argument ($desc)');
|
| + }
|
| + Expression expression = annotation.arguments.arguments[0];
|
| + if (expression is IntegerLiteral) {
|
| + id = expression.value;
|
| + } else {
|
| + throw new Exception(
|
| + '@Id parameter must be an integer literal ($desc)');
|
| + }
|
| + } else if (annotation.name.name == 'deprecated') {
|
| + if (annotation.arguments != null) {
|
| + throw new Exception('@deprecated does not take args ($desc)');
|
| + }
|
| + isDeprecated = true;
|
| + } else if (annotation.name.name == 'informative') {
|
| + isInformative = true;
|
| + }
|
| + }
|
| + if (id == null) {
|
| + throw new Exception('Missing @id annotation ($desc)');
|
| + }
|
| + String doc = _getNodeDoc(lineInfo, classMember);
|
| + idlModel.FieldType fieldType =
|
| + new idlModel.FieldType(type.name.name, isList);
|
| + cls.allFields.add(new idlModel.FieldDeclaration(
|
| + doc,
|
| + classMember.name.name,
|
| + fieldType,
|
| + id,
|
| + isDeprecated,
|
| + isInformative));
|
| + } else if (classMember is ConstructorDeclaration &&
|
| + classMember.name.name == 'fromBuffer') {
|
| + // Ignore `fromBuffer` declarations; they simply forward to the
|
| + // read functions generated by [_generateReadFunction].
|
| + } else {
|
| + throw new Exception('Unexpected class member `$classMember`');
|
| + }
|
| + }
|
| + } else if (decl is EnumDeclaration) {
|
| + String doc = _getNodeDoc(lineInfo, decl);
|
| + idlModel.EnumDeclaration enm =
|
| + new idlModel.EnumDeclaration(doc, decl.name.name);
|
| + _idl.enums[enm.name] = enm;
|
| + for (EnumConstantDeclaration constDecl in decl.constants) {
|
| + String doc = _getNodeDoc(lineInfo, constDecl);
|
| + enm.values
|
| + .add(new idlModel.EnumValueDeclaration(doc, constDecl.name.name));
|
| + }
|
| + } else if (decl is TopLevelVariableDeclaration) {
|
| + // Ignore top level variable declarations; they are present just to make
|
| + // the IDL analyze without warnings.
|
| + } else {
|
| + throw new Exception('Unexpected declaration `$decl`');
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Generate a string representing the FlatBuffer schema type which should be
|
| + * used to represent [type].
|
| + */
|
| + String fbsType(idlModel.FieldType type) {
|
| + String typeStr;
|
| + switch (type.typeName) {
|
| + case 'bool':
|
| + typeStr = 'bool';
|
| + break;
|
| + case 'double':
|
| + typeStr = 'double';
|
| + break;
|
| + case 'int':
|
| + typeStr = 'uint';
|
| + break;
|
| + case 'String':
|
| + typeStr = 'string';
|
| + break;
|
| + default:
|
| + typeStr = type.typeName;
|
| + break;
|
| + }
|
| + if (type.isList) {
|
| + // FlatBuffers don't natively support a packed list of booleans, so we
|
| + // treat it as a list of unsigned bytes, which is a compatible data
|
| + // structure.
|
| + if (typeStr == 'bool') {
|
| + typeStr = 'ubyte';
|
| + }
|
| + return '[$typeStr]';
|
| + } else {
|
| + return typeStr;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Entry point to the code generator when generating the "format.fbs" file.
|
| + */
|
| + void generateFlatBufferSchema() {
|
| + outputHeader();
|
| + for (idlModel.EnumDeclaration enm in _idl.enums.values) {
|
| + out();
|
| + outDoc(enm.documentation);
|
| + out('enum ${enm.name} : byte {');
|
| + indent(() {
|
| + for (int i = 0; i < enm.values.length; i++) {
|
| + idlModel.EnumValueDeclaration value = enm.values[i];
|
| + if (i != 0) {
|
| + out();
|
| + }
|
| + String suffix = i < enm.values.length - 1 ? ',' : '';
|
| + outDoc(value.documentation);
|
| + out('${value.name}$suffix');
|
| + }
|
| + });
|
| + out('}');
|
| + }
|
| + for (idlModel.ClassDeclaration cls in _idl.classes.values) {
|
| + out();
|
| + outDoc(cls.documentation);
|
| + out('table ${cls.name} {');
|
| + indent(() {
|
| + for (int i = 0; i < cls.allFields.length; i++) {
|
| + idlModel.FieldDeclaration field = cls.allFields[i];
|
| + if (i != 0) {
|
| + out();
|
| + }
|
| + outDoc(field.documentation);
|
| + List<String> attributes = <String>['id: ${field.id}'];
|
| + if (field.isDeprecated) {
|
| + attributes.add('deprecated');
|
| + }
|
| + String attrText = attributes.join(', ');
|
| + out('${field.name}:${fbsType(field.type)} ($attrText);');
|
| + }
|
| + });
|
| + out('}');
|
| + }
|
| + out();
|
| + // Standard flatbuffers only support one root type. We support multiple
|
| + // root types. For now work around this by forcing PackageBundle to be the
|
| + // root type. TODO(paulberry): come up with a better solution.
|
| + idlModel.ClassDeclaration rootType = _idl.classes['PackageBundle'];
|
| + out('root_type ${rootType.name};');
|
| + if (rootType.fileIdentifier != null) {
|
| + out();
|
| + out('file_identifier ${quoted(rootType.fileIdentifier)};');
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Entry point to the code generator when generating the "format.dart" file.
|
| + */
|
| + void generateFormatCode() {
|
| + outputHeader();
|
| + out('library analyzer.src.summary.format;');
|
| + out();
|
| + out("import 'flat_buffers.dart' as fb;");
|
| + out("import 'idl.dart' as idl;");
|
| + out("import 'dart:convert' as convert;");
|
| + out("import 'api_signature.dart' as api_sig;");
|
| + out();
|
| + for (idlModel.EnumDeclaration enm in _idl.enums.values) {
|
| + _generateEnumReader(enm);
|
| + out();
|
| + }
|
| + for (idlModel.ClassDeclaration cls in _idl.classes.values) {
|
| + _generateBuilder(cls);
|
| + out();
|
| + if (cls.isTopLevel) {
|
| + _generateReadFunction(cls);
|
| + out();
|
| + }
|
| + _generateReader(cls);
|
| + out();
|
| + _generateImpl(cls);
|
| + out();
|
| + _generateMixin(cls);
|
| + out();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Add the prefix `idl.` to a type name, unless that type name is the name of
|
| + * a built-in type.
|
| + */
|
| + String idlPrefix(String s) {
|
| + switch (s) {
|
| + case 'bool':
|
| + case 'double':
|
| + case 'int':
|
| + case 'String':
|
| + return s;
|
| + default:
|
| + return 'idl.$s';
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Execute [callback] with two spaces added to [_indentation].
|
| + */
|
| + void indent(void callback()) {
|
| + String oldIndentation = _indentation;
|
| + try {
|
| + _indentation += ' ';
|
| + callback();
|
| + } finally {
|
| + _indentation = oldIndentation;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Add the string [s] to the output as a single line, indenting as
|
| + * appropriate.
|
| + */
|
| + void out([String s = '']) {
|
| + if (s == '') {
|
| + _outBuffer.writeln('');
|
| + } else {
|
| + _outBuffer.writeln('$_indentation$s');
|
| + }
|
| + }
|
| +
|
| + void outDoc(String documentation) {
|
| + if (documentation != null) {
|
| + documentation.split('\n').forEach(out);
|
| + }
|
| + }
|
| +
|
| + void outputHeader() {
|
| + out('// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file');
|
| + out('// for details. All rights reserved. Use of this source code is governed by a');
|
| + out('// BSD-style license that can be found in the LICENSE file.');
|
| + out('//');
|
| + out('// This file has been automatically generated. Please do not edit it manually.');
|
| + out('// To regenerate the file, use the script "pkg/analyzer/tool/generate_files".');
|
| + out();
|
| + }
|
| +
|
| + /**
|
| + * Enclose [s] in quotes, escaping as necessary.
|
| + */
|
| + String quoted(String s) {
|
| + return JSON.encode(s);
|
| + }
|
| +
|
| + void _generateBuilder(idlModel.ClassDeclaration cls) {
|
| + String name = cls.name;
|
| + String builderName = name + 'Builder';
|
| + String mixinName = '_${name}Mixin';
|
| + List<String> constructorParams = <String>[];
|
| + out('class $builderName extends Object with $mixinName '
|
| + 'implements ${idlPrefix(name)} {');
|
| + indent(() {
|
| + // Generate fields.
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + String fieldName = field.name;
|
| + idlModel.FieldType type = field.type;
|
| + String typeStr = encodedType(type);
|
| + out('$typeStr _$fieldName;');
|
| + }
|
| + // Generate getters and setters.
|
| + for (idlModel.FieldDeclaration field in cls.allFields) {
|
| + String fieldName = field.name;
|
| + idlModel.FieldType fieldType = field.type;
|
| + String typeStr = encodedType(fieldType);
|
| + String def = defaultValue(fieldType, true);
|
| + String defSuffix = def == null ? '' : ' ??= $def';
|
| + out();
|
| + out('@override');
|
| + if (field.isDeprecated) {
|
| + out('$typeStr get $fieldName => $_throwDeprecated;');
|
| + } else {
|
| + out('$typeStr get $fieldName => _$fieldName$defSuffix;');
|
| + out();
|
| + outDoc(field.documentation);
|
| + constructorParams.add('$typeStr $fieldName');
|
| + out('void set $fieldName($typeStr value) {');
|
| + indent(() {
|
| + String stateFieldName = '_' + fieldName;
|
| + // Validate that int(s) are non-negative.
|
| + if (fieldType.typeName == 'int') {
|
| + if (!fieldType.isList) {
|
| + out('assert(value == null || value >= 0);');
|
| + } else {
|
| + out('assert(value == null || value.every((e) => e >= 0));');
|
| + }
|
| + }
|
| + // Set the value.
|
| + out('this.$stateFieldName = value;');
|
| + });
|
| + out('}');
|
| + }
|
| + }
|
| + // Generate constructor.
|
| + out();
|
| + out('$builderName({${constructorParams.join(', ')}})');
|
| + List<idlModel.FieldDeclaration> fields = cls.fields.toList();
|
| + for (int i = 0; i < fields.length; i++) {
|
| + idlModel.FieldDeclaration field = fields[i];
|
| + String prefix = i == 0 ? ' : ' : ' ';
|
| + String suffix = i == fields.length - 1 ? ';' : ',';
|
| + out('${prefix}_${field.name} = ${field.name}$suffix');
|
| + }
|
| + // Generate flushInformative().
|
| + {
|
| + out();
|
| + out('/**');
|
| + out(' * Flush [informative] data recursively.');
|
| + out(' */');
|
| + out('void flushInformative() {');
|
| + indent(() {
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + idlModel.FieldType fieldType = field.type;
|
| + String valueName = '_' + field.name;
|
| + if (field.isInformative) {
|
| + out('$valueName = null;');
|
| + } else if (_idl.classes.containsKey(fieldType.typeName)) {
|
| + if (fieldType.isList) {
|
| + out('$valueName?.forEach((b) => b.flushInformative());');
|
| + } else {
|
| + out('$valueName?.flushInformative();');
|
| + }
|
| + }
|
| + }
|
| + });
|
| + out('}');
|
| + }
|
| + // Generate collectApiSignature().
|
| + {
|
| + out();
|
| + out('/**');
|
| + out(' * Accumulate non-[informative] data into [signature].');
|
| + out(' */');
|
| + out('void collectApiSignature(api_sig.ApiSignature signature) {');
|
| + indent(() {
|
| + List<idlModel.FieldDeclaration> sortedFields = cls.fields.toList()
|
| + ..sort((idlModel.FieldDeclaration a, idlModel.FieldDeclaration b) =>
|
| + a.id.compareTo(b.id));
|
| + for (idlModel.FieldDeclaration field in sortedFields) {
|
| + if (field.isInformative) {
|
| + continue;
|
| + }
|
| + String ref = 'this._${field.name}';
|
| + if (field.type.isList) {
|
| + out('if ($ref == null) {');
|
| + indent(() {
|
| + out('signature.addInt(0);');
|
| + });
|
| + out('} else {');
|
| + indent(() {
|
| + out('signature.addInt($ref.length);');
|
| + out('for (var x in $ref) {');
|
| + indent(() {
|
| + _generateSignatureCall(field.type.typeName, 'x', false);
|
| + });
|
| + out('}');
|
| + });
|
| + out('}');
|
| + } else {
|
| + _generateSignatureCall(field.type.typeName, ref, true);
|
| + }
|
| + }
|
| + });
|
| + out('}');
|
| + }
|
| + // Generate finish.
|
| + if (cls.isTopLevel) {
|
| + out();
|
| + out('List<int> toBuffer() {');
|
| + indent(() {
|
| + out('fb.Builder fbBuilder = new fb.Builder();');
|
| + String fileId = cls.fileIdentifier == null
|
| + ? ''
|
| + : ', ${quoted(cls.fileIdentifier)}';
|
| + out('return fbBuilder.finish(finish(fbBuilder)$fileId);');
|
| + });
|
| + out('}');
|
| + }
|
| + out();
|
| + out('fb.Offset finish(fb.Builder fbBuilder) {');
|
| + indent(() {
|
| + // Write objects and remember Offset(s).
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + idlModel.FieldType fieldType = field.type;
|
| + String offsetName = 'offset_' + field.name;
|
| + if (fieldType.isList ||
|
| + fieldType.typeName == 'String' ||
|
| + _idl.classes.containsKey(fieldType.typeName)) {
|
| + out('fb.Offset $offsetName;');
|
| + }
|
| + }
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + idlModel.FieldType fieldType = field.type;
|
| + String valueName = '_' + field.name;
|
| + String offsetName = 'offset_' + field.name;
|
| + String condition;
|
| + String writeCode;
|
| + if (fieldType.isList) {
|
| + condition = ' || $valueName.isEmpty';
|
| + if (_idl.classes.containsKey(fieldType.typeName)) {
|
| + String itemCode = 'b.finish(fbBuilder)';
|
| + String listCode = '$valueName.map((b) => $itemCode).toList()';
|
| + writeCode = '$offsetName = fbBuilder.writeList($listCode);';
|
| + } else if (_idl.enums.containsKey(fieldType.typeName)) {
|
| + String itemCode = 'b.index';
|
| + String listCode = '$valueName.map((b) => $itemCode).toList()';
|
| + writeCode = '$offsetName = fbBuilder.writeListUint8($listCode);';
|
| + } else if (fieldType.typeName == 'bool') {
|
| + writeCode = '$offsetName = fbBuilder.writeListBool($valueName);';
|
| + } else if (fieldType.typeName == 'int') {
|
| + writeCode =
|
| + '$offsetName = fbBuilder.writeListUint32($valueName);';
|
| + } else if (fieldType.typeName == 'double') {
|
| + writeCode =
|
| + '$offsetName = fbBuilder.writeListFloat64($valueName);';
|
| + } else {
|
| + assert(fieldType.typeName == 'String');
|
| + String itemCode = 'fbBuilder.writeString(b)';
|
| + String listCode = '$valueName.map((b) => $itemCode).toList()';
|
| + writeCode = '$offsetName = fbBuilder.writeList($listCode);';
|
| + }
|
| + } else if (fieldType.typeName == 'String') {
|
| + writeCode = '$offsetName = fbBuilder.writeString($valueName);';
|
| + } else if (_idl.classes.containsKey(fieldType.typeName)) {
|
| + writeCode = '$offsetName = $valueName.finish(fbBuilder);';
|
| + }
|
| + if (writeCode != null) {
|
| + if (condition == null) {
|
| + out('if ($valueName != null) {');
|
| + } else {
|
| + out('if (!($valueName == null$condition)) {');
|
| + }
|
| + indent(() {
|
| + out(writeCode);
|
| + });
|
| + out('}');
|
| + }
|
| + }
|
| + // Write the table.
|
| + out('fbBuilder.startTable();');
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + int index = field.id;
|
| + idlModel.FieldType fieldType = field.type;
|
| + String valueName = '_' + field.name;
|
| + String condition = '$valueName != null';
|
| + String writeCode;
|
| + if (fieldType.isList ||
|
| + fieldType.typeName == 'String' ||
|
| + _idl.classes.containsKey(fieldType.typeName)) {
|
| + String offsetName = 'offset_' + field.name;
|
| + condition = '$offsetName != null';
|
| + writeCode = 'fbBuilder.addOffset($index, $offsetName);';
|
| + } else if (fieldType.typeName == 'bool') {
|
| + condition = '$valueName == true';
|
| + writeCode = 'fbBuilder.addBool($index, true);';
|
| + } else if (fieldType.typeName == 'int') {
|
| + condition += ' && $valueName != ${defaultValue(fieldType, true)}';
|
| + writeCode = 'fbBuilder.addUint32($index, $valueName);';
|
| + } else if (_idl.enums.containsKey(fieldType.typeName)) {
|
| + condition += ' && $valueName != ${defaultValue(fieldType, true)}';
|
| + writeCode = 'fbBuilder.addUint8($index, $valueName.index);';
|
| + }
|
| + if (writeCode == null) {
|
| + throw new UnimplementedError('Writing type ${fieldType.typeName}');
|
| + }
|
| + out('if ($condition) {');
|
| + indent(() {
|
| + out(writeCode);
|
| + });
|
| + out('}');
|
| + }
|
| + out('return fbBuilder.endTable();');
|
| + });
|
| + out('}');
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + void _generateEnumReader(idlModel.EnumDeclaration enm) {
|
| + String name = enm.name;
|
| + String readerName = '_${name}Reader';
|
| + String count = '${idlPrefix(name)}.values.length';
|
| + String def = '${idlPrefix(name)}.${enm.values[0].name}';
|
| + out('class $readerName extends fb.Reader<${idlPrefix(name)}> {');
|
| + indent(() {
|
| + out('const $readerName() : super();');
|
| + out();
|
| + out('@override');
|
| + out('int get size => 1;');
|
| + out();
|
| + out('@override');
|
| + out('${idlPrefix(name)} read(fb.BufferContext bc, int offset) {');
|
| + indent(() {
|
| + out('int index = const fb.Uint8Reader().read(bc, offset);');
|
| + out('return index < $count ? ${idlPrefix(name)}.values[index] : $def;');
|
| + });
|
| + out('}');
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + void _generateImpl(idlModel.ClassDeclaration cls) {
|
| + String name = cls.name;
|
| + String implName = '_${name}Impl';
|
| + String mixinName = '_${name}Mixin';
|
| + out('class $implName extends Object with $mixinName'
|
| + ' implements ${idlPrefix(name)} {');
|
| + indent(() {
|
| + out('final fb.BufferContext _bc;');
|
| + out('final int _bcOffset;');
|
| + out();
|
| + out('$implName(this._bc, this._bcOffset);');
|
| + out();
|
| + // Write cache fields.
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + String returnType = dartType(field.type);
|
| + String fieldName = field.name;
|
| + out('$returnType _$fieldName;');
|
| + }
|
| + // Write getters.
|
| + for (idlModel.FieldDeclaration field in cls.allFields) {
|
| + int index = field.id;
|
| + String fieldName = field.name;
|
| + idlModel.FieldType type = field.type;
|
| + String typeName = type.typeName;
|
| + // Prepare "readCode" + "def"
|
| + String readCode;
|
| + String def = defaultValue(type, false);
|
| + if (type.isList) {
|
| + if (typeName == 'bool') {
|
| + readCode = 'const fb.BoolListReader()';
|
| + } else if (typeName == 'int') {
|
| + readCode = 'const fb.Uint32ListReader()';
|
| + } else if (typeName == 'double') {
|
| + readCode = 'const fb.Float64ListReader()';
|
| + } else if (typeName == 'String') {
|
| + String itemCode = 'const fb.StringReader()';
|
| + readCode = 'const fb.ListReader<String>($itemCode)';
|
| + } else if (_idl.classes.containsKey(typeName)) {
|
| + String itemCode = 'const _${typeName}Reader()';
|
| + readCode = 'const fb.ListReader<${idlPrefix(typeName)}>($itemCode)';
|
| + } else {
|
| + assert(_idl.enums.containsKey(typeName));
|
| + String itemCode = 'const _${typeName}Reader()';
|
| + readCode = 'const fb.ListReader<${idlPrefix(typeName)}>($itemCode)';
|
| + }
|
| + } else if (typeName == 'bool') {
|
| + readCode = 'const fb.BoolReader()';
|
| + } else if (typeName == 'int') {
|
| + readCode = 'const fb.Uint32Reader()';
|
| + } else if (typeName == 'String') {
|
| + readCode = 'const fb.StringReader()';
|
| + } else if (_idl.enums.containsKey(typeName)) {
|
| + readCode = 'const _${typeName}Reader()';
|
| + } else if (_idl.classes.containsKey(typeName)) {
|
| + readCode = 'const _${typeName}Reader()';
|
| + }
|
| + assert(readCode != null);
|
| + // Write the getter implementation.
|
| + out();
|
| + out('@override');
|
| + String returnType = dartType(type);
|
| + if (field.isDeprecated) {
|
| + out('$returnType get $fieldName => $_throwDeprecated;');
|
| + } else {
|
| + out('$returnType get $fieldName {');
|
| + indent(() {
|
| + String readExpr =
|
| + '$readCode.vTableGet(_bc, _bcOffset, $index, $def)';
|
| + out('_$fieldName ??= $readExpr;');
|
| + out('return _$fieldName;');
|
| + });
|
| + out('}');
|
| + }
|
| + }
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + void _generateMixin(idlModel.ClassDeclaration cls) {
|
| + String name = cls.name;
|
| + String mixinName = '_${name}Mixin';
|
| + out('abstract class $mixinName implements ${idlPrefix(name)} {');
|
| + indent(() {
|
| + // Write toJson().
|
| + out('@override');
|
| + out('Map<String, Object> toJson() {');
|
| + indent(() {
|
| + out('Map<String, Object> _result = <String, Object>{};');
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + String condition;
|
| + if (field.type.isList) {
|
| + condition = '${field.name}.isNotEmpty';
|
| + } else {
|
| + condition = '${field.name} != ${defaultValue(field.type, false)}';
|
| + }
|
| + _StringToString convertItem;
|
| + if (_idl.classes.containsKey(field.type.typeName)) {
|
| + convertItem = (String name) => '$name.toJson()';
|
| + } else if (_idl.enums.containsKey(field.type.typeName)) {
|
| + // TODO(paulberry): it would be better to generate a const list of
|
| + // strings so that we don't have to do this kludge.
|
| + convertItem = (String name) => "$name.toString().split('.')[1]";
|
| + } else if (field.type.typeName == 'double') {
|
| + convertItem =
|
| + (String name) => '$name.isFinite ? $name : $name.toString()';
|
| + }
|
| + String convertField;
|
| + if (convertItem == null) {
|
| + convertField = field.name;
|
| + } else if (field.type.isList) {
|
| + convertField = '${field.name}.map((_value) =>'
|
| + ' ${convertItem('_value')}).toList()';
|
| + } else {
|
| + convertField = convertItem(field.name);
|
| + }
|
| + String storeField = '_result[${quoted(field.name)}] = $convertField';
|
| + out('if ($condition) $storeField;');
|
| + }
|
| + out('return _result;');
|
| + });
|
| + out('}');
|
| + out();
|
| + // Write toMap().
|
| + out('@override');
|
| + out('Map<String, Object> toMap() => {');
|
| + indent(() {
|
| + for (idlModel.FieldDeclaration field in cls.fields) {
|
| + String fieldName = field.name;
|
| + out('${quoted(fieldName)}: $fieldName,');
|
| + }
|
| + });
|
| + out('};');
|
| + out();
|
| + // Write toString().
|
| + out('@override');
|
| + out('String toString() => convert.JSON.encode(toJson());');
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + void _generateReader(idlModel.ClassDeclaration cls) {
|
| + String name = cls.name;
|
| + String readerName = '_${name}Reader';
|
| + String implName = '_${name}Impl';
|
| + out('class $readerName extends fb.TableReader<$implName> {');
|
| + indent(() {
|
| + out('const $readerName();');
|
| + out();
|
| + out('@override');
|
| + out('$implName createObject(fb.BufferContext bc, int offset) => new $implName(bc, offset);');
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + void _generateReadFunction(idlModel.ClassDeclaration cls) {
|
| + String name = cls.name;
|
| + out('${idlPrefix(name)} read$name(List<int> buffer) {');
|
| + indent(() {
|
| + out('fb.BufferContext rootRef = new fb.BufferContext.fromBytes(buffer);');
|
| + out('return const _${name}Reader().read(rootRef, 0);');
|
| + });
|
| + out('}');
|
| + }
|
| +
|
| + /**
|
| + * Generate a call to the appropriate method of [ApiSignature] for the type
|
| + * [typeName], using the data named by [ref]. If [couldBeNull] is `true`,
|
| + * generate code to handle the possibility that [ref] is `null` (substituting
|
| + * in the appropriate default value).
|
| + */
|
| + void _generateSignatureCall(String typeName, String ref, bool couldBeNull) {
|
| + if (_idl.enums.containsKey(typeName)) {
|
| + if (couldBeNull) {
|
| + out('signature.addInt($ref == null ? 0 : $ref.index);');
|
| + } else {
|
| + out('signature.addInt($ref.index);');
|
| + }
|
| + } else if (_idl.classes.containsKey(typeName)) {
|
| + if (couldBeNull) {
|
| + out('signature.addBool($ref != null);');
|
| + }
|
| + out('$ref?.collectApiSignature(signature);');
|
| + } else {
|
| + switch (typeName) {
|
| + case 'String':
|
| + if (couldBeNull) {
|
| + ref += " ?? ''";
|
| + }
|
| + out("signature.addString($ref);");
|
| + break;
|
| + case 'int':
|
| + if (couldBeNull) {
|
| + ref += ' ?? 0';
|
| + }
|
| + out('signature.addInt($ref);');
|
| + break;
|
| + case 'bool':
|
| + if (couldBeNull) {
|
| + ref += ' == true';
|
| + }
|
| + out('signature.addBool($ref);');
|
| + break;
|
| + case 'double':
|
| + if (couldBeNull) {
|
| + ref += ' ?? 0.0';
|
| + }
|
| + out('signature.addDouble($ref);');
|
| + break;
|
| + default:
|
| + throw "Don't know how to generate signature call for $typeName";
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Return the documentation text of the given [node], or `null` if the [node]
|
| + * does not have a comment. Each line is `\n` separated.
|
| + */
|
| + String _getNodeDoc(LineInfo lineInfo, AnnotatedNode node) {
|
| + Comment comment = node.documentationComment;
|
| + if (comment != null &&
|
| + comment.isDocumentation &&
|
| + comment.tokens.length == 1 &&
|
| + comment.tokens.first.type == TokenType.MULTI_LINE_COMMENT) {
|
| + Token token = comment.tokens.first;
|
| + int column = lineInfo.getLocation(token.offset).columnNumber;
|
| + String indent = ' ' * (column - 1);
|
| + return token.lexeme.split('\n').map((String line) {
|
| + if (line.startsWith(indent)) {
|
| + line = line.substring(indent.length);
|
| + }
|
| + return line;
|
| + }).join('\n');
|
| + }
|
| + return null;
|
| + }
|
| +}
|
|
|