| // Copyright (c) 2021, 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 '../code_generator.dart'; | |
| import '../config_provider/config.dart' show EnumStyle; | |
| import '../config_provider/config_types.dart'; | |
| import '../context.dart'; | |
| import '../header_parser/sub_parsers/api_availability.dart'; | |
| import '../visitor/ast.dart'; | |
| import 'binding_string.dart'; | |
| import 'scope.dart'; | |
| import 'utils.dart'; | |
| import 'writer.dart'; | |
| /// A binding for Compound type - Struct/Union. | |
| abstract class Compound extends BindingType with HasLocalScope { | |
| /// Marker for if a struct definition is complete. | |
| /// | |
| /// A function can be safely pass this struct by value if it's complete. | |
| bool isIncomplete; | |
| final List<CompoundMember> members; | |
| bool get isOpaque => members.isEmpty; | |
| /// Value for `@Packed(X)` annotation. Can be null (no packing), 1, 2, 4, 8, | |
| /// or 16. | |
| int? get pack; | |
| /// How dependencies of this compound should be generated. | |
| CompoundDependencies dependencies; | |
| /// Marker for checking if the dependencies are parsed. | |
| bool parsedDependencies = false; | |
| final Context context; | |
| /// The way the native type is written in C source code. This isn't always the | |
| /// same as the originalName, because the type may need to be prefixed with | |
| /// `struct` or `union`, depending on whether the declaration is a typedef. | |
| final String nativeType; | |
| final ApiAvailability? apiAvailability; | |
| /// The size of this compound in bytes, or `null` if the compound is | |
| /// incomplete, opaque, or has an unknown layout at parse time. | |
| final int? sizeInBytes; | |
| bool isIncluded = false; | |
| Compound({ | |
| super.usr, | |
| super.originalName, | |
| required super.name, | |
| this.isIncomplete = false, | |
| super.dartDoc, | |
| List<CompoundMember>? members, | |
| required this.context, | |
| String? nativeType, | |
| this.apiAvailability, | |
| this.sizeInBytes, | |
| this.dependencies = CompoundDependencies.opaque, | |
| }) : members = members ?? [], | |
| nativeType = nativeType ?? originalName ?? name; | |
| String _getInlineArrayTypeString(Type type, Writer w) { | |
| if (type is ConstantArray) { | |
| return '${context.libs.prefix(ffiImport)}.Array<' | |
| '${_getInlineArrayTypeString(type.child, w)}>'; | |
| } | |
| return type.getCType(context); | |
| } | |
| bool _shouldGenerateAllocate() { | |
| if (this is! Struct || isOpaque || name.startsWith('_')) { | |
| return false; | |
| } | |
| for (final m in members) { | |
| final memberType = m.type.typealiasType; | |
| if (memberType is ConstantArray) { | |
| return false; | |
| } | |
| if (memberType is Struct || memberType is Union) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool _isEnumDartStyleMember(CompoundMember member) { | |
| final type = member.type; | |
| return type is EnumClass && type.effectiveStyle == EnumStyle.dartEnum; | |
| } | |
| String _memberStorageName(CompoundMember member) { | |
| if (_isEnumDartStyleMember(member)) { | |
| return member.name; | |
| } | |
| return member.type.sameDartAndFfiDartType | |
| ? member.name | |
| : '${member.name}AsInt'; | |
| } | |
| String _memberParameterType(CompoundMember member) { | |
| if (_isEnumDartStyleMember(member)) { | |
| return member.type.getDartType(context); | |
| } | |
| return member.type.getFfiDartType(context); | |
| } | |
| String _generateAllocateMethod(String enclosingClassName, String ffiPrefix) { | |
| final usedParamNames = <String>{}; | |
| final params = <({String type, String name, String assignment})>[]; | |
| for (final m in members) { | |
| params.add(( | |
| type: _memberParameterType(m), | |
| name: _allocateParamName(m.name, usedParamNames), | |
| assignment: _memberStorageName(m), | |
| )); | |
| } | |
| final b = StringBuffer(); | |
| b.write( | |
| ' static $ffiPrefix.Pointer<$enclosingClassName> \$allocate(\n' | |
| ' $ffiPrefix.Allocator \$allocator, {\n', | |
| ); | |
| for (final p in params) { | |
| b.write(' required ${p.type} ${p.name},\n'); | |
| } | |
| b.write(' }) => \$allocator<$enclosingClassName>()'); | |
| for (final p in params) { | |
| b.write('\n ..ref.${p.assignment} = ${p.name}'); | |
| } | |
| b.write(';\n\n'); | |
| return b.toString(); | |
| } | |
| String _allocateParamName(String memberName, Set<String> usedNames) { | |
| var name = memberName; | |
| if (name.startsWith('_')) { | |
| final withoutLeadingUnderscores = name.replaceFirst(RegExp(r'^_+'), ''); | |
| final core = withoutLeadingUnderscores.isEmpty | |
| ? 'unnamed' | |
| : withoutLeadingUnderscores; | |
| name = '\$$core'; | |
| } | |
| var unique = name; | |
| for (var i = 1; usedNames.contains(unique); ++i) { | |
| unique = '$name\$$i'; | |
| } | |
| usedNames.add(unique); | |
| return unique; | |
| } | |
| @override | |
| bool get isObjCImport => | |
| context.objCBuiltInFunctions.getBuiltInCompoundName(originalName) != null; | |
| @override | |
| BindingString toBindingString(Writer w) { | |
| final s = StringBuffer(); | |
| final enclosingClassName = name; | |
| s.write(makeDartDoc(dartDoc)); | |
| final deprecatedAnnotation = apiAvailability?.deprecatedAnnotation; | |
| if (deprecatedAnnotation != null) { | |
| s.write('$deprecatedAnnotation\n'); | |
| } | |
| /// Write @Packed(X) annotation if struct is packed. | |
| final ffiPrefix = context.libs.prefix(ffiImport); | |
| if (pack != null) { | |
| s.write('@$ffiPrefix.Packed($pack)\n'); | |
| } | |
| final dartClassName = isOpaque | |
| ? 'Opaque' | |
| : this is Struct | |
| ? 'Struct' | |
| : 'Union'; | |
| // Write class declaration. | |
| s.write('final class $enclosingClassName extends '); | |
| s.write('$ffiPrefix.$dartClassName{\n'); | |
| const depth = ' '; | |
| for (final m in members) { | |
| s.write(makeDartDoc(m.dartDoc, indent: depth)); | |
| if (m.type case final ConstantArray arrayType) { | |
| s.writeln(makeArrayAnnotation(w, arrayType)); | |
| s.write('${depth}external ${_getInlineArrayTypeString(m.type, w)} '); | |
| s.write('${m.name};\n\n'); | |
| } else { | |
| if (!m.type.sameFfiDartAndCType) { | |
| s.write('$depth@${m.type.getCType(context)}()\n'); | |
| } | |
| final memberName = m.type.sameDartAndFfiDartType | |
| ? m.name | |
| : '${m.name}AsInt'; | |
| s.write( | |
| '${depth}external ${m.type.getFfiDartType(context)} $memberName;\n\n', | |
| ); | |
| } | |
| if (m.type case EnumClass( | |
| :final effectiveStyle, | |
| ) when effectiveStyle == EnumStyle.dartEnum) { | |
| final enumName = m.type.getDartType(context); | |
| final memberName = m.name; | |
| s.write( | |
| '$enumName get $memberName => ' | |
| '$enumName.fromValue(${memberName}AsInt);\n', | |
| ); | |
| s.write( | |
| 'set $memberName($enumName value) => ' | |
| '${memberName}AsInt = value.value;\n\n', | |
| ); | |
| } | |
| } | |
| if (_shouldGenerateAllocate()) { | |
| s.write(_generateAllocateMethod(enclosingClassName, ffiPrefix)); | |
| } | |
| s.write('}\n\n'); | |
| final bindingType = this is Struct | |
| ? BindingStringType.struct | |
| : BindingStringType.union; | |
| return BindingString(type: bindingType, string: s.toString()); | |
| } | |
| @override | |
| bool get isIncompleteCompound => isIncomplete; | |
| @override | |
| String getCType(Context context) { | |
| final builtInName = context.objCBuiltInFunctions.getBuiltInCompoundName( | |
| originalName, | |
| ); | |
| return builtInName != null | |
| ? '${context.libs.prefix(objcPkgImport)}.$builtInName' | |
| : name; | |
| } | |
| @override | |
| String getNativeType(Context context, {String varName = ''}) => | |
| '$nativeType $varName'; | |
| @override | |
| bool get sameFfiDartAndCType => true; | |
| @override | |
| void visitChildren(Visitor visitor) { | |
| super.visitChildren(visitor); | |
| visitor.visitAll(members); | |
| visitor.visit(ffiImport); | |
| if (isObjCImport) visitor.visit(objcPkgImport); | |
| } | |
| @override | |
| void visit(Visitation visitation) => visitation.visitCompound(this); | |
| } | |
| class CompoundMember extends AstNode { | |
| final String? dartDoc; | |
| final String originalName; | |
| final Type type; | |
| final Symbol symbol; | |
| String get name => symbol.name; | |
| CompoundMember({ | |
| String? originalName, | |
| required String name, | |
| required this.type, | |
| this.dartDoc, | |
| }) : originalName = originalName ?? name, | |
| symbol = Symbol(name, SymbolKind.field); | |
| @override | |
| void visitChildren(Visitor visitor) { | |
| super.visitChildren(visitor); | |
| visitor.visit(symbol); | |
| visitor.visit(type); | |
| } | |
| } |