blob: 10d1d573287fa1d1f451596184c7a6fcc2281c65 [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/utils.dart';
import 'binding.dart';
import 'typedef.dart';
/// To store generated String bindings.
class Writer {
final String? header;
/// Holds bindings, which lookup symbols.
final List<Binding> lookUpBindings;
/// Holds bindings which don't lookup symbols.
final List<Binding> noLookUpBindings;
/// Manages the `_SymbolAddress` class.
final symbolAddressWriter = SymbolAddressWriter();
late String _className;
final String? classDocComment;
late String _ffiLibraryPrefix;
String get ffiLibraryPrefix => _ffiLibraryPrefix;
late String _lookupFuncIdentifier;
String get lookupFuncIdentifier => _lookupFuncIdentifier;
late String _symbolAddressClassName;
late String _symbolAddressVariableName;
late String _symbolAddressLibraryVarName;
final bool dartBool;
/// Initial namers set after running constructor. Namers are reset to this
/// initial state everytime [generate] is called.
late UniqueNamer _initialTopLevelUniqueNamer, _initialWrapperLevelUniqueNamer;
/// Used by [Binding]s for generating required code.
late UniqueNamer _topLevelUniqueNamer, _wrapperLevelUniqueNamer;
UniqueNamer get topLevelUniqueNamer => _topLevelUniqueNamer;
UniqueNamer get wrapperLevelUniqueNamer => _wrapperLevelUniqueNamer;
late String _arrayHelperClassPrefix;
/// Guaranteed to be a unique prefix.
String get arrayHelperClassPrefix => _arrayHelperClassPrefix;
/// [_usedUpNames] should contain names of all the declarations which are
/// already used. This is used to avoid name collisions.
required this.lookUpBindings,
required this.noLookUpBindings,
required String className,
required this.dartBool,
}) {
final globalLevelNameSet = =>;
final wrapperLevelNameSet = =>;
final allNameSet = <String>{}
_initialTopLevelUniqueNamer = UniqueNamer(globalLevelNameSet);
_initialWrapperLevelUniqueNamer = UniqueNamer(wrapperLevelNameSet);
final allLevelsUniqueNamer = UniqueNamer(allNameSet);
/// Wrapper class name must be unique among all names.
_className = _resolveNameConflict(
name: className,
makeUnique: allLevelsUniqueNamer,
markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer],
/// [_ffiLibraryPrefix] should be unique unique among all names.
_ffiLibraryPrefix = _resolveNameConflict(
name: 'ffi',
makeUnique: allLevelsUniqueNamer,
markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer],
/// [_lookupFuncIdentifier] should be unique in top level.
_lookupFuncIdentifier = _resolveNameConflict(
name: '_lookup',
makeUnique: _initialTopLevelUniqueNamer,
markUsed: [_initialTopLevelUniqueNamer],
/// Resolve name conflicts of identifiers used for SymbolAddresses.
_symbolAddressClassName = _resolveNameConflict(
name: '_SymbolAddresses',
makeUnique: allLevelsUniqueNamer,
markUsed: [_initialWrapperLevelUniqueNamer, _initialTopLevelUniqueNamer],
_symbolAddressVariableName = _resolveNameConflict(
name: 'addresses',
makeUnique: _initialWrapperLevelUniqueNamer,
markUsed: [_initialWrapperLevelUniqueNamer],
_symbolAddressLibraryVarName = _resolveNameConflict(
name: '_library',
makeUnique: _initialWrapperLevelUniqueNamer,
markUsed: [_initialWrapperLevelUniqueNamer],
/// Finding a unique prefix for Array Helper Classes and store into
/// [_arrayHelperClassPrefix].
final base = 'ArrayHelper';
_arrayHelperClassPrefix = base;
var suffixInt = 0;
for (var i = 0; i < allNameSet.length; i++) {
if (allNameSet.elementAt(i).startsWith(_arrayHelperClassPrefix)) {
// Not a unique prefix, start over with a new suffix.
i = -1;
_arrayHelperClassPrefix = '$base$suffixInt';
/// Resolved name conflict using [makeUnique] and marks the result as used in
/// all [markUsed].
String _resolveNameConflict({
required String name,
required UniqueNamer makeUnique,
List<UniqueNamer> markUsed = const [],
}) {
final s = makeUnique.makeUnique(name);
for (final un in markUsed) {
return s;
/// Resets the namers to initial state. Namers are reset before generating.
void _resetUniqueNamersNamers() {
_topLevelUniqueNamer = _initialTopLevelUniqueNamer.clone();
_wrapperLevelUniqueNamer = _initialWrapperLevelUniqueNamer.clone();
/// Writes all bindings to a String.
String generate() {
final s = StringBuffer();
// Reset unique namers to initial state.
// Write file header (if any).
if (header != null) {
// Write auto generated declaration.
'AUTO GENERATED FILE, DO NOT EDIT.\n\nGenerated by `package:ffigen`.'));
// Write neccesary imports.
s.write("import 'dart:ffi' as $ffiLibraryPrefix;\n");
// Will contain duplicates, must be processed.
final rawDependencies = <Typedef>[];
/// Get typedef dependencies, these will be written at the end.
for (final b in lookUpBindings) {
for (final b in noLookUpBindings) {
// Dependencies, processed to remove duplicates and resolve name conflicts.
final dependencies = processDependencies(rawDependencies);
/// Write [lookUpBindings].
if (lookUpBindings.isNotEmpty) {
// Write doc comment for wrapper class.
if (classDocComment != null) {
// Write wrapper classs.
s.write('class $_className{\n');
// Write dylib.
s.write('/// Holds the symbol lookup function.\n');
'final $ffiLibraryPrefix.Pointer<T> Function<T extends $ffiLibraryPrefix.NativeType>(String symbolName) $lookupFuncIdentifier;\n');
//Write doc comment for wrapper class constructor.
s.write(makeDartDoc('The symbols are looked up in [dynamicLibrary].'));
// Write wrapper class constructor.
'$_className($ffiLibraryPrefix.DynamicLibrary dynamicLibrary): $lookupFuncIdentifier = dynamicLibrary.lookup;\n\n');
//Write doc comment for wrapper class named constructor.
s.write(makeDartDoc('The symbols are looked up with [lookup].'));
// Write wrapper class named constructor.
'$_className.fromLookup($ffiLibraryPrefix.Pointer<T> Function<T extends $ffiLibraryPrefix.NativeType>(String symbolName) lookup): $lookupFuncIdentifier = lookup;\n\n');
for (final b in lookUpBindings) {
if (symbolAddressWriter.shouldGenerate) {
if (symbolAddressWriter.shouldGenerate) {
/// Write [noLookUpBindings].
for (final b in noLookUpBindings) {
// Write typedef dependencies.
for (final d in dependencies) {
return s.toString();
/// Removes duplicates and resolves all name conflicts.
Set<Typedef> processDependencies(List<Typedef> dependencies) {
final processedDependencies = dependencies.toSet();
for (final d in processedDependencies) { = _makeUniqueTypedefName(;
return processedDependencies;
/// Returns a typedef name that is unique in both top level and wrapper level,
/// ans only marks it as used at top-level.
String _makeUniqueTypedefName(String name) {
final base = name;
var uniqueName = name;
var suffix = 0;
while (topLevelUniqueNamer.isUsed(uniqueName) ||
wrapperLevelUniqueNamer.isUsed(uniqueName)) {
uniqueName = base + suffix.toString();
return uniqueName;
/// Manages the generated `_SymbolAddress` class.
class SymbolAddressWriter {
final List<_SymbolAddressUnit> _addresses = [];
/// Used to check if we need to generate `_SymbolAddress` class.
bool get shouldGenerate => _addresses.isNotEmpty;
void addSymbol({
required String type,
required String name,
required String ptrName,
}) {
_addresses.add(_SymbolAddressUnit(type, name, ptrName));
String writeObject(Writer w) {
return 'late final ${w._symbolAddressVariableName} = ${w._symbolAddressClassName}(this);';
String writeClass(Writer w) {
final sb = StringBuffer();
sb.write('class ${w._symbolAddressClassName} {\n');
// Write Library object.
sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n');
// Write Constructor.
for (final address in _addresses) {
'${address.type} get ${} => ${w._symbolAddressLibraryVarName}.${address.ptrName};\n');
return sb.toString();
/// Holds the data for a single symbol address.
class _SymbolAddressUnit {
final String type, name, ptrName;
_SymbolAddressUnit(this.type,, this.ptrName);