6bd64ff8b2
Change-Id: Ic75305c7a76860c5b96032d4f8c8fff695006f22 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106004 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Dan Rubel <danrubel@google.com>
1462 lines
49 KiB
Dart
1462 lines
49 KiB
Dart
// 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.
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:analysis_tool/tools.dart';
|
|
import 'package:front_end/src/fasta/scanner.dart';
|
|
import 'package:front_end/src/scanner/token.dart' show Token;
|
|
|
|
import 'idl_model.dart' as idlModel;
|
|
import 'mini_ast.dart';
|
|
|
|
main(List<String> args) async {
|
|
if (args.length != 1) {
|
|
print('Error: IDL path is required');
|
|
print('usage: dart generate.dart path/to/idl.dart');
|
|
}
|
|
String idlPath = args[0];
|
|
await GeneratedContent.generateAll(
|
|
File(idlPath).parent.path, getAllTargets(idlPath));
|
|
}
|
|
|
|
List<GeneratedContent> getAllTargets(String idlPath) {
|
|
final GeneratedFile formatTarget =
|
|
new GeneratedFile('format.dart', (_) async {
|
|
_CodeGenerator codeGenerator = new _CodeGenerator(idlPath);
|
|
codeGenerator.generateFormatCode();
|
|
return codeGenerator._outBuffer.toString();
|
|
});
|
|
|
|
final GeneratedFile schemaTarget = new GeneratedFile('format.fbs', (_) async {
|
|
_CodeGenerator codeGenerator = new _CodeGenerator(idlPath);
|
|
codeGenerator.generateFlatBufferSchema();
|
|
return codeGenerator._outBuffer.toString();
|
|
});
|
|
|
|
return <GeneratedContent>[formatTarget, schemaTarget];
|
|
}
|
|
|
|
typedef String _StringToString(String s);
|
|
|
|
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;
|
|
|
|
/// 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);
|
|
}
|
|
|
|
List<String> _computeVariants(idlModel.ClassDeclaration cls) {
|
|
var allVariants = Set<String>();
|
|
for (var field in cls.fields) {
|
|
var logicalFields = field.logicalProperties?.values;
|
|
if (logicalFields != null) {
|
|
for (var logicalField in logicalFields) {
|
|
allVariants.addAll(logicalField.variants);
|
|
}
|
|
}
|
|
}
|
|
return allVariants.toList()..sort();
|
|
}
|
|
|
|
String _variantAssertStatement(
|
|
idlModel.ClassDeclaration class_,
|
|
idlModel.LogicalProperty property,
|
|
) {
|
|
var assertCondition = property.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) {');
|
|
|
|
void writeField(String name, idlModel.FieldType type, bool isInformative) {
|
|
if (isInformative) {
|
|
return;
|
|
}
|
|
String ref = 'this.$name';
|
|
if (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(type.typeName, 'x', false);
|
|
});
|
|
out('}');
|
|
});
|
|
out('}');
|
|
} else {
|
|
_generateSignatureCall(type.typeName, ref, true);
|
|
}
|
|
}
|
|
|
|
indent(() {
|
|
List<idlModel.FieldDeclaration> sortedFields = cls.fields.toList()
|
|
..sort((idlModel.FieldDeclaration a, idlModel.FieldDeclaration b) =>
|
|
a.id.compareTo(b.id));
|
|
if (cls.variantField != null) {
|
|
var firstVariant = true;
|
|
for (var variant in _computeVariants(cls)) {
|
|
if (firstVariant) {
|
|
firstVariant = false;
|
|
} else {
|
|
out('else');
|
|
}
|
|
out('if (${cls.variantField} == idl.$variant) {');
|
|
indent(() {
|
|
for (var field in sortedFields) {
|
|
var logicalProperties = field.logicalProperties;
|
|
if (logicalProperties != null) {
|
|
for (var logicalName in logicalProperties.keys) {
|
|
var logicalProperty = logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
writeField(
|
|
logicalName,
|
|
field.type,
|
|
logicalProperty.isInformative,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
writeField(field.name, field.type, field.isInformative);
|
|
}
|
|
}
|
|
});
|
|
out('}');
|
|
}
|
|
} else {
|
|
for (idlModel.FieldDeclaration field in sortedFields) {
|
|
writeField('_${field.name}', field.type, field.isInformative);
|
|
}
|
|
}
|
|
});
|
|
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.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
out('${encodedType(field.type)} $logicalName,');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out('}) : ');
|
|
|
|
out('_${cls.variantField} = idl.$variant');
|
|
|
|
var separator = ',';
|
|
for (var field in cls.fields) {
|
|
if (field.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
out('$separator _${field.name} = $logicalName');
|
|
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() {');
|
|
|
|
void writeField(String name, idlModel.FieldType type, bool isInformative) {
|
|
if (isInformative) {
|
|
out('$name = null;');
|
|
} else if (_idl.classes.containsKey(type.typeName)) {
|
|
if (type.isList) {
|
|
out('$name?.forEach((b) => b.flushInformative());');
|
|
} else {
|
|
out('$name?.flushInformative();');
|
|
}
|
|
}
|
|
}
|
|
|
|
indent(() {
|
|
if (cls.variantField != null) {
|
|
var firstVariant = true;
|
|
for (var variant in _computeVariants(cls)) {
|
|
if (firstVariant) {
|
|
firstVariant = false;
|
|
} else {
|
|
out('else');
|
|
}
|
|
out('if (${cls.variantField} == idl.$variant) {');
|
|
indent(() {
|
|
for (var field in cls.fields) {
|
|
var logicalProperties = field.logicalProperties;
|
|
if (logicalProperties != null) {
|
|
for (var logicalName in logicalProperties.keys) {
|
|
var logicalProperty = logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
writeField(
|
|
logicalName,
|
|
field.type,
|
|
logicalProperty.isInformative,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
writeField(field.name, field.type, field.isInformative);
|
|
}
|
|
}
|
|
});
|
|
out('}');
|
|
}
|
|
} else {
|
|
for (idlModel.FieldDeclaration field in cls.fields) {
|
|
writeField('_${field.name}', field.type, field.isInformative);
|
|
}
|
|
}
|
|
});
|
|
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.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
out('@override');
|
|
out('$typeStr get $logicalName {');
|
|
indent(() {
|
|
out(_variantAssertStatement(cls, logicalProperty));
|
|
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.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
out('set $logicalName($typeStr value) {');
|
|
indent(() {
|
|
out(_variantAssertStatement(cls, logicalProperty));
|
|
_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;
|
|
|
|
_CodeGenerator(String idlPath) {
|
|
// Parse the input "IDL" file.
|
|
File idlFile = new File(idlPath);
|
|
String idlText =
|
|
idlFile.readAsStringSync().replaceAll(new RegExp('\r\n?'), '\n');
|
|
// Extract a description of the IDL and make sure it is valid.
|
|
var startingToken = scanString(idlText, includeComments: true).tokens;
|
|
var listener = new MiniAstBuilder();
|
|
var parser = new MiniAstParser(listener);
|
|
parser.parseUnit(startingToken);
|
|
extractIdl(listener.compilationUnit);
|
|
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');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Process the AST in [idlParsed] and store the resulting semantic model in
|
|
/// [_idl]. Also perform some error checking.
|
|
void extractIdl(CompilationUnit idlParsed) {
|
|
_idl = new idlModel.Idl();
|
|
for (CompilationUnitMember decl in idlParsed.declarations) {
|
|
if (decl is ClassDeclaration) {
|
|
bool isTopLevel = false;
|
|
bool isDeprecated = false;
|
|
String fileIdentifier;
|
|
String clsName = decl.name;
|
|
String variantField;
|
|
for (Annotation annotation in decl.metadata) {
|
|
if (annotation.arguments != null &&
|
|
annotation.name == 'TopLevel' &&
|
|
annotation.constructorName == null) {
|
|
isTopLevel = true;
|
|
if (annotation.arguments.length == 1) {
|
|
Expression arg = annotation.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.length != 0) {
|
|
throw new Exception(
|
|
'Class `$clsName`: TopLevel requires 0 or 1 arguments');
|
|
}
|
|
} else if (annotation.arguments == null &&
|
|
annotation.name == 'deprecated' &&
|
|
annotation.constructorName == null) {
|
|
isDeprecated = true;
|
|
} else if (annotation.arguments != null &&
|
|
annotation.name == 'Variant' &&
|
|
annotation.constructorName == null) {
|
|
if (annotation.arguments.length == 1) {
|
|
Expression arg = annotation.arguments[0];
|
|
if (arg is StringLiteral) {
|
|
variantField = arg.stringValue;
|
|
} else {
|
|
throw new Exception(
|
|
'Class `$clsName`: @Variant argument must be a string literal',
|
|
);
|
|
}
|
|
} else if (annotation.arguments.length != 0) {
|
|
throw Exception(
|
|
'Class `$clsName`: @Variant requires 1 argument',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
idlModel.ClassDeclaration cls = new idlModel.ClassDeclaration(
|
|
documentation: _getNodeDoc(decl),
|
|
name: clsName,
|
|
isTopLevel: isTopLevel,
|
|
fileIdentifier: fileIdentifier,
|
|
isDeprecated: isDeprecated,
|
|
variantField: variantField,
|
|
);
|
|
_idl.classes[clsName] = cls;
|
|
String expectedBase = 'base.SummaryClass';
|
|
if (decl.superclass == null || decl.superclass.name != expectedBase) {
|
|
throw new Exception(
|
|
'Class `$clsName` needs to extend `$expectedBase`');
|
|
}
|
|
for (ClassMember classMember in decl.members) {
|
|
if (classMember is MethodDeclaration && classMember.isGetter) {
|
|
_addFieldForGetter(cls, classMember);
|
|
} else if (classMember is ConstructorDeclaration &&
|
|
classMember.name.endsWith('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(decl);
|
|
idlModel.EnumDeclaration enm =
|
|
new idlModel.EnumDeclaration(doc, decl.name);
|
|
_idl.enums[enm.name] = enm;
|
|
for (EnumConstantDeclaration constDecl in decl.constants) {
|
|
String doc = _getNodeDoc(constDecl);
|
|
enm.values
|
|
.add(new idlModel.EnumValueDeclaration(doc, constDecl.name));
|
|
}
|
|
} else {
|
|
throw new Exception('Unexpected declaration `$decl`');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Entry point to the code generator when generating the "format.fbs" file.
|
|
void generateFlatBufferSchema() {
|
|
outputHeader();
|
|
_FlatBufferSchemaGenerator(_idl, _outBuffer).generate();
|
|
}
|
|
|
|
/// Entry point to the code generator when generating the "format.dart" file.
|
|
void generateFormatCode() {
|
|
outputHeader();
|
|
out('library analyzer.src.summary.format;');
|
|
out();
|
|
out("import 'dart:convert' as convert;");
|
|
out();
|
|
out("import 'package:analyzer/src/summary/api_signature.dart' as api_sig;");
|
|
out("import 'package:analyzer/src/summary/flat_buffers.dart' as fb;");
|
|
out();
|
|
out("import 'idl.dart' as idl;");
|
|
out();
|
|
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) {
|
|
_BuilderGenerator(_idl, _outBuffer, cls).generate();
|
|
out();
|
|
}
|
|
if (cls.isTopLevel) {
|
|
_ReaderGenerator(_idl, _outBuffer, cls).generateReaderFunction();
|
|
out();
|
|
}
|
|
if (!cls.isDeprecated) {
|
|
_ReaderGenerator(_idl, _outBuffer, cls).generateReader();
|
|
out();
|
|
_ImplGenerator(_idl, _outBuffer, cls).generate();
|
|
out();
|
|
_MixinGenerator(_idl, _outBuffer, cls).generate();
|
|
out();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Add the string [s] to the output as a single line.
|
|
void out([String s = '']) {
|
|
_outBuffer.writeln(s);
|
|
}
|
|
|
|
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 SDK script');
|
|
out('// "pkg/analyzer/tool/summary/generate.dart \$IDL_FILE_PATH",');
|
|
out('// or "pkg/analyzer/tool/generate_files" for the analyzer package IDL/sources.');
|
|
out();
|
|
}
|
|
|
|
void _addFieldForGetter(
|
|
idlModel.ClassDeclaration cls,
|
|
MethodDeclaration getter,
|
|
) {
|
|
var desc = '${cls.name}.${getter.name}';
|
|
if (getter.returnType == null) {
|
|
throw new Exception('Getter needs a type: $desc');
|
|
}
|
|
|
|
var type = getter.returnType;
|
|
|
|
var isList = false;
|
|
if (type.name == 'List' &&
|
|
type.typeArguments != null &&
|
|
type.typeArguments.length == 1) {
|
|
isList = true;
|
|
type = type.typeArguments[0];
|
|
}
|
|
if (type.typeArguments != null) {
|
|
throw new Exception('Cannot handle type arguments in `$type`');
|
|
}
|
|
|
|
int id;
|
|
List<String> variants;
|
|
bool isDeprecated = false;
|
|
bool isInformative = false;
|
|
|
|
for (Annotation annotation in getter.metadata) {
|
|
if (annotation.name == 'Id') {
|
|
if (id != null) {
|
|
throw new Exception('Duplicate @id annotation ($getter)');
|
|
}
|
|
if (annotation.arguments == null) {
|
|
throw Exception('@Id must be passed an argument ($desc)');
|
|
}
|
|
if (annotation.arguments.length != 1) {
|
|
throw Exception('@Id must be passed exactly one argument ($desc)');
|
|
}
|
|
|
|
var idExpression = annotation.arguments[0];
|
|
if (idExpression is IntegerLiteral) {
|
|
id = idExpression.value;
|
|
} else {
|
|
throw new Exception(
|
|
'@Id argument must be an integer literal ($desc)',
|
|
);
|
|
}
|
|
} else if (annotation.name == 'deprecated') {
|
|
if (annotation.arguments != null) {
|
|
throw new Exception('@deprecated does not take args ($desc)');
|
|
}
|
|
isDeprecated = true;
|
|
} else if (annotation.name == 'informative') {
|
|
isInformative = true;
|
|
} else if (annotation.name == 'VariantId') {
|
|
if (id != null) {
|
|
throw Exception('Cannot specify both @Id and @VariantId ($getter)');
|
|
}
|
|
if (variants != null) {
|
|
throw Exception('Duplicate @VariantId annotation ($getter)');
|
|
}
|
|
|
|
if (annotation.arguments == null) {
|
|
throw Exception('@VariantId must be given arguments ($desc)');
|
|
}
|
|
if (annotation.arguments.length != 2) {
|
|
throw Exception(
|
|
'@VariantId must be given exactly two arguments ($desc)',
|
|
);
|
|
}
|
|
|
|
var idExpression = annotation.arguments[0];
|
|
if (idExpression is IntegerLiteral) {
|
|
id = idExpression.value;
|
|
} else {
|
|
throw Exception(
|
|
'@VariantId argument must be an integer literal ($desc)',
|
|
);
|
|
}
|
|
|
|
var variantExpression = annotation.arguments[1];
|
|
if (variantExpression is NamedExpression) {
|
|
if (variantExpression.name == 'variant') {
|
|
variants = [variantExpression.expression.toCode()];
|
|
} else if (variantExpression.name == 'variantList') {
|
|
variants = (variantExpression.expression as ListLiteral)
|
|
.elements
|
|
.map((e) => e.toCode())
|
|
.toList();
|
|
} else {
|
|
throw Exception(
|
|
'Only "key" or "keyList" expected in @VariantId ($desc)',
|
|
);
|
|
}
|
|
} else {
|
|
throw Exception(
|
|
'The second argument of @VariantId must be named ($desc)',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (id == null) {
|
|
throw new Exception('Missing @id annotation ($desc)');
|
|
}
|
|
|
|
var fieldType = new idlModel.FieldType(type.name, isList);
|
|
|
|
String name = getter.name;
|
|
Map<String, idlModel.LogicalProperty> logicalProperties;
|
|
if (variants != null) {
|
|
var fieldsWithSameId =
|
|
cls.allFields.where((field) => field.id == id).toList();
|
|
if (fieldsWithSameId.isNotEmpty) {
|
|
var existingField = fieldsWithSameId.single;
|
|
if (existingField.logicalProperties == null) {
|
|
throw Exception('$desc: id $id is already used as a non-variant '
|
|
'field: ${existingField.name}');
|
|
}
|
|
|
|
var map = existingField.logicalProperties;
|
|
for (var variant in variants) {
|
|
for (var logicalName in map.keys) {
|
|
if (map[logicalName].variants.contains(variant)) {
|
|
throw Exception('$desc: id $id is already used for $logicalName');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (existingField.type != fieldType) {
|
|
throw Exception(
|
|
'$desc: id $id is already used with type ${existingField.type}',
|
|
);
|
|
}
|
|
|
|
if (map[getter.name] != null) {
|
|
throw Exception(
|
|
'$desc: logical property ${getter.name} is already used',
|
|
);
|
|
}
|
|
|
|
map[getter.name] = idlModel.LogicalProperty(
|
|
isDeprecated: isDeprecated,
|
|
isInformative: isInformative,
|
|
variants: variants,
|
|
);
|
|
return;
|
|
} else {
|
|
name = 'variantField_$id';
|
|
logicalProperties = <String, idlModel.LogicalProperty>{
|
|
getter.name: idlModel.LogicalProperty(
|
|
isDeprecated: isDeprecated,
|
|
isInformative: isInformative,
|
|
variants: variants,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
cls.allFields.add(
|
|
idlModel.FieldDeclaration(
|
|
documentation: _getNodeDoc(getter),
|
|
name: name,
|
|
type: fieldType,
|
|
id: id,
|
|
isDeprecated: isDeprecated,
|
|
isInformative: isInformative,
|
|
logicalProperties: logicalProperties,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
}
|
|
|
|
class _EnumReaderGenerator extends _BaseGenerator {
|
|
final idlModel.EnumDeclaration enum_;
|
|
|
|
_EnumReaderGenerator(idlModel.Idl idl, StringBuffer outBuffer, this.enum_)
|
|
: super(idl, outBuffer);
|
|
|
|
void generate() {
|
|
String name = enum_.name;
|
|
String readerName = '_${name}Reader';
|
|
String count = '${idlPrefix(name)}.values.length';
|
|
String def = '${idlPrefix(name)}.${enum_.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('}');
|
|
}
|
|
}
|
|
|
|
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';
|
|
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 == 'double') {
|
|
readCode = 'const fb.Float64Reader()';
|
|
} 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();
|
|
String returnType = _dartType(type);
|
|
if (field.isDeprecated) {
|
|
out('@override');
|
|
out('Null get $fieldName => ${_BaseGenerator._throwDeprecated};');
|
|
} else {
|
|
if (field.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
out('@override');
|
|
out('$returnType get $logicalName {');
|
|
indent(() {
|
|
out(_variantAssertStatement(cls, logicalProperty));
|
|
String readExpr =
|
|
'$readCode.vTableGet(_bc, _bcOffset, $index, $def)';
|
|
out('_$fieldName ??= $readExpr;');
|
|
out('return _$fieldName;');
|
|
});
|
|
out('}');
|
|
out();
|
|
}
|
|
} else {
|
|
out('@override');
|
|
out('$returnType get $fieldName {');
|
|
indent(() {
|
|
String readExpr =
|
|
'$readCode.vTableGet(_bc, _bcOffset, $index, $def)';
|
|
out('_$fieldName ??= $readExpr;');
|
|
out('return _$fieldName;');
|
|
});
|
|
out('}');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
out('}');
|
|
}
|
|
|
|
/// 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)} {');
|
|
indent(() {
|
|
String jsonCondition(idlModel.FieldType type, String name) {
|
|
if (type.isList) {
|
|
return '$name.isNotEmpty';
|
|
} else {
|
|
return '$name != ${defaultValue(type, false)}';
|
|
}
|
|
}
|
|
|
|
String jsonStore(idlModel.FieldType type, String name) {
|
|
_StringToString convertItem;
|
|
if (_idl.classes.containsKey(type.typeName)) {
|
|
convertItem = (String name) => '$name.toJson()';
|
|
} else if (_idl.enums.containsKey(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 (type.typeName == 'double') {
|
|
convertItem =
|
|
(String name) => '$name.isFinite ? $name : $name.toString()';
|
|
}
|
|
String convertField;
|
|
if (convertItem == null) {
|
|
convertField = name;
|
|
} else if (type.isList) {
|
|
convertField = '$name.map((_value) =>'
|
|
' ${convertItem('_value')}).toList()';
|
|
} else {
|
|
convertField = convertItem(name);
|
|
}
|
|
return '_result[${quoted(name)}] = $convertField';
|
|
}
|
|
|
|
// Write toJson().
|
|
out('@override');
|
|
out('Map<String, Object> toJson() {');
|
|
indent(() {
|
|
out('Map<String, Object> _result = <String, Object>{};');
|
|
|
|
if (cls.variantField != null) {
|
|
indent(() {
|
|
for (idlModel.FieldDeclaration field in cls.fields) {
|
|
if (field.logicalProperties == null) {
|
|
var condition = jsonCondition(field.type, field.name);
|
|
var storeField = jsonStore(field.type, field.name);
|
|
out('if ($condition) $storeField;');
|
|
}
|
|
}
|
|
for (var variant in _computeVariants(cls)) {
|
|
out('if (${cls.variantField} == idl.$variant) {');
|
|
indent(() {
|
|
for (idlModel.FieldDeclaration field in cls.fields) {
|
|
var logicalProperties = field.logicalProperties;
|
|
if (logicalProperties != null) {
|
|
for (var logicalName in logicalProperties.keys) {
|
|
var logicalProperty = logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
var condition = jsonCondition(field.type, logicalName);
|
|
var storeField = jsonStore(field.type, logicalName);
|
|
out('if ($condition) $storeField;');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
out('}');
|
|
}
|
|
});
|
|
} else {
|
|
indent(() {
|
|
for (idlModel.FieldDeclaration field in cls.fields) {
|
|
String condition = jsonCondition(field.type, field.name);
|
|
String storeField = jsonStore(field.type, field.name);
|
|
out('if ($condition) $storeField;');
|
|
}
|
|
});
|
|
}
|
|
|
|
out('return _result;');
|
|
});
|
|
out('}');
|
|
out();
|
|
|
|
// Write toMap().
|
|
out('@override');
|
|
if (cls.variantField != null) {
|
|
out('Map<String, Object> toMap() {');
|
|
for (var variant in _computeVariants(cls)) {
|
|
out('if (${cls.variantField} == idl.$variant) {');
|
|
indent(() {
|
|
out('return {');
|
|
for (idlModel.FieldDeclaration field in cls.fields) {
|
|
if (field.logicalProperties != null) {
|
|
for (var logicalName in field.logicalProperties.keys) {
|
|
var logicalProperty = field.logicalProperties[logicalName];
|
|
if (logicalProperty.variants.contains(variant)) {
|
|
out('${quoted(logicalName)}: $logicalName,');
|
|
}
|
|
}
|
|
} else {
|
|
String fieldName = field.name;
|
|
out('${quoted(fieldName)}: $fieldName,');
|
|
}
|
|
}
|
|
out('};');
|
|
});
|
|
out('}');
|
|
}
|
|
out('throw StateError("Unexpected \$${cls.variantField}");');
|
|
out('}');
|
|
} else {
|
|
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('}');
|
|
}
|
|
}
|
|
|
|
class _ReaderGenerator extends _BaseGenerator {
|
|
final 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';
|
|
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 generateReaderFunction() {
|
|
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('}');
|
|
}
|
|
}
|