[ffigen] First bits of Objective C support (#281)

diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart
index ab5e6c0..e620626 100644
--- a/pkgs/ffigen/lib/src/code_generator/imports.dart
+++ b/pkgs/ffigen/lib/src/code_generator/imports.dart
@@ -2,6 +2,8 @@
 // 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 'struc.dart';
+
 /// A library import which will be written as an import in the generated file.
 class LibraryImport {
   final String name;
@@ -51,3 +53,6 @@
 
 final sizeType = ImportedType(ffiPkgImport, 'Size', 'int');
 final wCharType = ImportedType(ffiPkgImport, 'WChar', 'int');
+
+final objCObjectType = Struc(name: 'ObjCObject');
+final objCSelType = Struc(name: 'ObjCSel');
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart
index 7acb2c8..29673a6 100644
--- a/pkgs/ffigen/lib/src/config_provider/config.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -23,9 +23,14 @@
   String get libclangDylib => _libclangDylib;
   late String _libclangDylib;
 
-  /// output file name.
+  /// Output file name.
   String get output => _output;
   late String _output;
+
+  /// Language that ffigen is consuming.
+  Language get language => _language;
+  late Language _language;
+
   // Holds headers and filters for header.
   Headers get headers => _headers;
   late Headers _headers;
@@ -220,6 +225,13 @@
         extractor: outputExtractor,
         extractedResult: (dynamic result) => _output = result as String,
       ),
+      [strings.language]: Specification<Language>(
+        requirement: Requirement.no,
+        validator: languageValidator,
+        extractor: languageExtractor,
+        defaultValue: () => Language.c,
+        extractedResult: (dynamic result) => _language = result as Language,
+      ),
       [strings.headers]: Specification<Headers>(
         requirement: Requirement.yes,
         validator: headersValidator,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart
index d973013..1f0c7a6 100644
--- a/pkgs/ffigen/lib/src/config_provider/config_types.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -9,6 +9,8 @@
 
 import 'path_finder.dart';
 
+enum Language { c, objc }
+
 class CommentType {
   CommentStyle style;
   CommentLength length;
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
index e5f0f41..aff84b2 100644
--- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
+++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -453,6 +453,28 @@
 bool outputValidator(List<String> name, dynamic value) =>
     checkType<String>(name, value);
 
+Language languageExtractor(dynamic value) {
+  if (value == strings.langC) {
+    return Language.c;
+  } else if (value == strings.langObjC) {
+    return Language.objc;
+  }
+  return Language.c;
+}
+
+bool languageValidator(List<String> name, dynamic value) {
+  if (value is String) {
+    if (value == strings.langC || value == strings.langObjC) {
+      return true;
+    }
+    _logger.severe("'$name' must be one of the following - "
+        "{${strings.langC}, ${strings.langObjC}}");
+    return false;
+  }
+  _logger.severe("Expected value of key '$name' to be a String.");
+  return false;
+}
+
 /// Returns true if [str] is not a full name.
 ///
 /// E.g `abc` is a full name, `abc.*` is not.
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart
index aaad693..0b3505b 100644
--- a/pkgs/ffigen/lib/src/header_parser/includer.dart
+++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -5,6 +5,8 @@
 /// Utility functions to check whether a binding should be parsed or not
 /// based on filters.
 
+import '../config_provider/config_types.dart';
+import '../strings.dart' as strings;
 import 'data.dart';
 
 bool _shouldIncludeDecl(String usr, String name,
@@ -66,6 +68,12 @@
     return false;
   }
 
+  // Objective C has some extra system headers that have a non-empty sourceFile.
+  if (config.language == Language.objc &&
+      sourceFile.startsWith(strings.clangDefaultObjCSystemHeaderPath)) {
+    return false;
+  }
+
   // Add header to seen if it's not.
   if (!bindingsIndex.isSeenHeader(sourceFile)) {
     bindingsIndex.addHeaderToSeen(
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart
index ffcfc8f..33c7072 100644
--- a/pkgs/ffigen/lib/src/header_parser/parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -56,17 +56,27 @@
   final index = clang.clang_createIndex(0, 0);
 
   Pointer<Pointer<Utf8>> clangCmdArgs = nullptr;
-  var cmdLen = 0;
+  final compilerOpts = List<String>.from(config.compilerOpts);
 
   /// Add compiler opt for comment parsing for clang based on config.
   if (config.commentType.length != CommentLength.none &&
       config.commentType.style == CommentStyle.any) {
-    config.compilerOpts.add(strings.fparseAllComments);
+    compilerOpts.add(strings.fparseAllComments);
   }
 
-  _logger.fine('CompilerOpts used: ${config.compilerOpts}');
-  clangCmdArgs = createDynamicStringArray(config.compilerOpts);
-  cmdLen = config.compilerOpts.length;
+  /// If the config targets Objective C, add a compiler opt for it.
+  if (config.language == Language.objc) {
+    compilerOpts.addAll(strings.clangLangObjC);
+    compilerOpts.add('-I' + strings.clangDefaultObjCSystemHeaderPath);
+    for (final sysHdr in strings.clangDefaultObjCSystemHeaders) {
+      compilerOpts.add(strings.clangInclude);
+      compilerOpts.add(sysHdr);
+    }
+  }
+
+  _logger.fine('CompilerOpts used: $compilerOpts');
+  clangCmdArgs = createDynamicStringArray(compilerOpts);
+  final cmdLen = compilerOpts.length;
 
   // Contains all bindings. A set ensures we never have duplicates.
   final bindings = <Binding>{};
@@ -111,7 +121,7 @@
   // Parse all saved macros.
   bindings.addAll(parseSavedMacros()!);
 
-  clangCmdArgs.dispose(config.compilerOpts.length);
+  clangCmdArgs.dispose(cmdLen);
   clang.clang_disposeIndex(index);
   return bindings.toList();
 }
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 8ea5d96..3c2f2dc 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -8,6 +8,7 @@
 import 'package:ffigen/src/strings.dart' as strings;
 import 'package:logging/logging.dart';
 
+import '../../config_provider/config_types.dart';
 import '../clang_bindings/clang_bindings.dart' as clang_types;
 import '../data.dart';
 import '../sub_parsers/compounddecl_parser.dart';
@@ -37,6 +38,20 @@
         ignoreFilter: ignoreFilter, pointerReference: pointerReference);
   }
 
+  // Objective C types skip the cache, and are conditional on the language flag.
+  if (config.language == Language.objc) {
+    switch (cxtype.kind) {
+      case clang_types.CXTypeKind.CXType_ObjCObjectPointer:
+      case clang_types.CXTypeKind.CXType_BlockPointer:
+      case clang_types.CXTypeKind.CXType_ObjCId:
+        return Type.pointer(Type.struct(objCObjectType));
+      case clang_types.CXTypeKind.CXType_ObjCSel:
+        return Type.pointer(Type.struct(objCSelType));
+      case clang_types.CXTypeKind.CXType_ObjCClass:
+        return Type.struct(objCObjectType);
+    }
+  }
+
   // If the type has a declaration cursor, then use the BindingsIndex to break
   // any potential cycles, and dedupe the Type.
   final cursor = clang.clang_getTypeDeclaration(cxtype);
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart
index 58e9b5d..2e57de7 100644
--- a/pkgs/ffigen/lib/src/strings.dart
+++ b/pkgs/ffigen/lib/src/strings.dart
@@ -29,6 +29,19 @@
 
 const output = 'output';
 
+const language = 'language';
+
+// String mappings for the Language enum.
+const langC = 'c';
+const langObjC = 'objc';
+
+// Clang command line args for Objective C.
+const clangLangObjC = ['-x', 'objective-c'];
+const clangInclude = '-include';
+const clangDefaultObjCSystemHeaderPath =
+    '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include';
+const clangDefaultObjCSystemHeaders = ['objc/NSObject.h'];
+
 const headers = 'headers';
 
 // Sub-fields of headers
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart
new file mode 100644
index 0000000..5cd8096
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart
@@ -0,0 +1,30 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+import 'package:ffi/ffi.dart' as pkg_ffi;
+
+class Foo extends ffi.Struct {
+  @BOOL()
+  external int someBool;
+
+  external ffi.Pointer<ObjCObject> anId;
+
+  external ffi.Pointer<ObjCSel> selector;
+
+  external ffi.Pointer<ObjCObject> object;
+
+  external ffi.Pointer<ObjCObject> clazz;
+
+  external ffi.Pointer<ObjCObject> blockThatReturnsAnInt;
+}
+
+typedef BOOL = pkg_ffi.SignedChar;
+
+class ObjCObject extends ffi.Opaque {}
+
+class ObjCSel extends ffi.Opaque {}
+
+const int true1 = 1;
+
+const int false1 = 0;
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
new file mode 100644
index 0000000..a2f7fc9
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
@@ -0,0 +1,8 @@
+struct Foo {
+  BOOL someBool;
+  id anId;
+  SEL selector;
+  NSObject* object;
+  Class* clazz;
+  int32_t (^blockThatReturnsAnInt)(void);
+};
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
new file mode 100644
index 0000000..8ee93f4
--- /dev/null
+++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2022, 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.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/config_provider.dart';
+import 'package:ffigen/src/header_parser.dart' as parser;
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import '../test_utils.dart';
+
+late Library actual;
+void main() {
+  group('objc_basic_types_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'Opaque Dependencies Test'
+${strings.output}: 'unused'
+${strings.language}: '${strings.langObjC}'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/objc_basic_types.h'
+${strings.structs}:
+  ${strings.include}:
+    - 'Foo'
+        ''') as yaml.YamlMap),
+      );
+    });
+    test('Expected bindings', () {
+      matchLibraryWithExpected(actual, [
+        'test',
+        'debug_generated',
+        'header_parser_objc_basic_types_test_output.dart'
+      ], [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_objc_basic_types_bindings.dart'
+      ]);
+    });
+  });
+}