blob: ee8a52329d2bfb9b3b16a2dac4c655c64faf9100 [file] [log] [blame]
// Copyright (c) 2020, 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.
import 'package:ffigen/src/code_generator/typedef.dart';
import 'binding.dart';
import 'binding_string.dart';
import 'type.dart';
import 'utils.dart';
import 'writer.dart';
/// A binding for C Struct.
///
/// For a C structure -
/// ```c
/// struct C {
/// int a;
/// double b;
/// int c;
/// };
/// ```
/// The generated dart code is -
/// ```dart
/// class Struct extends ffi.Struct{
/// @ffi.Int32()
/// int a;
///
/// @ffi.Double()
/// double b;
///
/// @ffi.Uint8()
/// int c;
///
/// }
/// ```
class Struc extends NoLookUpBinding {
List<Member> members;
Struc({
String? usr,
String? originalName,
required String name,
String? dartDoc,
List<Member>? members,
}) : members = members ?? [],
super(
usr: usr,
originalName: originalName,
name: name,
dartDoc: dartDoc,
);
List<int> _getArrayDimensionLengths(Type type) {
final array = <int>[];
var startType = type;
while (startType.broadType == BroadType.ConstantArray) {
array.add(startType.length!);
startType = startType.child!;
}
return array;
}
List<Typedef>? _typedefDependencies;
@override
List<Typedef> getTypedefDependencies(Writer w) {
if (_typedefDependencies == null) {
_typedefDependencies = <Typedef>[];
// Write typedef's required by members and resolve name conflicts.
for (final m in members) {
final base = m.type.getBaseType();
if (base.broadType == BroadType.NativeFunction) {
_typedefDependencies!.addAll(base.nativeFunc!.getDependencies());
}
}
}
return _typedefDependencies ?? [];
}
@override
BindingString toBindingString(Writer w) {
final s = StringBuffer();
final enclosingClassName = name;
if (dartDoc != null) {
s.write(makeDartDoc(dartDoc!));
}
final helpers = <ArrayHelper>[];
final expandedArrayItemPrefix = getUniqueExpandedArrayItemPrefix();
/// Adding [enclosingClassName] because dart doesn't allow class member
/// to have the same name as the class.
final localUniqueNamer = UniqueNamer({enclosingClassName});
// Write class declaration.
s.write(
'class $enclosingClassName extends ${w.ffiLibraryPrefix}.Struct{\n');
for (final m in members) {
final memberName = localUniqueNamer.makeUnique(m.name);
if (m.type.broadType == BroadType.ConstantArray) {
// TODO(5): Remove array helpers when inline array support arives.
final arrayHelper = ArrayHelper(
helperClassGroupName:
'${w.arrayHelperClassPrefix}_${enclosingClassName}_${memberName}',
elementType: m.type.getBaseArrayType(),
dimensions: _getArrayDimensionLengths(m.type),
name: memberName,
structName: enclosingClassName,
elementNamePrefix: '${expandedArrayItemPrefix}${memberName}_item_',
);
s.write(arrayHelper.declarationString(w));
helpers.add(arrayHelper);
} else {
const depth = ' ';
if (m.dartDoc != null) {
s.write(depth + '/// ');
s.writeAll(m.dartDoc!.split('\n'), '\n' + depth + '/// ');
s.write('\n');
}
if (m.type.isPrimitive) {
s.write('$depth@${m.type.getCType(w)}()\n');
}
s.write('${depth}external ${m.type.getDartType(w)} ${memberName};\n\n');
}
}
s.write('}\n\n');
for (final helper in helpers) {
s.write(helper.helperClassString(w));
}
return BindingString(type: BindingStringType.struc, string: s.toString());
}
/// Gets a unique prefix in local namespace for expanded array items.
String getUniqueExpandedArrayItemPrefix() {
final base = '_unique';
var expandedArrayItemPrefix = base;
var suffixInt = 0;
for (var i = 0; i < members.length; i++) {
if (members[i].name.startsWith(expandedArrayItemPrefix)) {
// Not a unique prefix, start over with a new suffix.
i = -1;
suffixInt++;
expandedArrayItemPrefix = '${base}${suffixInt}';
}
}
return expandedArrayItemPrefix + '_';
}
}
class Member {
final String? dartDoc;
final String originalName;
final String name;
final Type type;
const Member({
String? originalName,
required this.name,
required this.type,
this.dartDoc,
}) : originalName = originalName ?? name;
}
// Helper bindings for struct array.
class ArrayHelper {
final Type elementType;
final List<int> dimensions;
final String? structName;
final String? name;
final String helperClassGroupName;
final String elementNamePrefix;
int? _expandedArrayLength;
int get expandedArrayLength {
if (_expandedArrayLength != null) return _expandedArrayLength!;
var arrayLength = 1;
for (final i in dimensions) {
arrayLength = arrayLength * i;
}
return arrayLength;
}
ArrayHelper({
required this.elementType,
required this.dimensions,
required this.structName,
required this.name,
required this.helperClassGroupName,
required this.elementNamePrefix,
});
/// Create declaration binding, added inside the struct binding.
String declarationString(Writer w) {
final s = StringBuffer();
final arrayDartType = elementType.getDartType(w);
final arrayCType = elementType.getCType(w);
for (var i = 0; i < expandedArrayLength; i++) {
if (elementType.isPrimitive) {
s.write(' @${arrayCType}()\n');
}
s.write(' external ${arrayDartType} ${elementNamePrefix}$i;\n');
}
s.write('/// Helper for array `$name`.\n');
s.write(
'${helperClassGroupName}_level0 get $name => ${helperClassGroupName}_level0(this, $dimensions, 0, 0);\n');
return s.toString();
}
String helperClassString(Writer w) {
final s = StringBuffer();
final arrayType = elementType.getDartType(w);
for (var dim = 0; dim < dimensions.length; dim++) {
final helperClassName = '${helperClassGroupName}_level${dim}';
final structIdentifier = '_struct';
final dimensionsIdentifier = 'dimensions';
final levelIdentifier = 'level';
final absoluteIndexIdentifier = '_absoluteIndex';
final checkBoundsFunctionIdentifier = '_checkBounds';
final legthIdentifier = 'length';
s.write('/// Helper for array `$name` in struct `$structName`.\n');
// Write class declaration.
s.write('class ${helperClassName}{\n');
s.write('final $structName $structIdentifier;\n');
s.write('final List<int> $dimensionsIdentifier;\n');
s.write('final int $levelIdentifier;\n');
s.write('final int $absoluteIndexIdentifier;\n');
s.write(
'int get $legthIdentifier => $dimensionsIdentifier[$levelIdentifier];\n');
// Write class constructor.
s.write(
'$helperClassName(this.$structIdentifier, this.$dimensionsIdentifier, this.$levelIdentifier, this.$absoluteIndexIdentifier);\n');
// Write checkBoundsFunction.
s.write('''
void $checkBoundsFunctionIdentifier(int index) {
if (index >= $legthIdentifier || index < 0) {
throw RangeError('Dimension \$$levelIdentifier: index not in range 0..\${$legthIdentifier} exclusive.');
}
}
''');
// If this isn't the last level.
if (dim + 1 != dimensions.length) {
// Override [] operator.
s.write('''
${helperClassGroupName}_level${dim + 1} operator [](int index) {
$checkBoundsFunctionIdentifier(index);
var offset = index;
for (var i = level + 1; i < $dimensionsIdentifier.length; i++) {
offset *= $dimensionsIdentifier[i];
}
return ${helperClassGroupName}_level${dim + 1}(
$structIdentifier, $dimensionsIdentifier, $levelIdentifier + 1, $absoluteIndexIdentifier + offset);
}
''');
} else {
// This is the last level, add switching logic here.
// Override [] operator.
s.write('$arrayType operator[](int index){\n');
s.write('$checkBoundsFunctionIdentifier(index);\n');
s.write('switch($absoluteIndexIdentifier+index){\n');
for (var i = 0; i < expandedArrayLength; i++) {
s.write('case $i:\n');
s.write(' return $structIdentifier.${elementNamePrefix}$i;\n');
}
s.write('default:\n');
s.write(" throw Exception('Invalid Array Helper generated.');");
s.write('}\n');
s.write('}\n');
// Override []= operator.
s.write('void operator[]=(int index, $arrayType value){\n');
s.write('$checkBoundsFunctionIdentifier(index);\n');
s.write('switch($absoluteIndexIdentifier+index){\n');
for (var i = 0; i < expandedArrayLength; i++) {
s.write('case $i:\n');
s.write(' $structIdentifier.${elementNamePrefix}$i = value;\n');
s.write(' break;\n');
}
s.write('default:\n');
s.write(" throw Exception('Invalid Array Helper generated.');\n");
s.write('}\n');
s.write('}\n');
}
s.write('}\n');
}
return s.toString();
}
}