module objc_generator
import opts
+import gen_nit
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?
+ # Shall `init` methods/constructors be wrapped as methods?
#
- # If `true`, also the default behavior, initializing an extern Objective-C object looks like:
+ # 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 `false`, the object must first be allocated and then initialized.
+ # 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 init_with_alloc = true is writable
+ 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"
+ 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["NSInteger"] = "Int"
+ types["CGFloat"] = "Float"
+ 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
+
+ # `ObjcModel` to wrap
+ var model: ObjcModel
+
+ # Generate Nit code to wrap `classes`
+ fun generate
+ do
+ var classes = model.classes
- fun generator(classes: Array[nullable ObjcClass]) do
# Open specified path or stdin
var file
var path = opt_output.value
end
# Generate code
+ file.write """
+# File generated by objcwrapper with the following command:
+# {{{program_name}}} {{{args.join(" ")}}}
+
+"""
+
+ file.write "import cocoa::foundation\n"
for classe in classes do
- nit_class_generator(classe, file, init_with_alloc)
+ write_class(classe, file)
end
if path != null then file.close
end
- 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"
+ private fun write_class(classe: ObjcClass, file: Writer)
+ do
+ # FIXME remove the redef when the base lib is generated by objcwrapper
+ var r = ""
+ if classe.name == "NSObject" then r = "redef "
- types["NSUInteger"] = "Int"
- types["BOOL"] = "Bool"
- types["id"] = "NSObject"
+ # Class header
+ file.write """
- if types.has_key(type_word) then
- return types[type_word]
- else
- return type_word
- end
- end
+{{{r}}}extern class {{{classe.name}}} in "ObjC" `{ {{{classe.name}}} * `}
+"""
- 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")
- end
- if classe.super_names.is_empty then file.write(""" super NSObject\n""")
- new_nit_generator(classe, file, init_with_alloc)
- file.write("\n")
+ # Supers
+ for super_name in classe.super_names do file.write """
+ super {{{super_name}}}
+"""
+ if classe.super_names.is_empty and classe.name != "NSObject" 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\n"
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 """
+
+{{{attribute.doc}}}
+{{{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 """
+
+{{{attribute.doc}}}
+{{{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 = ""
+
+ name = name.to_nit_name(property=true, pointer=true)
+
+ # 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.nit_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 """
+
+{{{method.doc}}}
+{{{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.nit_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.nit_variable_name}"
+ else params.add param.name
+ end
+
+ # 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
- file.write "];\n"
end
+
+ # Convert to a safe Nit name for a `property`, a property in a subclass of `pointer` or a variable
+ private fun to_nit_name(property, pointer: nullable Bool): String
+ do
+ var name = to_s
+ name = name.to_snake_case
+
+ while not name.is_empty and name.chars.first == '_' do name = name.substring_from(1)
+
+ if keywords.has(name) then name = name + "0"
+
+ if property == true then
+ if methods_in_object.has(name) then name = name + "0"
+ if pointer == true and methods_in_pointer.has(name) then name = name + "0"
+ end
+
+ return name.to_s
+ end
+end
+
+redef class ObjcProperty
+ private fun comment_str: String do if is_commented then
+ return "#"
+ else return ""
+
+ # Full documentation to be generated for the Nit code
+ private fun doc: String is abstract
+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
+
+ redef fun doc
+ do
+ var recv = if is_class_property then objc_class.name else "self"
+ return "{indent}# Wraps: `[{recv} {params.join(" ")}]`"
+ end
+end
+
+redef class ObjcAttribute
+ redef fun doc do return "\t# Wraps: `{objc_class.name}.{name}`"
+end
+
+redef class ObjcParam
+ # `variable_name` mangled for the Nit language
+ private fun nit_variable_name: String do return variable_name.to_nit_name
end