| // 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 'dart:ffi'; |
| |
| import 'package:ffi/ffi.dart'; |
| import 'package:ffigen/src/code_generator.dart'; |
| import 'package:ffigen/src/config_provider/config_types.dart'; |
| import 'package:logging/logging.dart'; |
| |
| import 'clang_bindings/clang_bindings.dart' as clang_types; |
| import 'data.dart'; |
| import 'type_extractor/extractor.dart'; |
| |
| /// Check [resultCode] of [clang.clang_visitChildren_wrap]. |
| /// |
| /// Throws exception if resultCode is not 0. |
| void visitChildrenResultChecker(int resultCode) { |
| if (resultCode != 0) { |
| throw Exception( |
| 'Exception thrown in a dart function called via C, use --verbose to see more details'); |
| } |
| } |
| |
| /// Logs the warnings/errors returned by clang for a translation unit. |
| void logTuDiagnostics( |
| Pointer<clang_types.CXTranslationUnitImpl> tu, |
| Logger logger, |
| String header, |
| ) { |
| final total = clang.clang_getNumDiagnostics(tu); |
| if (total == 0) { |
| return; |
| } |
| |
| logger.severe('Header $header: Total errors/warnings: $total.'); |
| for (var i = 0; i < total; i++) { |
| final diag = clang.clang_getDiagnostic(tu, i); |
| final cxstring = clang.clang_formatDiagnostic( |
| diag, |
| clang_types |
| .CXDiagnosticDisplayOptions.CXDiagnostic_DisplaySourceLocation | |
| clang_types.CXDiagnosticDisplayOptions.CXDiagnostic_DisplayColumn | |
| clang_types |
| .CXDiagnosticDisplayOptions.CXDiagnostic_DisplayCategoryName, |
| ); |
| logger.severe(' ' + cxstring.toStringAndDispose()); |
| clang.clang_disposeDiagnostic(diag); |
| } |
| } |
| |
| extension CXSourceRangeExt on Pointer<clang_types.CXSourceRange> { |
| void dispose() { |
| calloc.free(this); |
| } |
| } |
| |
| extension CXCursorExt on clang_types.CXCursor { |
| String usr() { |
| return clang.clang_getCursorUSR(this).toStringAndDispose(); |
| } |
| |
| /// Returns the kind int from [clang_types.CXCursorKind]. |
| int kind() { |
| return clang.clang_getCursorKind(this); |
| } |
| |
| /// Name of the cursor (E.g function name, Struct name, Parameter name). |
| String spelling() { |
| return clang.clang_getCursorSpelling(this).toStringAndDispose(); |
| } |
| |
| /// Spelling for a [clang_types.CXCursorKind], useful for debug purposes. |
| String kindSpelling() { |
| return clang |
| .clang_getCursorKindSpelling(clang.clang_getCursorKind(this)) |
| .toStringAndDispose(); |
| } |
| |
| /// for debug: returns [spelling] [kind] [kindSpelling] [type] [typeSpelling]. |
| String completeStringRepr() { |
| final cxtype = type(); |
| final s = |
| '(Cursor) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}, type: ${cxtype.kind}, typeSpelling: ${cxtype.spelling()}, usr: ${usr()}'; |
| return s; |
| } |
| |
| /// Dispose type using [type.dispose]. |
| clang_types.CXType type() { |
| return clang.clang_getCursorType(this); |
| } |
| |
| /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl]. |
| /// |
| /// Dispose type using [type.dispose]. |
| clang_types.CXType returnType() { |
| return clang.clang_getResultType(type()); |
| } |
| |
| String sourceFileName() { |
| final cxsource = clang.clang_getCursorLocation(this); |
| final cxfilePtr = calloc<Pointer<Void>>(); |
| final line = calloc<Uint32>(); |
| final column = calloc<Uint32>(); |
| final offset = calloc<Uint32>(); |
| |
| // Puts the values in these pointers. |
| clang.clang_getFileLocation(cxsource, cxfilePtr, line, column, offset); |
| final s = clang.clang_getFileName(cxfilePtr.value).toStringAndDispose(); |
| |
| calloc.free(cxfilePtr); |
| calloc.free(line); |
| calloc.free(column); |
| calloc.free(offset); |
| return s; |
| } |
| } |
| |
| const commentPrefix = '/// '; |
| const nesting = ' '; |
| |
| /// Stores the [clang_types.CXSourceRange] of the last comment. |
| clang_types.CXSourceRange? lastCommentRange; |
| |
| /// Returns a cursor's associated comment. |
| /// |
| /// The given string is wrapped at line width = 80 - [indent]. The [indent] is |
| /// [commentPrefix.dimensions] by default because a comment starts with |
| /// [commentPrefix]. |
| String? getCursorDocComment(clang_types.CXCursor cursor, |
| [int indent = commentPrefix.length]) { |
| String? formattedDocComment; |
| final currentCommentRange = clang.clang_Cursor_getCommentRange(cursor); |
| |
| // See if this comment and the last comment both point to the same source |
| // range. |
| if (lastCommentRange != null && |
| clang.clang_equalRanges(lastCommentRange!, currentCommentRange) != 0) { |
| formattedDocComment = null; |
| } else { |
| switch (config.commentType.length) { |
| case CommentLength.full: |
| formattedDocComment = removeRawCommentMarkups( |
| clang.clang_Cursor_getRawCommentText(cursor).toStringAndDispose()); |
| break; |
| case CommentLength.brief: |
| formattedDocComment = _wrapNoNewLineString( |
| clang.clang_Cursor_getBriefCommentText(cursor).toStringAndDispose(), |
| 80 - indent); |
| break; |
| default: |
| formattedDocComment = null; |
| } |
| } |
| lastCommentRange = currentCommentRange; |
| return formattedDocComment; |
| } |
| |
| /// Wraps [string] according to given [lineWidth]. |
| /// |
| /// Wrapping will work properly only when String has no new lines |
| /// characters(\n). |
| String? _wrapNoNewLineString(String? string, int lineWidth) { |
| if (string == null || string.isEmpty) { |
| return null; |
| } |
| final sb = StringBuffer(); |
| |
| final words = string.split(' '); |
| |
| sb.write(words[0]); |
| var trackLineWidth = words[0].length; |
| for (var i = 1; i < words.length; i++) { |
| final word = words[i]; |
| if (trackLineWidth + word.length < lineWidth) { |
| sb.write(' '); |
| sb.write(word); |
| trackLineWidth += word.length + 1; |
| } else { |
| sb.write('\n'); |
| sb.write(word); |
| trackLineWidth = word.length; |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /// Removes /*, */ and any *'s in the beginning of a line. |
| String? removeRawCommentMarkups(String? string) { |
| if (string == null || string.isEmpty) { |
| return null; |
| } |
| final sb = StringBuffer(); |
| |
| // Remove comment identifiers (`/** * */`, `///`, `//`) from lines. |
| if (string.contains(RegExp(r'^\s*\/\*+'))) { |
| string = string.replaceFirst(RegExp(r'^\s*\/\*+\s*'), ''); |
| string = string.replaceFirst(RegExp(r'\s*\*+\/$'), ''); |
| string.split('\n').forEach((element) { |
| element = element.replaceFirst(RegExp(r'^\s*\**\s*'), ''); |
| sb.writeln(element); |
| }); |
| } else if (string.contains(RegExp(r'^\s*\/\/\/?\s*'))) { |
| string.split('\n').forEach((element) { |
| element = element.replaceFirst(RegExp(r'^\s*\/\/\/?\s*'), ''); |
| sb.writeln(element); |
| }); |
| } |
| |
| return sb.toString().trim(); |
| } |
| |
| bool isForwardDeclaration(clang_types.CXCursor cursor) { |
| return clang.clang_Cursor_isNull(clang.clang_getCursorDefinition(cursor)) == |
| 0; |
| } |
| |
| extension CXTypeExt on clang_types.CXType { |
| /// Get code_gen [Type] representation of [clang_types.CXType]. |
| Type toCodeGenType() { |
| return getCodeGenType(this); |
| } |
| |
| /// Spelling for a [clang_types.CXTypeKind], useful for debug purposes. |
| String spelling() { |
| return clang.clang_getTypeSpelling(this).toStringAndDispose(); |
| } |
| |
| /// Returns the typeKind int from [clang_types.CXTypeKind]. |
| int kind() { |
| return this.kind; |
| } |
| |
| String kindSpelling() { |
| return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose(); |
| } |
| |
| int alignment() { |
| return clang.clang_Type_getAlignOf(this); |
| } |
| |
| /// For debugging: returns [spelling] [kind] [kindSpelling]. |
| String completeStringRepr() { |
| final s = |
| '(Type) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}'; |
| return s; |
| } |
| } |
| |
| extension CXStringExt on clang_types.CXString { |
| /// Convert CXString to a Dart string |
| /// |
| /// Make sure to dispose CXstring using dispose method, or use the |
| /// [toStringAndDispose] method. |
| String string() { |
| final cstring = clang.clang_getCString(this); |
| if (cstring != nullptr) { |
| return cstring.cast<Utf8>().toDartString(); |
| } else { |
| return ''; |
| } |
| } |
| |
| /// Converts CXString to dart string and disposes CXString. |
| String toStringAndDispose() { |
| // Note: clang_getCString_wrap returns a const char *, calling free will result in error. |
| final s = string(); |
| clang.clang_disposeString(this); |
| return s; |
| } |
| |
| void dispose() { |
| clang.clang_disposeString(this); |
| } |
| } |
| |
| /// Converts a [List<String>] to [Pointer<Pointer<Utf8>>]. |
| Pointer<Pointer<Utf8>> createDynamicStringArray(List<String> list) { |
| final nativeCmdArgs = calloc<Pointer<Utf8>>(list.length); |
| |
| for (var i = 0; i < list.length; i++) { |
| nativeCmdArgs[i] = list[i].toNativeUtf8(); |
| } |
| |
| return nativeCmdArgs; |
| } |
| |
| extension DynamicCStringArray on Pointer<Pointer<Utf8>> { |
| // Properly disposes a Pointer<Pointer<Utf8>, ensure that sure length is correct. |
| void dispose(int length) { |
| for (var i = 0; i < length; i++) { |
| calloc.free(this[i]); |
| } |
| calloc.free(this); |
| } |
| } |
| |
| class Stack<T> { |
| final _stack = <T>[]; |
| |
| T get top => _stack.last; |
| T pop() => _stack.removeLast(); |
| void push(T item) => _stack.add(item); |
| } |
| |
| class IncrementalNamer { |
| final _incrementedStringCounters = <String, int>{}; |
| |
| /// Appends `<int>` to base. <int> is incremented on every call. |
| String name(String base) { |
| var i = _incrementedStringCounters[base] ?? 0; |
| i++; |
| _incrementedStringCounters[base] = i; |
| return '$base$i'; |
| } |
| } |
| |
| class Macro { |
| final String usr; |
| final String? originalName; |
| |
| Macro(this.usr, this.originalName); |
| } |
| |
| /// Tracks if a binding is 'seen' or not. |
| class BindingsIndex { |
| // Tracks if bindings are already seen, Map key is USR obtained from libclang. |
| final Map<String, Struc> _structs = {}; |
| final Map<String, Union> _unions = {}; |
| final Map<String, Func> _functions = {}; |
| final Map<String, EnumClass> _enumClass = {}; |
| final Map<String, Constant> _unnamedEnumConstants = {}; |
| final Map<String, String> _macros = {}; |
| final Map<String, Global> _globals = {}; |
| |
| /// Contains usr for typedefs which cannot be generated. |
| final Set<String> _unsupportedTypealiases = {}; |
| final Map<String, Typealias> _typealiases = {}; |
| |
| /// Index for headers. |
| final Map<String, bool> _headerCache = {}; |
| |
| bool isSeenStruct(String usr) { |
| return _structs.containsKey(usr); |
| } |
| |
| void addStructToSeen(String usr, Compound struc) { |
| _structs[usr] = struc as Struc; |
| } |
| |
| Struc? getSeenStruct(String usr) { |
| return _structs[usr]; |
| } |
| |
| bool isSeenUnion(String usr) { |
| return _unions.containsKey(usr); |
| } |
| |
| void addUnionToSeen(String usr, Compound union) { |
| _unions[usr] = union as Union; |
| } |
| |
| Union? getSeenUnion(String usr) { |
| return _unions[usr]; |
| } |
| |
| bool isSeenFunc(String usr) { |
| return _functions.containsKey(usr); |
| } |
| |
| void addFuncToSeen(String usr, Func func) { |
| _functions[usr] = func; |
| } |
| |
| Func? getSeenFunc(String usr) { |
| return _functions[usr]; |
| } |
| |
| bool isSeenEnumClass(String usr) { |
| return _enumClass.containsKey(usr); |
| } |
| |
| void addEnumClassToSeen(String usr, EnumClass enumClass) { |
| _enumClass[usr] = enumClass; |
| } |
| |
| EnumClass? getSeenEnumClass(String usr) { |
| return _enumClass[usr]; |
| } |
| |
| bool isSeenUnnamedEnumConstant(String usr) { |
| return _unnamedEnumConstants.containsKey(usr); |
| } |
| |
| void addUnnamedEnumConstantToSeen(String usr, Constant enumConstant) { |
| _unnamedEnumConstants[usr] = enumConstant; |
| } |
| |
| Constant? getSeenUnnamedEnumConstant(String usr) { |
| return _unnamedEnumConstants[usr]; |
| } |
| |
| bool isSeenGlobalVar(String usr) { |
| return _globals.containsKey(usr); |
| } |
| |
| void addGlobalVarToSeen(String usr, Global global) { |
| _globals[usr] = global; |
| } |
| |
| Global? getSeenGlobalVar(String usr) { |
| return _globals[usr]; |
| } |
| |
| bool isSeenMacro(String usr) { |
| return _macros.containsKey(usr); |
| } |
| |
| void addMacroToSeen(String usr, String macro) { |
| _macros[usr] = macro; |
| } |
| |
| String? getSeenMacro(String usr) { |
| return _macros[usr]; |
| } |
| |
| bool isSeenTypealias(String usr) { |
| return _typealiases.containsKey(usr); |
| } |
| |
| void addTypealiasToSeen(String usr, Typealias t) { |
| _typealiases[usr] = t; |
| } |
| |
| bool isSeenUnsupportedTypealias(String usr) { |
| return _unsupportedTypealiases.contains(usr); |
| } |
| |
| void addUnsupportedTypealiasToSeen(String usr) { |
| _unsupportedTypealiases.add(usr); |
| } |
| |
| Typealias? getSeenTypealias(String usr) { |
| return _typealiases[usr]; |
| } |
| |
| bool isSeenHeader(String source) { |
| return _headerCache.containsKey(source); |
| } |
| |
| void addHeaderToSeen(String source, bool includeStatus) { |
| _headerCache[source] = includeStatus; |
| } |
| |
| bool? getSeenHeaderStatus(String source) { |
| return _headerCache[source]; |
| } |
| } |