Refactor summary IDL generator.
I need to make changes to 'collectApiSignature' and 'flushInformative'
to support variants, and we have lots of code around already.
R=brianwilkerson@google.com, paulberry@google.com
Change-Id: I9ba3b61a22b9e9e2c547e97d5c5adaa07443b24a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103552
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/tool/summary/generate.dart b/pkg/analyzer/tool/summary/generate.dart
index 798e369..4d4b3bf 100644
--- a/pkg/analyzer/tool/summary/generate.dart
+++ b/pkg/analyzer/tool/summary/generate.dart
@@ -54,16 +54,512 @@
typedef String _StringToString(String s);
-class _CodeGenerator {
+class _BaseGenerator {
static const String _throwDeprecated =
"throw new UnimplementedError('attempt to access deprecated field')";
+ /// Semantic model of the "IDL" input file.
+ final idlModel.Idl _idl;
+
/// Buffer in which generated code is accumulated.
- final StringBuffer _outBuffer = new StringBuffer();
+ final StringBuffer _outBuffer;
/// Current indentation level.
String _indentation = '';
+ _BaseGenerator(this._idl, this._outBuffer);
+
+ /// 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 == 'double') {
+ return '0.0';
+ } 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;
+ }
+ }
+
+ /// 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);
+ }
+ }
+
+ /// Enclose [s] in quotes, escaping as necessary.
+ String quoted(String s) {
+ return json.encode(s);
+ }
+
+ Iterable<String> _computeVariants(idlModel.ClassDeclaration cls) {
+ return cls.fields
+ .map((f) => f.variantMap?.values ?? [])
+ .expand((v) => v)
+ .expand((v) => v)
+ .toSet();
+ }
+
+ String _variantAssertStatement(
+ idlModel.ClassDeclaration class_,
+ List<String> variants,
+ ) {
+ var assertCondition = variants
+ ?.map((key) => '${class_.variantField} == idl.$key')
+ ?.join(' || ');
+ return 'assert($assertCondition);';
+ }
+}
+
+class _BuilderGenerator extends _BaseGenerator {
+ final idlModel.ClassDeclaration cls;
+ List<String> constructorParams = <String>[];
+
+ _BuilderGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.cls)
+ : super(idl, outBuffer);
+
+ String get builderName => name + 'Builder';
+
+ String get name => cls.name;
+
+ void generate() {
+ String mixinName = '_${name}Mixin';
+ var implementsClause =
+ cls.isDeprecated ? '' : ' implements ${idlPrefix(name)}';
+ out('class $builderName extends Object with $mixinName$implementsClause {');
+ indent(() {
+ _generateFields();
+ _generateGettersSetters();
+ _generateConstructors();
+ _generateFlushInformative();
+ _generateCollectApiSignature();
+ _generateToBuffer();
+ _generateFinish();
+ });
+ out('}');
+ }
+
+ void _generateCollectApiSignature() {
+ out();
+ out('/// Accumulate non-[informative] data into [signature].');
+ 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('}');
+ }
+
+ void _generateConstructors() {
+ out();
+ if (cls.variantField != null) {
+ for (var variant in _computeVariants(cls)) {
+ var constructorName = variant.split('.')[1];
+ out('$builderName.$constructorName({');
+
+ for (var field in cls.fields) {
+ if (field.variantMap != null) {
+ for (var property in field.variantMap.keys) {
+ if (field.variantMap[property].contains(variant)) {
+ out('${encodedType(field.type)} $property,');
+ }
+ }
+ }
+ }
+
+ out('}) : ');
+
+ out('_${cls.variantField} = idl.$variant');
+
+ var separator = ',';
+ for (var field in cls.fields) {
+ if (field.variantMap != null) {
+ for (var property in field.variantMap.keys) {
+ if (field.variantMap[property].contains(variant)) {
+ out('$separator _${field.name} = $property');
+ separator = ', ';
+ }
+ }
+ }
+ }
+
+ out(';');
+ out();
+ }
+ } else {
+ 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');
+ }
+ }
+ }
+
+ void _generateFields() {
+ for (idlModel.FieldDeclaration field in cls.fields) {
+ String fieldName = field.name;
+ idlModel.FieldType type = field.type;
+ String typeStr = encodedType(type);
+ out('$typeStr _$fieldName;');
+ }
+ }
+
+ void _generateFinish() {
+ 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 == 'double') {
+ condition += ' && $valueName != ${defaultValue(fieldType, true)}';
+ writeCode = 'fbBuilder.addFloat64($index, $valueName);';
+ } 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('}');
+ }
+
+ void _generateFlushInformative() {
+ out();
+ out('/// Flush [informative] data recursively.');
+ 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('}');
+ }
+
+ void _generateGettersSetters() {
+ 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();
+ if (field.isDeprecated) {
+ out('@override');
+ out('Null get $fieldName => ${_BaseGenerator._throwDeprecated};');
+ } else {
+ if (field.variantMap != null) {
+ for (var logicalName in field.variantMap.keys) {
+ var variants = field.variantMap[logicalName];
+ out('@override');
+ out('$typeStr get $logicalName {');
+ indent(() {
+ out(_variantAssertStatement(cls, variants));
+ out('return _${field.name}$defSuffix;');
+ });
+ out('}');
+ out();
+ }
+ } else {
+ out('@override');
+ out('$typeStr get $fieldName => _$fieldName$defSuffix;');
+ }
+ out();
+
+ constructorParams.add('$typeStr $fieldName');
+
+ outDoc(field.documentation);
+
+ if (field.variantMap != null) {
+ for (var logicalName in field.variantMap.keys) {
+ var variants = field.variantMap[logicalName];
+ out('set $logicalName($typeStr value) {');
+ indent(() {
+ out(_variantAssertStatement(cls, variants));
+ _generateNonNegativeInt(fieldType);
+ out('_variantField_${field.id} = value;');
+ });
+ out('}');
+ out();
+ }
+ } else {
+ out('set $fieldName($typeStr value) {');
+ indent(() {
+ _generateNonNegativeInt(fieldType);
+ out('this._$fieldName = value;');
+ });
+ out('}');
+ }
+ }
+ }
+ }
+
+ void _generateNonNegativeInt(idlModel.FieldType fieldType) {
+ if (fieldType.typeName == 'int') {
+ if (!fieldType.isList) {
+ out('assert(value == null || value >= 0);');
+ } else {
+ out('assert(value == null || value.every((e) => e >= 0));');
+ }
+ }
+ }
+
+ /// 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";
+ }
+ }
+ }
+
+ void _generateToBuffer() {
+ 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('}');
+ }
+ }
+}
+
+class _CodeGenerator {
+ /// Buffer in which generated code is accumulated.
+ final StringBuffer _outBuffer = new StringBuffer();
+
/// Semantic model of the "IDL" input file.
idlModel.Idl _idl;
@@ -133,63 +629,6 @@
});
}
- /// 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 == 'double') {
- return '0.0';
- } 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(CompilationUnit idlParsed) {
@@ -283,91 +722,10 @@
}
}
- /// 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)};');
- }
+ _FlatBufferSchemaGenerator(_idl, _outBuffer).generate();
}
/// Entry point to the code generator when generating the "format.dart" file.
@@ -382,69 +740,33 @@
out();
out("import 'idl.dart' as idl;");
out();
- for (idlModel.EnumDeclaration enm in _idl.enums.values) {
- _generateEnumReader(enm);
+ for (idlModel.EnumDeclaration enum_ in _idl.enums.values) {
+ _EnumReaderGenerator(_idl, _outBuffer, enum_).generate();
out();
}
for (idlModel.ClassDeclaration cls in _idl.classes.values) {
if (!cls.isDeprecated) {
- _generateBuilder(cls);
+ _BuilderGenerator(_idl, _outBuffer, cls).generate();
out();
}
if (cls.isTopLevel) {
- _generateReadFunction(cls);
+ _ReaderGenerator(_idl, _outBuffer, cls).generateReaderFunction();
out();
}
if (!cls.isDeprecated) {
- _generateReader(cls);
+ _ReaderGenerator(_idl, _outBuffer, cls).generateReader();
out();
- _generateImpl(cls);
+ _ImplGenerator(_idl, _outBuffer, cls).generate();
out();
- _generateMixin(cls);
+ _MixinGenerator(_idl, _outBuffer, cls).generate();
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.
+ /// Add the string [s] to the output as a single line.
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);
- }
+ _outBuffer.writeln(s);
}
void outputHeader() {
@@ -459,11 +781,6 @@
out();
}
- /// Enclose [s] in quotes, escaping as necessary.
- String quoted(String s) {
- return json.encode(s);
- }
-
void _addFieldForGetter(
idlModel.ClassDeclaration cls,
MethodDeclaration getter,
@@ -619,313 +936,41 @@
);
}
- Iterable<String> _computeVariants(idlModel.ClassDeclaration cls) {
- return cls.fields
- .map((f) => f.variantMap?.values ?? [])
- .expand((v) => v)
- .expand((v) => v)
- .toSet();
+ /// 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(AnnotatedNode node) {
+ Comment comment = node.documentationComment;
+ if (comment != null && comment.isDocumentation) {
+ if (comment.tokens.length == 1 &&
+ comment.tokens.first.lexeme.startsWith('/*')) {
+ Token token = comment.tokens.first;
+ return token.lexeme.split('\n').map((String line) {
+ line = line.trimLeft();
+ if (line.startsWith('*')) line = ' ' + line;
+ return line;
+ }).join('\n');
+ } else if (comment.tokens
+ .every((token) => token.lexeme.startsWith('///'))) {
+ return comment.tokens
+ .map((token) => token.lexeme.trimLeft())
+ .join('\n');
+ }
+ }
+ return null;
}
+}
- void _generateBuilder(idlModel.ClassDeclaration cls) {
- String name = cls.name;
- String builderName = name + 'Builder';
- String mixinName = '_${name}Mixin';
- List<String> constructorParams = <String>[];
- var implementsClause =
- cls.isDeprecated ? '' : ' implements ${idlPrefix(name)}';
- out('class $builderName extends Object with $mixinName$implementsClause {');
- 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;');
- }
+class _EnumReaderGenerator extends _BaseGenerator {
+ final idlModel.EnumDeclaration enum_;
- // 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();
- if (field.isDeprecated) {
- out('@override');
- out('Null get $fieldName => $_throwDeprecated;');
- } else {
- if (field.variantMap != null) {
- for (var logicalName in field.variantMap.keys) {
- var variants = field.variantMap[logicalName];
- out('@override');
- out('$typeStr get $logicalName {');
- indent(() {
- out(_variantAssertStatement(cls, variants));
- out('return _${field.name}$defSuffix;');
- });
- out('}');
- out();
- }
- } else {
- out('@override');
- out('$typeStr get $fieldName => _$fieldName$defSuffix;');
- }
- out();
+ _EnumReaderGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.enum_)
+ : super(idl, outBuffer);
- constructorParams.add('$typeStr $fieldName');
-
- outDoc(field.documentation);
-
- if (field.variantMap != null) {
- for (var logicalName in field.variantMap.keys) {
- var variants = field.variantMap[logicalName];
- out('set $logicalName($typeStr value) {');
- indent(() {
- out(_variantAssertStatement(cls, variants));
- _generateNonNegativeInt(fieldType);
- out('_variantField_${field.id} = value;');
- });
- out('}');
- out();
- }
- } else {
- out('set $fieldName($typeStr value) {');
- indent(() {
- _generateNonNegativeInt(fieldType);
- out('this._$fieldName = value;');
- });
- out('}');
- }
- }
- }
- // Generate constructor.
- out();
- if (cls.variantField != null) {
- for (var variant in _computeVariants(cls)) {
- var constructorName = variant.split('.')[1];
- out('$builderName.$constructorName({');
-
- for (var field in cls.fields) {
- if (field.variantMap != null) {
- for (var property in field.variantMap.keys) {
- if (field.variantMap[property].contains(variant)) {
- out('${encodedType(field.type)} $property,');
- }
- }
- }
- }
-
- out('}) : ');
-
- out('_${cls.variantField} = idl.$variant');
-
- var separator = ',';
- for (var field in cls.fields) {
- if (field.variantMap != null) {
- for (var property in field.variantMap.keys) {
- if (field.variantMap[property].contains(variant)) {
- out('$separator _${field.name} = $property');
- separator = ', ';
- }
- }
- }
- }
-
- out(';');
- out();
- }
- } else {
- 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('/// Flush [informative] data recursively.');
- 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('/// Accumulate non-[informative] data into [signature].');
- 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 == 'double') {
- condition += ' && $valueName != ${defaultValue(fieldType, true)}';
- writeCode = 'fbBuilder.addFloat64($index, $valueName);';
- } 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;
+ void generate() {
+ String name = enum_.name;
String readerName = '_${name}Reader';
String count = '${idlPrefix(name)}.values.length';
- String def = '${idlPrefix(name)}.${enm.values[0].name}';
+ String def = '${idlPrefix(name)}.${enum_.values[0].name}';
out('class $readerName extends fb.Reader<${idlPrefix(name)}> {');
indent(() {
out('const $readerName() : super();');
@@ -943,8 +988,105 @@
});
out('}');
}
+}
- void _generateImpl(idlModel.ClassDeclaration cls) {
+class _FlatBufferSchemaGenerator extends _BaseGenerator {
+ _FlatBufferSchemaGenerator(idlModel.Idl idl, StringBuffer outBuffer)
+ : super(idl, outBuffer);
+
+ void generate() {
+ 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)};');
+ }
+ }
+
+ /// 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;
+ }
+ }
+}
+
+class _ImplGenerator extends _BaseGenerator {
+ final idlModel.ClassDeclaration cls;
+
+ _ImplGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.cls)
+ : super(idl, outBuffer);
+
+ void generate() {
String name = cls.name;
String implName = '_${name}Impl';
String mixinName = '_${name}Mixin';
@@ -958,7 +1100,7 @@
out();
// Write cache fields.
for (idlModel.FieldDeclaration field in cls.fields) {
- String returnType = dartType(field.type);
+ String returnType = _dartType(field.type);
String fieldName = field.name;
out('$returnType _$fieldName;');
}
@@ -1005,10 +1147,10 @@
assert(readCode != null);
// Write the getter implementation.
out();
- String returnType = dartType(type);
+ String returnType = _dartType(type);
if (field.isDeprecated) {
out('@override');
- out('Null get $fieldName => $_throwDeprecated;');
+ out('Null get $fieldName => ${_BaseGenerator._throwDeprecated};');
} else {
if (field.variantMap != null) {
for (var logicalName in field.variantMap.keys) {
@@ -1042,7 +1184,25 @@
out('}');
}
- void _generateMixin(idlModel.ClassDeclaration cls) {
+ /// 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;
+ }
+ }
+}
+
+class _MixinGenerator extends _BaseGenerator {
+ final idlModel.ClassDeclaration cls;
+
+ _MixinGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.cls)
+ : super(idl, outBuffer);
+
+ void generate() {
String name = cls.name;
String mixinName = '_${name}Mixin';
out('abstract class $mixinName implements ${idlPrefix(name)} {');
@@ -1170,18 +1330,15 @@
});
out('}');
}
+}
- void _generateNonNegativeInt(idlModel.FieldType fieldType) {
- if (fieldType.typeName == 'int') {
- if (!fieldType.isList) {
- out('assert(value == null || value >= 0);');
- } else {
- out('assert(value == null || value.every((e) => e >= 0));');
- }
- }
- }
+class _ReaderGenerator extends _BaseGenerator {
+ final idlModel.ClassDeclaration cls;
- void _generateReader(idlModel.ClassDeclaration cls) {
+ _ReaderGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.cls)
+ : super(idl, outBuffer);
+
+ void generateReader() {
String name = cls.name;
String readerName = '_${name}Reader';
String implName = '_${name}Impl';
@@ -1195,7 +1352,7 @@
out('}');
}
- void _generateReadFunction(idlModel.ClassDeclaration cls) {
+ void generateReaderFunction() {
String name = cls.name;
out('${idlPrefix(name)} read$name(List<int> buffer) {');
indent(() {
@@ -1204,85 +1361,4 @@
});
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(AnnotatedNode node) {
- Comment comment = node.documentationComment;
- if (comment != null && comment.isDocumentation) {
- if (comment.tokens.length == 1 &&
- comment.tokens.first.lexeme.startsWith('/*')) {
- Token token = comment.tokens.first;
- return token.lexeme.split('\n').map((String line) {
- line = line.trimLeft();
- if (line.startsWith('*')) line = ' ' + line;
- return line;
- }).join('\n');
- } else if (comment.tokens
- .every((token) => token.lexeme.startsWith('///'))) {
- return comment.tokens
- .map((token) => token.lexeme.trimLeft())
- .join('\n');
- }
- }
- return null;
- }
-
- String _variantAssertStatement(
- idlModel.ClassDeclaration class_,
- List<String> variants,
- ) {
- var assertCondition = variants
- ?.map((key) => '${class_.variantField} == idl.$key')
- ?.join(' || ');
- return 'assert($assertCondition);';
- }
}