| // 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 'dart:io'; | |
| import 'dart:typed_data'; | |
| import 'package:ffi/ffi.dart'; | |
| import 'package:logging/logging.dart'; | |
| import '../code_generator.dart'; | |
| import '../code_generator/utils.dart'; | |
| import '../config_provider/config_types.dart'; | |
| import '../context.dart'; | |
| import '../strings.dart'; | |
| import '../strings.dart' as strings; | |
| import 'clang_bindings/clang_bindings.dart' as clang_types; | |
| import 'type_extractor/extractor.dart'; | |
| const exceptionalVisitorReturn = | |
| clang_types.CXChildVisitResult.CXChildVisit_Break; | |
| typedef _CursorVisitorCallback = | |
| Int32 Function(clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>); | |
| /// Logs the warnings/errors returned by clang for a translation unit. | |
| void logTuDiagnostics( | |
| Pointer<clang_types.CXTranslationUnitImpl> tu, | |
| Context context, | |
| String header, { | |
| Level logLevel = Level.SEVERE, | |
| }) { | |
| final total = clang.clang_getNumDiagnostics(tu); | |
| if (total == 0) { | |
| return; | |
| } | |
| context.logger.log( | |
| logLevel, | |
| 'Header $header: Total errors/warnings: $total.', | |
| ); | |
| for (var i = 0; i < total; i++) { | |
| final diag = clang.clang_getDiagnostic(tu, i); | |
| if (clang.clang_getDiagnosticSeverity(diag) >= | |
| clang_types.CXDiagnosticSeverity.CXDiagnostic_Warning) { | |
| context.hasSourceErrors = true; | |
| } | |
| final cxstring = clang.clang_formatDiagnostic( | |
| diag, | |
| clang_types | |
| .CXDiagnosticDisplayOptions | |
| .CXDiagnostic_DisplaySourceLocation | | |
| clang_types.CXDiagnosticDisplayOptions.CXDiagnostic_DisplayColumn | | |
| clang_types | |
| .CXDiagnosticDisplayOptions | |
| .CXDiagnostic_DisplayCategoryName, | |
| ); | |
| context.logger.log(logLevel, ' ${cxstring.toStringAndDispose()}'); | |
| clang.clang_disposeDiagnostic(diag); | |
| } | |
| } | |
| extension CXSourceLocationExt on clang_types.CXSourceLocation { | |
| (String, int) get fileAndOffset { | |
| final filePtr = calloc<Pointer<Void>>(); | |
| final offsetPtr = calloc<UnsignedInt>(); | |
| clang.clang_getFileLocation(this, filePtr, nullptr, nullptr, offsetPtr); | |
| final file = clang.clang_getFileName(filePtr.value).toStringAndDispose(); | |
| final offset = offsetPtr.value; | |
| calloc.free(filePtr); | |
| calloc.free(offsetPtr); | |
| return (file, offset); | |
| } | |
| } | |
| extension CXSourceRangeExt on clang_types.CXSourceRange { | |
| clang_types.CXSourceLocation get start => clang.clang_getRangeStart(this); | |
| clang_types.CXSourceLocation get end => clang.clang_getRangeEnd(this); | |
| ((String, int), (String, int)) toTuple() => | |
| (start.fileAndOffset, end.fileAndOffset); | |
| String? readSourceText() { | |
| final ((path, startOffset), (_, endOffset)) = toTuple(); | |
| try { | |
| final file = File(path).openSync(); | |
| try { | |
| file.setPositionSync(startOffset); | |
| return String.fromCharCodes(file.readSync(endOffset - startOffset)); | |
| } finally { | |
| file.closeSync(); | |
| } | |
| } catch (_) {} | |
| return null; | |
| } | |
| } | |
| extension CXSourceRangePtrExt on Pointer<clang_types.CXSourceRange> { | |
| void dispose() { | |
| calloc.free(this); | |
| } | |
| } | |
| extension CXCursorExt on clang_types.CXCursor { | |
| bool get isNull => clang.clang_Cursor_isNull(this) != 0; | |
| bool get isDefinition => clang.clang_isCursorDefinition(this) != 0; | |
| clang_types.CXCursor get definition => clang.clang_getCursorDefinition(this); | |
| clang_types.CXSourceRange get extent => clang.clang_getCursorExtent(this); | |
| String usr() { | |
| var res = clang.clang_getCursorUSR(this).toStringAndDispose(); | |
| assert(!res.contains(synthUsrChar)); | |
| if (isAnonymousRecordDecl()) { | |
| res += '$synthUsrChar anonRec: offset:${sourceFileOffset()}'; | |
| } | |
| return res; | |
| } | |
| /// The [Declaration] handed to `importType` for this cursor. | |
| /// | |
| /// A C++ type nested in a namespace or record is named by its qualified | |
| /// name, matching the `originalName` of the binding a `Visitor` would see. | |
| /// Anonymous cursors keep an empty name. | |
| Declaration declaration() { | |
| final leaf = spelling(); | |
| return Declaration( | |
| usr: usr(), | |
| originalName: leaf.isEmpty ? '' : qualifiedNameFromCursor(this, leaf), | |
| ); | |
| } | |
| /// 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(); | |
| } | |
| /// Get code_gen [Type] representation of [clang_types.CXType]. | |
| Type toCodeGenType(Context context) { | |
| return getCodeGenType(context, type(), originalCursor: this); | |
| } | |
| /// 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; | |
| } | |
| /// Type associated with the pointer if any. Type will have kind | |
| /// clang.CXTypeKind.CXType_Invalid otherwise. | |
| clang_types.CXType type() { | |
| return clang.clang_getCursorType(this); | |
| } | |
| /// Determine whether the given cursor | |
| /// represents an anonymous record declaration. | |
| bool isAnonymousRecordDecl() { | |
| return clang.clang_Cursor_isAnonymousRecordDecl(this) == 1; | |
| } | |
| /// Only valid for clang.CXCursorKind.CXCursor_FunctionDecl. Type will have | |
| /// kind clang.CXTypeKind.CXType_Invalid otherwise. | |
| clang_types.CXType returnType() { | |
| return clang.clang_getResultType(type()); | |
| } | |
| /// Returns the file name of the file that the cursor is inside. | |
| String sourceFileName() { | |
| final cxsource = clang.clang_getCursorLocation(this); | |
| final cxfilePtr = calloc<Pointer<Void>>(); | |
| // Puts the values in these pointers. | |
| clang.clang_getFileLocation(cxsource, cxfilePtr, nullptr, nullptr, nullptr); | |
| final s = clang.clang_getFileName(cxfilePtr.value).toStringAndDispose(); | |
| calloc.free(cxfilePtr); | |
| return s; | |
| } | |
| int sourceFileOffset() { | |
| final cxsource = clang.clang_getCursorLocation(this); | |
| final cxOffset = calloc<UnsignedInt>(); | |
| // Puts the values in these pointers. | |
| clang.clang_getFileLocation(cxsource, nullptr, nullptr, nullptr, cxOffset); | |
| final offset = cxOffset.value; | |
| calloc.free(cxOffset); | |
| return offset; | |
| } | |
| /// Returns whether the file that the cursor is inside is a system header. | |
| bool isInSystemHeader() { | |
| final location = clang.clang_getCursorLocation(this); | |
| return clang.clang_Location_isInSystemHeader(location) != 0; | |
| } | |
| /// Visits all the direct children of this cursor. | |
| /// | |
| /// [callback] is called with the child cursor. The iteration continues until | |
| /// completion. The only way it can be interrupted is if the [callback] | |
| /// throws, in which case this method also throws. | |
| void visitChildren(void Function(clang_types.CXCursor child) callback) { | |
| final completed = visitChildrenMayBreak((clang_types.CXCursor child) { | |
| callback(child); | |
| return true; | |
| }); | |
| if (!completed) { | |
| throw Exception( | |
| 'Exception thrown in a dart function called via C, ' | |
| 'use --verbose to see more details', | |
| ); | |
| } | |
| } | |
| /// Visits all the direct children of this cursor. | |
| /// | |
| /// [callback] is called with the child cursor. If [callback] returns true, | |
| /// the iteration will continue. Otherwise, if [callback] returns false, the | |
| /// iteration will stop. | |
| /// | |
| /// Returns whether the iteration completed. | |
| bool visitChildrenMayBreak( | |
| bool Function(clang_types.CXCursor child) callback, | |
| ) => visitChildrenMayRecurse( | |
| (clang_types.CXCursor child, clang_types.CXCursor parent) => callback(child) | |
| ? clang_types.CXChildVisitResult.CXChildVisit_Continue | |
| : clang_types.CXChildVisitResult.CXChildVisit_Break, | |
| ); | |
| /// Visits all the direct children of this cursor. | |
| /// | |
| /// [callback] is called with the child cursor and parent cursor. If | |
| /// [callback] returns CXChildVisit_Continue, the iteration continues. If it | |
| /// returns CXChildVisit_Break the iteration stops. If it returns | |
| /// CXChildVisit_Recurse, the iteration recurses into the node. | |
| /// | |
| /// Returns whether the iteration completed. | |
| bool visitChildrenMayRecurse( | |
| int Function(clang_types.CXCursor child, clang_types.CXCursor parent) | |
| callback, | |
| ) { | |
| final visitor = NativeCallable<_CursorVisitorCallback>.isolateLocal(( | |
| clang_types.CXCursor child, | |
| clang_types.CXCursor parent, | |
| Pointer<Void> clientData, | |
| ) { | |
| try { | |
| return callback(child, parent); | |
| } catch (e, st) { | |
| print(e); | |
| print(st); | |
| rethrow; | |
| } | |
| }, exceptionalReturn: exceptionalVisitorReturn); | |
| final result = clang.clang_visitChildren( | |
| this, | |
| visitor.nativeFunction.cast(), | |
| nullptr, | |
| ); | |
| visitor.close(); | |
| return result == 0; | |
| } | |
| /// Returns the first child where `predicate(child)` is true, or null if there | |
| /// isn't one. | |
| clang_types.CXCursor? findChildWhere( | |
| bool Function(clang_types.CXCursor) predicate, | |
| ) { | |
| clang_types.CXCursor? result; | |
| visitChildrenMayBreak((child) { | |
| if (predicate(child)) { | |
| result = child; | |
| return false; | |
| } | |
| return true; | |
| }); | |
| return result; | |
| } | |
| /// Returns the first child with the given CXCursorKind, or null if there | |
| /// isn't one. | |
| clang_types.CXCursor? findChildWithKind(int kind) => | |
| findChildWhere((child) => child.kind == kind); | |
| /// Returns whether there is a child with the given CXCursorKind. | |
| bool hasChildWithKind(int kind) => findChildWithKind(kind) != null; | |
| /// Recursively print the AST, for debugging. | |
| void printAst([int maxDepth = 3]) => _printAst(maxDepth, 0); | |
| void _printAst(int maxDepth, int depth) { | |
| if (depth > maxDepth) { | |
| return; | |
| } | |
| print((' ' * depth) + completeStringRepr()); | |
| visitChildren((child) => child._printAst(maxDepth, depth + 1)); | |
| } | |
| } | |
| const commentPrefix = '/// '; | |
| const nesting = ' '; | |
| /// Returns a cursor's associated comment. | |
| /// | |
| /// The given string is wrapped at line width = 80 - [indent]. The [indent] is | |
| /// [commentPrefix].length by default because a comment starts with | |
| /// [commentPrefix]. | |
| String? getCursorDocComment( | |
| Context context, | |
| clang_types.CXCursor cursor, { | |
| int indent = commentPrefix.length, | |
| String? fallbackComment, | |
| String? availability, | |
| }) { | |
| String? formattedDocComment; | |
| final currentCommentRange = clang.clang_Cursor_getCommentRange(cursor); | |
| // Only report the comment if we haven't reported this comment before. | |
| if (!context.reportedCommentRanges.add(currentCommentRange.toTuple())) { | |
| formattedDocComment = null; | |
| } else { | |
| switch (context.config.output.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; | |
| } | |
| } | |
| final docs = [formattedDocComment ?? fallbackComment, availability].nonNulls; | |
| return docs.isEmpty ? null : docs.join('\n\n'); | |
| } | |
| /// 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(lineBreakRegex).forEach((element) { | |
| element = element.replaceFirst(RegExp(r'^\s*\**\s*'), ''); | |
| sb.writeln(element); | |
| }); | |
| } else if (string.contains(RegExp(r'^\s*\/\/\/?\s*'))) { | |
| string.split(lineBreakRegex).forEach((element) { | |
| element = element.replaceFirst(RegExp(r'^\s*\/\/\/?\s*'), ''); | |
| sb.writeln(element); | |
| }); | |
| } | |
| return sb.toString().trim(); | |
| } | |
| extension CXTypeExt on clang_types.CXType { | |
| /// Get code_gen [Type] representation of [clang_types.CXType]. | |
| Type toCodeGenType(Context context, {bool supportNonInlineArray = false}) { | |
| return getCodeGenType( | |
| context, | |
| this, | |
| supportNonInlineArray: supportNonInlineArray, | |
| ); | |
| } | |
| /// 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); | |
| } | |
| int size() { | |
| return clang.clang_Type_getSizeOf(this); | |
| } | |
| /// For debugging: returns [spelling] [kind] [kindSpelling]. | |
| String completeStringRepr() { | |
| final s = | |
| '(Type) spelling: ${spelling()}, kind: ${kind()}, ' | |
| 'kindSpelling: ${kindSpelling()}'; | |
| return s; | |
| } | |
| bool get isConstQualified { | |
| return clang.clang_isConstQualifiedType(this) != 0; | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| extension CXVersionExt on clang_types.CXVersion { | |
| Version? get triple { | |
| // -1 can appear in a CXVersion, and has various meanings. | |
| // Whenever one of the fields is -1, the subsequent fields are also -1 (eg | |
| // you can't have Minor=-1 and Subminor=4). If all 3 fields are -1, it means | |
| // "no version", and we return null. Otherwise, we treat a -1 in any field | |
| // as a 0. | |
| if (Major < 0) return null; | |
| return Version(Major, Minor < 0 ? 0 : Minor, Subminor < 0 ? 0 : Subminor); | |
| } | |
| String string() => '$Major.$Minor.$Subminor'; | |
| } | |
| /// 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 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, Func> _functions = {}; | |
| final Map<String, UnnamedEnumConstant> _unnamedEnumConstants = {}; | |
| final Map<String, String> _macros = {}; | |
| final Map<String, Global> _globals = {}; | |
| final Map<String, Typealias> _typealiases = {}; | |
| final Map<String, EnumClass> _enums = {}; | |
| final Map<String, Compound> _compounds = {}; | |
| final Map<String, CppClass> _cppClasses = {}; | |
| final Map<String, ObjCBlock> _objcBlocks = {}; | |
| final Map<String, ObjCInterface> _objcInterfaces = {}; | |
| final Map<String, ObjCProtocol> _objcProtocols = {}; | |
| final Map<String, ObjCCategory> _objcCategories = {}; | |
| /// Contains usr for typedefs which cannot be generated. | |
| final Set<String> _unsupportedTypealiases = {}; | |
| bool isSeenFunc(String usr) => _functions.containsKey(usr); | |
| void addFuncToSeen(String usr, Func func) => _functions[usr] = func; | |
| Func? getSeenFunc(String usr) => _functions[usr]; | |
| void addUnnamedEnumConstantToSeen( | |
| String usr, | |
| UnnamedEnumConstant enumConstant, | |
| ) => _unnamedEnumConstants[usr] = enumConstant; | |
| UnnamedEnumConstant? getSeenUnnamedEnumConstant(String usr) => | |
| _unnamedEnumConstants[usr]; | |
| bool isSeenGlobalVar(String usr) => _globals.containsKey(usr); | |
| void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global; | |
| Global? getSeenGlobalVar(String usr) => _globals[usr]; | |
| bool isSeenTypealias(String usr) => _typealiases.containsKey(usr); | |
| void addTypealiasToSeen(String usr, Typealias t) => _typealiases[usr] = t; | |
| Typealias? getSeenTypealias(String usr) => _typealiases[usr]; | |
| bool isSeenEnum(String usr) => _enums.containsKey(usr); | |
| void addEnumToSeen(String usr, EnumClass t) => _enums[usr] = t; | |
| EnumClass? getSeenEnum(String usr) => _enums[usr]; | |
| bool isSeenCompound(String usr) => _compounds.containsKey(usr); | |
| void addCompoundToSeen(String usr, Compound t) => _compounds[usr] = t; | |
| Compound? getSeenCompound(String usr) => _compounds[usr]; | |
| bool isSeenCppClass(String usr) => _cppClasses.containsKey(usr); | |
| void addCppClassToSeen(String usr, CppClass t) => _cppClasses[usr] = t; | |
| CppClass? getSeenCppClass(String usr) => _cppClasses[usr]; | |
| bool isSeenMacro(String usr) => _macros.containsKey(usr); | |
| void addMacroToSeen(String usr, String macro) => _macros[usr] = macro; | |
| bool isSeenUnsupportedTypealias(String usr) => | |
| _unsupportedTypealiases.contains(usr); | |
| void addUnsupportedTypealiasToSeen(String usr) => | |
| _unsupportedTypealiases.add(usr); | |
| void addObjCBlockToSeen(String key, ObjCBlock t) => _objcBlocks[key] = t; | |
| ObjCBlock? getSeenObjCBlock(String key) => _objcBlocks[key]; | |
| void addObjCInterfaceToSeen(String usr, ObjCInterface t) => | |
| _objcInterfaces[usr] = t; | |
| ObjCInterface? getSeenObjCInterface(String usr) => _objcInterfaces[usr]; | |
| bool isSeenObjCInterface(String usr) => _objcInterfaces.containsKey(usr); | |
| void addObjCProtocolToSeen(String usr, ObjCProtocol t) => | |
| _objcProtocols[usr] = t; | |
| ObjCProtocol? getSeenObjCProtocol(String usr) => _objcProtocols[usr]; | |
| bool isSeenObjCProtocol(String usr) => _objcProtocols.containsKey(usr); | |
| void addObjCCategoryToSeen(String usr, ObjCCategory t) => | |
| _objcCategories[usr] = t; | |
| ObjCCategory? getSeenObjCCategory(String usr) => _objcCategories[usr]; | |
| bool isSeenObjCCategory(String usr) => _objcCategories.containsKey(usr); | |
| } | |
| class CursorIndex { | |
| final Logger _logger; | |
| final _usrCursorDefinition = <String, clang_types.CXCursor>{}; | |
| CursorIndex(this._logger); | |
| /// Returns the Cursor definition (if found) or itself. | |
| clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) { | |
| final cursorDefinition = clang.clang_getCursorDefinition(cursor); | |
| if (clang.clang_Cursor_isNull(cursorDefinition) == 0) { | |
| return cursorDefinition; | |
| } else { | |
| final usr = cursor.usr(); | |
| if (_usrCursorDefinition.containsKey(usr)) { | |
| return _usrCursorDefinition[cursor.usr()]!; | |
| } else { | |
| _logger.warning( | |
| 'No definition found for declaration -' | |
| '${cursor.completeStringRepr()}', | |
| ); | |
| return cursor; | |
| } | |
| } | |
| } | |
| /// Saves cursor definition based on its kind. | |
| void saveDefinition(clang_types.CXCursor cursor) { | |
| switch (cursor.kind) { | |
| case clang_types.CXCursorKind.CXCursor_StructDecl: | |
| case clang_types.CXCursorKind.CXCursor_UnionDecl: | |
| case clang_types.CXCursorKind.CXCursor_EnumDecl: | |
| final usr = cursor.usr(); | |
| if (!_usrCursorDefinition.containsKey(usr)) { | |
| final cursorDefinition = clang.clang_getCursorDefinition(cursor); | |
| if (clang.clang_Cursor_isNull(cursorDefinition) == 0) { | |
| _usrCursorDefinition[usr] = cursorDefinition; | |
| } else { | |
| _logger.finest( | |
| 'Missing cursor definition in current translation unit: ' | |
| '${cursor.completeStringRepr()}', | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// Converts a double to a string, handling cases like Infinity and NaN. | |
| String writeDoubleAsString(double d) { | |
| if (d.isFinite) { | |
| return d.toString(); | |
| } else { | |
| // The only Non-Finite numbers are Infinity, NegativeInfinity and NaN. | |
| if (d.isInfinite) { | |
| return d.isNegative | |
| ? strings.doubleNegativeInfinity | |
| : strings.doubleInfinity; | |
| } | |
| return strings.doubleNaN; | |
| } | |
| } | |
| /// Escapes a Dart string so it can be placed inside single-quoted string | |
| /// literals in generated Dart source code. | |
| /// | |
| /// Handles characters that need escaping: `$ ' \` and control characters. | |
| String escapeDartString(String s) { | |
| final sb = StringBuffer(); | |
| for (final char in s.runes) { | |
| sb.write(_getWritableChar(char)); | |
| } | |
| return sb.toString(); | |
| } | |
| /// Gets a written representation string of a C string. | |
| /// | |
| /// E.g- For a string "Hello\nWorld", The new line character is converted to \n. | |
| /// Note: The string is considered to be Utf8, but is treated as Extended ASCII, | |
| /// if the conversion fails. | |
| String getWrittenStringRepresentation( | |
| String varName, | |
| Pointer<Char> strPtr, | |
| Context context, | |
| ) { | |
| try { | |
| // Consider string to be Utf8 encoded by default. | |
| // This throws a Format Exception if string isn't Utf8 so that we handle it | |
| // in the catch block. | |
| final result = strPtr.cast<Utf8>().toDartString(); | |
| return escapeDartString(result); | |
| } catch (e) { | |
| // Handle string if it isn't Utf8. String is considered to be | |
| // Extended ASCII in this case. | |
| context.logger.warning( | |
| "Couldn't decode string value for '$varName' as Utf8, using " | |
| 'ASCII instead.', | |
| ); | |
| final sb = StringBuffer(); | |
| final length = strPtr.cast<Utf8>().length; | |
| final charList = Uint8List.view( | |
| strPtr.cast<Uint8>().asTypedList(length).buffer, | |
| 0, | |
| length, | |
| ); | |
| for (final char in charList) { | |
| sb.write(_getWritableChar(char, utf8: false)); | |
| } | |
| return sb.toString(); | |
| } | |
| } | |
| /// Creates a writable char from [char] code. | |
| /// | |
| /// E.g- `\` is converted to `\\`. | |
| String _getWritableChar(int char, {bool utf8 = true}) { | |
| /// Handle control characters. | |
| if (char >= 0 && char < 32 || char == 127) { | |
| /// Handle these - `\b \t \n \v \f \r` as special cases. | |
| switch (char) { | |
| case 8: // \b | |
| return r'\b'; | |
| case 9: // \t | |
| return r'\t'; | |
| case 10: // \n | |
| return r'\n'; | |
| case 11: // \v | |
| return r'\v'; | |
| case 12: // \f | |
| return r'\f'; | |
| case 13: // \r | |
| return r'\r'; | |
| default: | |
| final h = char.toRadixString(16).toUpperCase().padLeft(2, '0'); | |
| return '\\x$h'; | |
| } | |
| } | |
| /// Handle characters - `$ ' \` these need to be escaped when writing to file. | |
| switch (char) { | |
| case 36: // $ | |
| return r'\$'; | |
| case 39: // ' | |
| return r"\'"; | |
| case 92: // \ | |
| return r'\\'; | |
| } | |
| /// In case encoding is not Utf8, we know all characters will fall in [0..255] | |
| /// Print range [128..255] as `\xHH`. | |
| if (!utf8) { | |
| final h = char.toRadixString(16).toUpperCase().padLeft(2, '0'); | |
| return '\\x$h'; | |
| } | |
| /// In all other cases, simply convert to string. | |
| return String.fromCharCode(char); | |
| } | |
| /// Builds the fully-qualified C++ name of the declaration at [cursor] by | |
| /// walking its semantic parents, e.g. an enum `Tone` nested in `struct | |
| /// Palette` in `namespace outer` yields `outer::Palette::Tone`. | |
| /// | |
| /// Anonymous scopes and `extern "C"` blocks contribute no segment. The walk | |
| /// stops at the first parent that is not a namespace or record, so a type | |
| /// declared at global scope or inside a function yields [leafName] alone. | |
| String qualifiedNameFromCursor(clang_types.CXCursor cursor, String leafName) { | |
| var name = leafName; | |
| var parent = clang.clang_getCursorSemanticParent(cursor); | |
| while (clang.clang_Cursor_isNull(parent) == 0 && | |
| _isNameScope(clang.clang_getCursorKind(parent))) { | |
| final spelling = parent.spelling(); | |
| if (spelling.isNotEmpty) name = '$spelling::$name'; | |
| parent = clang.clang_getCursorSemanticParent(parent); | |
| } | |
| return name; | |
| } | |
| /// Whether a cursor of [kind] is a scope that qualifies the names declared | |
| /// inside it (or, for `extern "C"`, is transparent to them). | |
| bool _isNameScope(int kind) => switch (kind) { | |
| clang_types.CXCursorKind.CXCursor_Namespace || | |
| clang_types.CXCursorKind.CXCursor_LinkageSpec || | |
| clang_types.CXCursorKind.CXCursor_StructDecl || | |
| clang_types.CXCursorKind.CXCursor_ClassDecl || | |
| clang_types.CXCursorKind.CXCursor_UnionDecl || | |
| clang_types.CXCursorKind.CXCursor_ClassTemplate || | |
| clang_types.CXCursorKind.CXCursor_ClassTemplatePartialSpecialization => true, | |
| _ => false, | |
| }; | |
| /// Joins the segments of a `::`-qualified C++ name with `$`, so that it can be | |
| /// used as a Dart identifier, e.g. `outer::inner::Color` becomes | |
| /// `outer$inner$Color`. | |
| String flattenQualifiedName(String qualifiedName) => | |
| qualifiedName.replaceAll('::', r'$'); |