X-Git-Url: http://nitlanguage.org diff --git a/contrib/objcwrapper/src/objc_generator.nit b/contrib/objcwrapper/src/objc_generator.nit index 3ad10b8..d50795b 100644 --- a/contrib/objcwrapper/src/objc_generator.nit +++ b/contrib/objcwrapper/src/objc_generator.nit @@ -15,19 +15,34 @@ # Code generation module objc_generator +import opts + import objc_model -class CodeGenerator - fun generator(classes: Array[nullable ObjcClass]) do - var init_with_alloc = true - for classe in classes do - var file = new FileWriter.open(classe.name + ".nit") - nit_class_generator(classe, file, init_with_alloc) - file.close - end - end +redef class Sys + + # Path to the output file + var opt_output = new OptionString("Output file", "-o") - fun type_convertor(type_word: String): String do + # Shall `init` methods/constructors be wrapped as methods? + # + # By default, these methods/constructors are wrapped as extern constructors. + # So initializing an extern Objective-C object looks like: + # ~~~nitish + # var o = new NSArray.init_with_array(some_other_array) + # ~~~ + # + # If this option is set, 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 opt_init_as_methods = new OptionBool( + "Wrap `init...` constructors as Nit methods instead of Nit constructors", + "--init-as-methods") + + private var objc_to_nit_types: Map[String, String] is lazy do var types = new HashMap[String, String] types["char"] = "Byte" types["short"] = "Int" @@ -44,153 +59,275 @@ class CodeGenerator types["NSUInteger"] = "Int" types["BOOL"] = "Bool" types["id"] = "NSObject" + types["constid"] = "NSObject" + types["SEL"] = "NSObject" + types["void"] = "Pointer" + + return types + end +end + +redef class ObjcModel + redef fun knows_type(objc_type) do return super or + objc_to_nit_types.keys.has(objc_type) +end + +# Wrapper generator +class CodeGenerator - if types.has_key(type_word) then - return types[type_word] + # `ObjcModel` to wrap + var model: ObjcModel + + # Generate Nit code to wrap `classes` + fun generate + do + var classes = model.classes + + # 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 - return type_word + file = stdout end - end - fun nit_class_generator(classe: nullable ObjcClass, file: FileWriter, init_with_alloc: Bool) do - var commented_methods = new Array[ObjcMethod] - file.write "import cocoa::foundation\n\n" - 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") + # Generate code + file.write "import cocoa::foundation\n" + for classe in classes do + write_class(classe, file) end - if classe.super_names.is_empty then file.write(""" super NSObject\n""") - new_nit_generator(classe, file, init_with_alloc) - file.write("\n") + + if path != null then file.close + end + + private fun write_class(classe: ObjcClass, file: Writer) + do + # Class header + file.write """ + +extern class {{{classe.name}}} in "ObjC" `{ {{{classe.name}}} * `} +""" + + # Supers + for super_name in classe.super_names do file.write """ + super {{{super_name}}} +""" + if classe.super_names.is_empty then file.write """ + super NSObject +""" + + # Constructor or constructors + write_constructors(classe, file) + + # Attributes for attribute in classe.attributes do - nit_attribute_generator(attribute, file) + write_attribute(attribute, file) end + + # Instance methods '-' 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(""" """) - nit_method_generator(method, file, init_with_alloc) - file.write(""" in "ObjC" `{\n """) - objc_method_generator(method, file) - file.write(""" `}""") - if method != classe.methods.last then file.write("\n\n") - end + if not model.knows_all_types(method) then method.is_commented = true + + if not opt_init_as_methods.value and method.is_init then continue + if method.is_class_property then continue + + write_method_signature(method, file) + write_objc_method_call(method, file) end - for commented_method in commented_methods do - if commented_method == commented_methods.first then file.write("\n") - file.write(""" #""") - nit_method_generator(commented_method, file, init_with_alloc) - if commented_method != commented_methods.last then file.write("\n") + + file.write """ +end +""" + + # Class methods '+' + for method in classe.methods do + if not method.is_class_property then continue + + write_method_signature(method, file) + write_objc_method_call(method, file) end - file.write("\nend") end - fun new_nit_generator(classe: nullable ObjcClass, file: FileWriter, init_with_alloc: Bool) 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 - nit_method_generator(method, file, init_with_alloc) - end - file.write """ in "ObjC" `{\n""" - new_alloc_init_objc_generator(classe.name, method, file) - file.write """ `}\n""" - end - end - else - file.write """\n new in "ObjC"`{\n""" - new_objc_generator(classe, file) - file.write """ `}\n""" + private fun write_constructors(classe: ObjcClass, file: Writer) + do + if opt_init_as_methods.value then + # A single constructor for `alloc` + file.write """ + + new in "ObjC" `{ + return [{{{classe.name}}} alloc]; + `} +""" + return + end + + # A constructor per `init...` method + for method in classe.methods do + if not method.is_init then continue + + if not model.knows_all_types(method) then method.is_commented = true + + write_method_signature(method, file) + + write_objc_init_call(classe.name, method, file) end end - fun nit_attribute_generator(attribute: ObjcAttribute, file: FileWriter) do - nit_attribute_setter_generator(attribute, file) - file.write "\n" + private fun write_attribute(attribute: ObjcAttribute, file: Writer) + do + if not model.knows_type(attribute.return_type) then attribute.is_commented = true + + write_attribute_getter(attribute, file) + # TODO write_attribute_setter if there is no `readonly` annotation end - fun nit_attribute_setter_generator(attribute: ObjcAttribute, file: FileWriter) do - file.write(""" fun """ + attribute.name.to_snake_case + ": " + type_convertor(attribute.return_type)) - file.write """ in "ObjC" `{\n""" - objc_attribute_setter_generator(attribute, file) - file.write """ `}\n""" + private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer) + do + var nit_attr_name = attribute.name.to_snake_case + var nit_attr_type = attribute.return_type.objc_to_nit_type + + var c = attribute.comment_str + + file.write """ + +{{{c}}} fun {{{nit_attr_name}}}: {{{nit_attr_type}}} in "ObjC" `{ +{{{c}}} return [self {{{attribute.name}}}]; +{{{c}}} `} +""" end - fun nit_attribute_getter_generator(attribute: ObjcAttribute, file: FileWriter) do - file.write(""" fun """ + attribute.name.to_snake_case + "=(value: " + type_convertor(attribute.return_type) + ")") - file.write " in \"ObjC\" `\{\n" - objc_attribute_getter_generator(attribute, file) - file.write """ `}\n""" + private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer) + do + var nit_attr_name = attribute.name.to_snake_case + var nit_attr_type = attribute.return_type.objc_to_nit_type + + var c = attribute.comment_str + + file.write """ + +{{{c}}} fun {{{nit_attr_name}}}=(value: {{{nit_attr_type}}}) in "ObjC" `{ +{{{c}}} return self.{{{attribute.name}}} = value; +{{{c}}} `} +""" end - fun nit_method_generator(method: ObjcMethod, file: FileWriter, init_with_alloc: Bool) do + private fun write_method_signature(method: ObjcMethod, file: Writer) + do + var c = method.comment_str + + # Build Nit method name 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 " + name = name.to_snake_case + + if name == "init" then name = "" + + # If class method, prefix with class name + if method.is_class_property then name = "{method.objc_class.name.to_snake_case}_{name}" + + # Kind of method + var fun_keyword = "fun" + if not opt_init_as_methods.value and method.is_init then + fun_keyword = "new" end - file.write(name) + + # Params + var params = new Array[String] 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 + if param.is_single then break + params.add "{param.variable_name}: {param.return_type.objc_to_nit_type}" + end + + var params_with_par = "" + if params.not_empty then params_with_par = "({params.join(", ")})" + + # Return + var ret = "" + if method.return_type != "void" and fun_keyword != "new" then + ret = ": {method.return_type.objc_to_nit_type}" end - if method.return_type != "void" and not method.params.first.name.has("init") then file.write(": " + type_convertor(method.return_type)) + + file.write """ + +{{{c}}}{{{fun_keyword}}} {{{name}}}{{{params_with_par}}}{{{ret}}} in "ObjC" `{ +""" end - fun new_alloc_init_objc_generator(classe_name: String, method: ObjcMethod, file: FileWriter) do - file.write(""" return [[""" + classe_name + " alloc] ") + # Write a combined call to alloc and to a constructor/method + private fun write_objc_init_call(class_name: String, method: ObjcMethod, file: Writer) + do + # Method name and other params + var params = new Array[String] 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 + params.add "{param.name}: {param.variable_name}" + else params.add param.name end - file.write "];\n" - end - fun new_objc_generator(classe: nullable ObjcClass, file: FileWriter) do - file.write(""" return [""" + classe.name + " alloc];\n") - end + var c = method.comment_str - fun objc_attribute_setter_generator(attribute: ObjcAttribute, file: FileWriter) do - file.write(""" return [self """ + attribute.name + "];\n") + file.write """ +{{{c}}} return [[{{{class_name}}} alloc] {{{params.join(" ")}}}]; +{{{c}}}`} +""" end - fun objc_attribute_getter_generator(attribute: ObjcAttribute, file: FileWriter) do - file.write(""" self.""" + attribute.name + " = value;\n") - end + private fun write_objc_method_call(method: ObjcMethod, file: Writer) + do + # Is there a value to return? + var ret = "" + if method.return_type != "void" then ret = "return " - fun objc_method_generator(method: ObjcMethod, file: FileWriter) do - if method.return_type != "void" then file.write("return ") - file.write "[self " + # Method name and other params + var params = new Array[String] 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 + params.add "{param.name}: {param.variable_name}" + else params.add param.name end - file.write "];\n" + + # Receiver, instance or class + var recv = "self" + if method.is_class_property then recv = method.objc_class.name + + var c = method.comment_str + + file.write """ +{{{c}}} {{{ret}}}[{{{recv}}} {{{params.join(" ")}}}]; +{{{c}}}`} +""" end end + +redef class Text + # Nit equivalent to this type + private fun objc_to_nit_type: String + do + var types = sys.objc_to_nit_types + + if types.has_key(self) then + return types[self] + else + return to_s + end + end +end + +redef class ObjcProperty + private fun comment_str: String do if is_commented then + return "#" + else return "" +end + +redef class ObjcMethod + private fun indent: String do return if is_class_property then "" else "\t" + + redef fun comment_str do return indent + super +end