Merge: objcwrapper: wrapper generator to access Objective-C services from Nit
authorJean Privat <jean@pryen.org>
Fri, 21 Aug 2015 19:48:25 +0000 (15:48 -0400)
committerJean Privat <jean@pryen.org>
Fri, 21 Aug 2015 19:48:25 +0000 (15:48 -0400)
This PR introduces *objcwrapper*, a wrapper generator to access Objective-C services from Nit. At this point, *objcwrapper* can parse large Objective-C header files from Apple, and generate valid Nit code to wrap simple classes.

This PR contains the work of @Tagachi (as the first commit), some of my refactoring and new features. Because of this, there is some rewrite between commits. I chose to keep them as-is because it may be useful to debug regressions in the future. Actually, some of the refactoring remove a few features (such as super class declaration parsing), they will be brought back in future PRs.

This is a good base for future work but also a work in progress. Please restrain comments to structural and algorithmic problems, the doc and manual will be completed with the missing features.

Working features:
- [x] Generate a Nit extern class for each Objective-C `@interface` block.
- [x] Generate Nit extern methods to wrap Objective-C methods (`- `).
- [x] Generate getters for attributes `@property`.
- [x] Basic type conversion from Objective-C to Nit.
- [x] Partial detection of supported and unsupported types.

Todo features:
- [ ] Static functions (`+`) as top-level methods.
- [ ] Attribute setter (when not `readonly`).
- [ ] Intro and use an `ObjcType` instead of a `String`.
- [ ] Reproduce class hierarchy.
- [ ] Reuse wrapped classes (serialize the model alongside the wrapper).
- [ ] Deal with incompatible types: function pointers and others.
- [ ] Print a report on unsupported services.
- [ ] Support parsing GNUstep classes.
- [ ] Generate valid Nit code with Apple classes.
- [ ] Merge with *jwrapper*?

Pull-Request: #1647
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

12 files changed:
contrib/objcwrapper/.gitignore [new file with mode: 0644]
contrib/objcwrapper/Makefile [new file with mode: 0644]
contrib/objcwrapper/bin/.gitignore [new file with mode: 0644]
contrib/objcwrapper/gen/.gitignore [new file with mode: 0644]
contrib/objcwrapper/grammar/objc.sablecc [new file with mode: 0644]
contrib/objcwrapper/header_static/makefile [deleted file]
contrib/objcwrapper/src/header_static.nit [moved from contrib/objcwrapper/header_static/src/header_static.nit with 100% similarity]
contrib/objcwrapper/src/objc_generator.nit [new file with mode: 0644]
contrib/objcwrapper/src/objc_model.nit [new file with mode: 0644]
contrib/objcwrapper/src/objc_visitor.nit [new file with mode: 0644]
contrib/objcwrapper/src/objcwrapper.nit [new file with mode: 0644]
contrib/objcwrapper/tests/MyClass.h [new file with mode: 0644]

diff --git a/contrib/objcwrapper/.gitignore b/contrib/objcwrapper/.gitignore
new file mode 100644 (file)
index 0000000..0d0b14f
--- /dev/null
@@ -0,0 +1,5 @@
+src/objc_lexer.nit
+src/objc_parser.nit
+src/objc_test_parser.nit
+tests/MyClass.nit
+objc.ast.dot
diff --git a/contrib/objcwrapper/Makefile b/contrib/objcwrapper/Makefile
new file mode 100644 (file)
index 0000000..3f39029
--- /dev/null
@@ -0,0 +1,42 @@
+all: bin/objcwrapper
+
+../nitcc/src/nitcc:
+       make -C ../nitcc
+
+src/objc_parser.nit: ../nitcc/src/nitcc grammar/objc.sablecc
+       ../nitcc/src/nitcc grammar/objc.sablecc
+       mv *.nit src/
+       mv objc* gen/
+
+bin/objcwrapper: $(shell ../../bin/nitls -M src/objcwrapper.nit) src/objc_parser.nit
+       ../../bin/nitc -o bin/objcwrapper src/objcwrapper.nit --semi-global
+
+bin/objc_test_parser: $(shell ../../bin/nitls -M src/objc_test_parser.nit)
+       ../../bin/nitc -o bin/objc_test_parser src/objc_test_parser.nit --semi-global
+
+check: bin/objc_test_parser bin/objcwrapper
+       # Test the parser
+       bin/objc_test_parser tests/MyClass.h
+
+       # Test objcwrapper
+       bin/objcwrapper tests/MyClass.h -o tests/MyClass.nit
+       ../../bin/nitpick tests/MyClass.nit
+
+# Test on classes of libgnustep-base-dev
+check-gnustep:
+       gcc -E /usr/include/GNUstep/Foundation/NSArray.h -I /usr/include/GNUstep/ -Wno-deprecated \
+       | ../header_keeper/bin/header_keeper /usr/include/GNUstep/Foundation/NSArray.h \
+       | bin/header_static > tests/NSArray.pre.h
+       bin/objcwrapper tests/NSArray.pre.h
+       ../../bin/nitpick NSArray.nit
+
+# Test on classes of the Apple Foundation framework
+check-apple:
+       gcc -E /System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSArray.h \
+       | ../header_keeper/bin/header_keeper /System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSArray.h \
+       | bin/header_static > tests/NSArray.pre.h
+       bin/objcwrapper tests/NSArray.pre.h
+       ../../bin/nitpick NSArray.nit
+
+bin/header_static:
+       ../../bin/nitc --dir bin src/header_static.nit
diff --git a/contrib/objcwrapper/bin/.gitignore b/contrib/objcwrapper/bin/.gitignore
new file mode 100644 (file)
index 0000000..72e8ffc
--- /dev/null
@@ -0,0 +1 @@
+*
diff --git a/contrib/objcwrapper/gen/.gitignore b/contrib/objcwrapper/gen/.gitignore
new file mode 100644 (file)
index 0000000..72e8ffc
--- /dev/null
@@ -0,0 +1 @@
+*
diff --git a/contrib/objcwrapper/grammar/objc.sablecc b/contrib/objcwrapper/grammar/objc.sablecc
new file mode 100644 (file)
index 0000000..b997614
--- /dev/null
@@ -0,0 +1,331 @@
+/* Grammar for Objective-C header files */
+Grammar objc;
+
+Lexer
+    upper = 'A'..'Z';
+    lower = 'a'..'z';
+    letter = upper | lower;
+    digit = '0'..'9';
+
+    comma = ',';
+    lpar = '(';
+    rpar = ')';
+    star = '*';
+
+    string = '"' (Any - '"')* '"';
+    class = upper (letter | '_' | digit)*;
+    id = lower (letter | '_' | digit)*;
+
+    private = '_' (letter | digit)+;
+    num = digit+;
+    macro_name = '__' (letter | '_')+;
+    num_typed = num upper+;
+    hexadecimal = '0x' (letter | digit)+;
+    pointer = star+;
+
+    blank = (' ' | '\t' | '\n')+;
+    comments = ('#' (Any - '\n')*);
+
+Parser
+    Ignored blank, comments;
+
+    prog = lines*;
+
+    lines =
+        {class:} '@class' classes ';' |
+        {protocol:} protocol |
+        {attribute:} attribute |
+        {static:} static |
+        {structure:} structure |
+        {interface:} '@interface' class interface? inheritance? protocols? instance_variables? interface_block* '@end' |
+        {line:} line;
+
+    interface_block =
+        {line:} line |
+        {instance:} instance_declaration;
+
+    line =
+        {library_extern_declaration:} library_extern_declaration |
+        {typedef:} typedef |
+        {extern:} extern |
+        {enum:} 'enum' '{' params '}' attribute? ';';
+
+    typedef =
+        {variable:} 'typedef' 'struct'? declaration ';' |
+        {method:} 'typedef' type method ';' |
+        {structure:} 'typedef' structure |
+        {anonymous:} 'typedef' type anonymous ';';
+
+    instance_declaration =
+        {signature:} signature_block |
+        {property:} property_declaration;
+
+    property_declaration =
+        {property:} optional_method? '@property' property_attribute? property attribute* ';';
+
+    property_attribute =
+        {attribute:} lpar params rpar;
+
+    interface =
+        {interface:} lpar params? rpar;
+
+    library_extern_declaration =
+        {simple:} 'UIKIT_EXTERN' type pointer? 'const'? term attribute? ';' |
+        {method:} 'UIKIT_EXTERN' type method? attribute? ';';
+
+    protocol =
+        {declaration:} '@protocol' type additional_type* ';' |
+        {construction:} '@protocol' type protocols? protocol_block+ '@end';
+
+    additional_type =
+        {type:} comma type;
+
+    protocol_block =
+        {signature:} signature_block |
+        {property:} property_declaration;
+
+    signature_block =
+        {signature:} optional_method? scope signature_return_type? parameter+ attribute_list? ';';
+
+    signature_return_type =
+        {return:} lpar type pointer? function_pointer? protocols? rpar;
+
+    optional_method =
+        {required:} '@required' |
+        {optional:} '@optional';
+
+    method =
+        {call:} pointer? term lpar args rpar |
+        {declaration:} pointer? term lpar declarations rpar;
+
+    attribute_list =
+        {attr:} attribute+;
+
+    attribute =
+        {availability:} '__attribute__' lpar lpar 'availability' lpar params rpar rpar rpar |
+        {visibility:} '__attribute__' lpar lpar 'visibility' lpar term rpar rpar rpar |
+        {objc_gc:} '__attribute__' lpar lpar 'objc_gc' lpar term rpar rpar rpar |
+        {attribute_id:} '__attribute__' lpar lpar term rpar rpar |
+        {attribute_method:} '__attribute__' lpar lpar method rpar rpar;
+
+    extern =
+        {method:} 'extern' type method attribute? ';' |
+        {simple:} 'extern' type pointer? 'const'? term attribute? ';';
+
+    static =
+        {method:} 'static' 'inline' type method attribute? ';' |
+        {attr:} 'static' '__inline__' attribute+ type method attribute? ';';
+
+    scope =
+        {class:} '+' |
+        {instance:} '-';
+
+    parameter =
+        {named:} [left:]term ':' lpar parameter_type rpar attribute? [right:]term |
+        {single:} term |
+        {comma:} comma '...' |
+        {macro:} macro_name;
+
+    parameter_type =
+        {normal:} type |
+        {anonymous:} type anonymous |
+        {table:} type protocols? '[]' |
+        {pointer:} type pointer |
+        {protocol:} type protocols |
+        {unsigned:} unsigned pointer |
+        {function_pointer:} function_pointer;
+
+    anonymous =
+        {method:} lpar '^' term? rpar lpar declarations? rpar |
+        {inception:} lpar '^' term? rpar lpar type lpar '^' term? rpar lpar type rpar rpar;
+
+    property =
+        {property:} type protocols? pointer? [left:]term [right:]term? |
+        {anonymous:} type anonymous |
+        {function_pointer:} function_pointer;
+
+    type =
+        {type:} type_annotation? unsigned? data_type;
+
+    type_annotation =
+        {const:} 'const' |
+        {in:} 'in' |
+        {out:} 'out' |
+        {inout:} 'inout' |
+        {bycopy:} 'bycopy' |
+        {byref:} 'byref';
+
+    unsigned =
+        {u:} 'unsigned' |
+        {s:} 'signed';
+
+    data_type =
+        {more:} more_type |
+        {otype:} class;
+
+    more_type =
+        'uuid_t' |
+        'id' |
+        '_Bool' |
+        'pthread_mutex_t' |
+        'dispatch_semaphore_t' |
+        'dispatch_queue_t' |
+        'va_list' |
+        'SecIdentityRef' |
+        'SecTrustRef' |
+        'Class' |
+        'Protocol' |
+        'void' |
+        'uint8_t' |
+        'uint16_t' |
+        'uint32_t' |
+        'uint64_t' |
+        'int8_t' |
+        'int16_t' |
+        'int32_t' |
+        'int64_t' |
+        'unichar' |
+        'char' |
+        'short' |
+        'short int' |
+        'int' |
+        'long' |
+        'long int' |
+        'long long' |
+        'long long int' |
+        'float' |
+        'double' |
+        'long double';
+
+    classe =
+        {class:} class |
+        {protocol:} 'Protocol';
+
+    classes =
+        {classes:} classe additional*;
+
+    protocols =
+        {protocols:} '<' classe additional* '>';
+
+    inheritance =
+        {add:} ':' classe additional*;
+
+    additional =
+        {add:} comma classe;
+
+    declarations =
+        {declarations:} declaration additional_declaration*;
+
+    additional_declaration =
+        {add:} comma declaration;
+
+    declaration =
+        {pointer:} type protocols? attribute? pointer? term? '[]'? |
+        {typedef:} type more_type |
+        {function_pointer:} function_pointer |
+        {comma:} '...';
+
+    declaration_variable_instance =
+        {unsigned:} type_annotation? unsigned more_type? protocols? attribute? pointer? term '[]'? |
+        {data_type:} type_annotation? data_type protocols? attribute? pointer? term? '[]'?;
+
+    instance_variables =
+        {instance:} '{' instance_variable* '}';
+
+    scope_instance_variable =
+        {package:} '@package' |
+        {public:} '@public' |
+        {private:} '@private' |
+        {protected:} '@protected';
+
+    instance_variable =
+        {scope:} scope_instance_variable |
+        {variable:} attribute? declaration_variable_instance bit_field? additional_variable* ';' |
+        {anonymous:} declaration_variable_instance anonymous ';' |
+        {structure:} structure |
+        {union:} union;
+
+    additional_variable =
+        {add} comma term bit_field?;
+
+    function_pointer =
+        {type:} type pointer? lpar pointer term? rpar lpar declarations rpar;
+
+    union =
+        {name:} 'union' '{' structure_params+ '}' term ';';
+
+    structure =
+        {name:} 'struct' structure_core term ';' |
+        {type:} 'struct' type structure_core term? ';' |
+        {private:} 'struct' private structure_core? term? ';';
+
+    structure_core =
+        {core:} '{' structure_params+ '}';
+
+    structure_params =
+        {value:} declaration bit_field? ';' |
+        {structure:} structure;
+
+    params =
+        {params:} param additional_params*;
+
+    additional_params =
+        {add:} comma param?;
+
+    param =
+        {id:} term |
+        {bitwise:} term '=' bitwise |
+        {term:} term '=' pointer? term+ |
+        {attr:} term attribute |
+        {attrterm:} term attribute '=' term |
+        {attrbitwise:} term attribute '=' bitwise;
+
+    args =
+        {args:} arg additional_arg*;
+
+    additional_arg =
+        {add:} comma arg;
+
+    arg =
+        {num:} num |
+        {macro:} macro_name;
+
+    bitwise =
+        {rterm:} bitwise_operator_not? term bitwise_operator bitwise_operator_not? term |
+        {num:} lpar bitwise_operator_not? term rpar |
+        {term:} lpar bitwise_operator_not? term additional_bitwise+ rpar arithmetic?;
+
+    bitwise_operator =
+        {left_shift:} '<<' |
+        {right_shift:} '>>' |
+        {and:} '&' |
+        {or:} '|' |
+        {xor:} '^';
+
+    bitwise_operator_not =
+        {not:} '~';
+
+    additional_bitwise =
+        {add:} bitwise_operator bitwise_operator_not? term;
+
+    bit_field =
+        {value:} ':' num;
+
+    arithmetic =
+        {add:} '+' num |
+        {minus:} '-' num |
+        {star:} pointer num |
+        {divide:} '/' num;
+
+    term =
+        {num_typed:} '-'? num_typed |
+        {private:} private |
+        {num:} num |
+        {negative:} '-' num |
+        {float:} num '.' num |
+        {string:} string |
+        {hexadecimal:} hexadecimal |
+        {class:} class |
+        {var:} id |
+        {private_table:} private '[' lpar? num rpar? ']' |
+        {table:} id '[' lpar? num rpar? ']';
diff --git a/contrib/objcwrapper/header_static/makefile b/contrib/objcwrapper/header_static/makefile
deleted file mode 100644 (file)
index e4d3e19..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-bin/header_static:
-       mkdir -p bin
-       ../../../bin/nitc --dir bin src/header_static.nit
-
-tests: bin/header_static
-       cat CGGeometry.h | bin/header_static > static_CGGeometry.h
-       cat NSObject.h | bin/header_static > static_NSObject.h
diff --git a/contrib/objcwrapper/src/objc_generator.nit b/contrib/objcwrapper/src/objc_generator.nit
new file mode 100644 (file)
index 0000000..25bd84f
--- /dev/null
@@ -0,0 +1,233 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Code generation
+module objc_generator
+
+import opts
+
+import objc_model
+
+redef class Sys
+       # Path to the output file
+       var opt_output = new OptionString("Output file", "-o")
+end
+
+class CodeGenerator
+       # Merge the calls to `alloc` and `init...` in a single constructor?
+       #
+       # If `true`, also the default behavior, initializing an extern Objective-C object looks like:
+       # ~~~nitish
+       # var o = new NSArray.init_with_array(some_other_array)
+       # ~~~
+       #
+       # If `false`, the object must first be allocated and then initialized.
+       # This is closer to the Objective-C behavior:
+       # ~~~nitish
+       # var o = new NSArray
+       # o.init_with_array(some_other_array)
+       # ~~~
+       var init_with_alloc = true is writable
+
+       # Generate Nit code to wrap `classes`
+       fun generate(classes: Array[ObjcClass])
+       do
+               # Open specified path or stdin
+               var file
+               var path = opt_output.value
+               if path != null then
+                       if path.file_extension != "nit" then
+                               print_error "Warning: output file path does not end with '.nit'"
+                       end
+
+                       file = new FileWriter.open(path)
+               else
+                       file = stdout
+               end
+
+               # Generate code
+               file.write "import cocoa::foundation\n\n"
+               for classe in classes do
+                       write_class(classe, file)
+               end
+
+               if path != null then file.close
+       end
+
+       private fun type_convertor(type_word: String): String
+       do
+               var types = new HashMap[String, String]
+               types["char"] = "Byte"
+               types["short"] = "Int"
+               types["short int"] = "Int"
+               types["int"] = "Int"
+               types["long"] = "Int"
+               types["long int"] = "Int"
+               types["long long"] = "Int"
+               types["long long int"] = "Int"
+               types["float"] = "Float"
+               types["double"] = "Float"
+               types["long double"] = "Float"
+
+               types["NSUInteger"] = "Int"
+               types["BOOL"] = "Bool"
+               types["id"] = "NSObject"
+
+               if types.has_key(type_word) then
+                       return types[type_word]
+               else
+                       return type_word
+               end
+       end
+
+       private fun write_class(classe: ObjcClass, file: Writer)
+       do
+               var commented_methods = new Array[ObjcMethod]
+               file.write "extern class " + classe.name + """ in "ObjC" `{ """ + classe.name  + """ * `}\n"""
+               for super_name in classe.super_names do
+                       file.write """  super """ + super_name + "\n"
+               end
+               if classe.super_names.is_empty then file.write """      super NSObject\n"""
+               write_constructor(classe, file)
+               file.write "\n"
+               for attribute in classe.attributes do
+                       write_attribute(attribute, file)
+               end
+               for method in classe.methods do
+                       if method.is_commented then
+                               commented_methods.add(method)
+                       else
+                               if init_with_alloc and method.params.first.name.has("init") then continue
+                               file.write """  """
+                               write_method(method, file)
+                               file.write """ in "ObjC" `{\n           """
+                               write_objc_method_call(method, file)
+                               file.write """  `}"""
+                               if method != classe.methods.last then file.write "\n\n"
+                       end
+               end
+               for commented_method in commented_methods do
+                       if commented_method == commented_methods.first then file.write "\n"
+                       file.write """  #"""
+                       write_method(commented_method, file)
+                       if commented_method != commented_methods.last then file.write "\n"
+               end
+               file.write "\nend\n"
+       end
+
+       private fun write_constructor(classe: ObjcClass, file: Writer)
+       do
+               if init_with_alloc then
+                       for method in classe.methods do
+                               if method.params.first.name.has("init") and not method.is_commented then
+                                       file.write """\n        """
+                                       if method.params.first.name == "init" then
+                                               file.write "new"
+                                       else
+                                               write_method(method, file)
+                                       end
+                                       file.write """ in "ObjC" `{\n"""
+                                       write_objc_init_call(classe.name, method, file)
+                                       file.write """  `}\n"""
+                               end
+                       end
+               else
+                       file.write """\n        new in "ObjC"`{\n"""
+                       file.write """          return [""" + classe.name + " alloc];\n"
+                       file.write """  `}\n"""
+               end
+       end
+
+       private fun write_attribute(attribute: ObjcAttribute, file: Writer)
+       do
+               write_attribute_getter(attribute, file)
+               # TODO write_attribute_setter if there is no `readonly` annotation
+               file.write "\n"
+       end
+
+       private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
+       do
+               file.write """  fun """ + attribute.name.to_snake_case + ": " + type_convertor(attribute.return_type)
+               file.write """ in "ObjC" `{\n"""
+               file.write """          return [self """ + attribute.name + "];\n"
+               file.write """  `}\n"""
+       end
+
+       private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
+       do
+               file.write """  fun """ + attribute.name.to_snake_case + "=(value: " + type_convertor(attribute.return_type) + ")"
+               file.write " in \"ObjC\" `\{\n"
+               file.write """          self.""" + attribute.name + " = value;\n"
+               file.write """  `}\n"""
+       end
+
+       private fun write_method(method: ObjcMethod, file: Writer)
+       do
+               var name = ""
+               for param in method.params do
+                       name += param.name[0].to_upper.to_s + param.name.substring_from(1)
+                       name = name.to_snake_case
+               end
+               if name.has("init") and init_with_alloc then
+                       file.write "new "
+               else
+                       if not init_with_alloc and name == "init" then name = "init_0"
+                       file.write "fun "
+               end
+               file.write name
+               for param in method.params do
+                       if param == method.params.first and not param.is_single then
+                               file.write "(" + param.variable_name + ": " + type_convertor(param.return_type)
+                       end
+                       if param != method.params.first and not param.is_single then
+                               file.write ", " + param.variable_name + ": " + type_convertor(param.return_type)
+                       end
+                       if param == method.params.last and not param.is_single then
+                               file.write ")"
+                       end
+               end
+               if method.return_type != "void" and not method.params.first.name.has("init") then
+                       file.write ": " + type_convertor(method.return_type)
+               end
+       end
+
+       private fun write_objc_init_call(classe_name: String, method: ObjcMethod, file: Writer)
+       do
+               file.write """          return [[""" + classe_name + " alloc] "
+               for param in method.params do
+                       if not param.is_single then
+                               file.write param.name + ":" + param.variable_name
+                               if not param == method.params.last then file.write " "
+                       else
+                               file.write param.name
+                       end
+               end
+               file.write "];\n"
+       end
+
+       private fun write_objc_method_call(method: ObjcMethod, file: Writer)
+       do
+               if method.return_type != "void" then file.write "return "
+               file.write "[self "
+               for param in method.params do
+                       if not param.is_single then
+                               file.write param.name + ":" + param.variable_name
+                               if not param == method.params.last then file.write " "
+                       else
+                               file.write param.name
+                       end
+               end
+               file.write "];\n"
+       end
+end
diff --git a/contrib/objcwrapper/src/objc_model.nit b/contrib/objcwrapper/src/objc_model.nit
new file mode 100644 (file)
index 0000000..b2465c5
--- /dev/null
@@ -0,0 +1,89 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Model of the parsed Objective-C files
+module objc_model
+
+# Model of all the analyzed Objective-C classes
+class ObjcModel
+       # All analyzed classes
+       var classes = new Array[ObjcClass]
+end
+
+# Objective-C class
+class ObjcClass
+       # Name of the super classes
+       var super_names = new Array[String]
+
+       # Name of this Objective-C class
+       var name: String
+
+       # Attributes of this Objective-C class
+       var attributes = new Array[ObjcAttribute]
+
+       # Methods of this Objective-C class
+       var methods = new Array[ObjcMethod]
+end
+
+# Method of an `ObjcClass`
+class ObjcMethod
+       super Property
+
+       # Scope: '+' for a static class method, and '-' for an instance method
+       var scope: Char is noinit, writable
+
+       # Parameters of the method
+       var params = new Array[Param]
+
+       # Return type as a `String`
+       var return_type: String is noinit, writable
+end
+
+# Attribute of an `ObjcClass`
+class ObjcAttribute
+       super Property
+
+       # Name of this attribute
+       var name: String is noinit, writable
+
+       # Type of this attribute
+       var return_type: String is noinit, writable
+end
+
+# Property of an `ObjcClass`
+class Property
+       # Is this property to be commented out?
+       var is_commented = false is writable
+end
+
+# Parameter of an `ObjcMethod`
+class Param
+       # Parameter name, used by the caller (e.g. `withObject` in `withObject: (NSObject*) obj`)
+       var name: String is noinit, writable
+
+       # Type of the parameter name
+       var return_type: String is noinit, writable
+
+       # Argument name, used within the body (e.g. `obj` in `withObject: (NSObject*) obj`)
+       var variable_name: String is noinit, writable
+
+       # Is this a primitive array? with at least one `[]`.
+       var is_table = false is writable
+
+       # Is this a pointer type?
+       var is_pointer = false is writable
+
+       # Is this a parameter with only a `name`?
+       var is_single = false is writable
+end
diff --git a/contrib/objcwrapper/src/objc_visitor.nit b/contrib/objcwrapper/src/objc_visitor.nit
new file mode 100644 (file)
index 0000000..3d51935
--- /dev/null
@@ -0,0 +1,204 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# AST visitor
+module objc_visitor
+
+import objc_model
+import objc_parser
+
+# AST visitor building `model` from the parsed class headers
+class ObjcVisitor
+       super Visitor
+
+       # `ObjcModel` in construction
+       var model = new ObjcModel
+
+       # `ObjcClass` in construction, if any
+       private var objc_class: nullable ObjcClass = null
+
+       redef fun visit(n) do n.accept_objc(self)
+end
+
+redef class Node
+       private fun accept_objc(v: ObjcVisitor) do visit_children(v)
+end
+
+# ---
+# Main nodes
+
+# Class declaration
+redef class Nlines_interface
+       redef fun accept_objc(v)
+       do
+               var interface_block = n_interface_block
+               if interface_block == null then return
+
+               # If reopening a class, continue with the exisitng one
+               var c = null
+               for objc_class in v.model.classes do
+                       if objc_class.name == n_class.text then
+                               c = objc_class
+                       end
+               end
+
+               # New class
+               if c == null then
+                       c = new ObjcClass(n_class.text)
+                       v.model.classes.add c
+               end
+               v.objc_class = c
+
+               # Visit superclass declarations
+               var inheritance_block = n_inheritance
+               if inheritance_block != null then v.enter_visit(inheritance_block)
+
+               # Visit main body
+               v.enter_visit(interface_block)
+       end
+end
+
+# Method or function declaration
+redef class Nsignature_block_signature
+       redef fun accept_objc(v)
+       do
+               var method = new ObjcMethod
+               method.return_type = n_signature_return_type.to_type
+               method.scope = if n_scope.is_class_property then '-' else '+'
+
+               for n_param in n_parameter.children do
+                       var param = n_param.to_param
+                       if param == null then
+
+                               # Unsupported parameter format
+                               method.is_commented = true
+
+                               # Use a placeholder for easier debugging
+                               param = new Param
+                               param.name = "UNKNOWN"
+                               param.return_type = "UNKNOWN"
+                               param.variable_name = "UNKNOWN"
+                       end
+
+                       method.params.add param
+               end
+
+               v.objc_class.methods.add method
+       end
+end
+
+# Class variable/attribute declaration (inside alternative node)
+redef class Nproperty_property
+       redef fun accept_objc(v)
+       do
+               var attr = new ObjcAttribute
+               attr.return_type = n_type.to_type
+               attr.name = n_left.collect_text
+
+               v.objc_class.attributes.add attr
+       end
+end
+
+# Class variable/attribute declaration (outside with @property)
+redef class Nproperty_declaration_property
+       redef fun accept_objc(v)
+       do
+               # TODO property attribute readonly, copy, etc.
+               super
+       end
+end
+
+# ---
+# Support nodes
+
+redef class NProd
+       # Append all tokens under this node in a `String`
+       private fun collect_text: String
+       do
+               var buf = new FlatBuffer
+               for node in depth do if node isa NToken then buf.append node.text
+               return buf.to_s
+       end
+end
+
+redef class Nlines
+       # Do not visit other lines, they are only to be eaten
+       redef fun accept_objc(v) do end
+end
+
+redef class Nscope
+       # Does this mark a class property (+)? Otherwise it's an instance property (-).
+       private fun is_class_property: Bool do return false
+end
+
+redef class Nscope_class
+       redef fun is_class_property do return true
+end
+
+redef class Nsignature_return_type
+       # Get type from this node TODO return an ObjcType
+       private fun to_type: String do return collect_text
+end
+
+redef class Nsignature_return_type_return
+       redef fun to_type do return n_type.to_type
+end
+
+redef class Nparameter
+       # Return null if type is not yet unsupported
+       private fun to_param: nullable Param do return null
+end
+
+# Parameters with both a public and an internal name
+redef class Nparameter_named
+       redef fun to_param
+       do
+               var param = new Param
+               param.variable_name = n_right.collect_text
+               param.name = n_left.collect_text
+               param.return_type = n_parameter_type.to_type
+               return param
+       end
+end
+
+# Usually the name of a method without parameters
+redef class Nparameter_single
+       redef fun to_param
+       do
+               var param = new Param
+               param.name = n_term.collect_text
+               param.is_single = true
+               return param
+       end
+end
+
+redef class Nparameter_type
+       # Get type from this node TODO return an ObjcType
+       private fun to_type: String
+       do
+               # FIXME taking the first token skips pointers
+               for child in children do
+                       if child isa Ntype then
+                               return child.to_type
+                       end
+               end
+
+               return collect_text
+       end
+end
+
+redef class Ntype
+       # Get type from this node TODO return an ObjcType
+       private fun to_type: String do return collect_text
+end
diff --git a/contrib/objcwrapper/src/objcwrapper.nit b/contrib/objcwrapper/src/objcwrapper.nit
new file mode 100644 (file)
index 0000000..b1abbc6
--- /dev/null
@@ -0,0 +1,67 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generator of Nit modules to wrap Objective-C services
+module objcwrapper
+
+import nitcc_runtime
+import opts
+
+import objc_visitor
+import objc_model
+import objc_generator
+import objc_lexer
+import objc_parser
+
+var opt_help = new OptionBool("Show this help message", "-h", "--help")
+
+var opts = new OptionContext
+opts.add_option(opt_help, opt_output)
+opts.parse(args)
+
+if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
+       print """
+Usage: objcwrapper [options] input_file [other_input_file [...]]
+Options:"""
+       opts.usage
+
+       if opt_help.value then exit 0
+       exit 1
+end
+
+var v = new ObjcVisitor
+var g = new CodeGenerator
+
+for arg in opts.rest do
+       # Read input
+       var content = arg.to_path.read_all
+
+       # Parse
+       var lexer = new Lexer_objc(content)
+       var parser = new Parser_objc
+       var tokens = lexer.lex
+       parser.tokens.add_all(tokens)
+       var root = parser.parse
+
+       # Check for errors
+       if root isa NError then
+               print_error "Syntax Error: {root.message}: {root.position or else ""}"
+               continue
+       end
+
+       # Run analysis
+       v.enter_visit root
+end
+
+g.generate v.model.classes
diff --git a/contrib/objcwrapper/tests/MyClass.h b/contrib/objcwrapper/tests/MyClass.h
new file mode 100644 (file)
index 0000000..ccacb22
--- /dev/null
@@ -0,0 +1,12 @@
+@interface MyClass : NSObject
+  <NSCoding, NSCopying, NSMutableCopying, NSFastEnumeration>
+
++ (id) array;
++ (id) arrayWithContentsOfFile: (NSString*)file;
+
+- (id) arrayWithArray: (MyClass*)array;
+
+@property (readonly) NSUInteger count;
+@property NSUInteger head;
+
+@end