# 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 nit_to_java_types: Map[String, String] is lazy do
var types = new HashMap[String, String]
types["char"] = "Byte"
types["short"] = "Int"
types["NSUInteger"] = "Int"
types["BOOL"] = "Bool"
types["id"] = "NSObject"
+ types["constid"] = "NSObject"
+ types["SEL"] = "NSObject"
+ types["void"] = "Pointer"
- if types.has_key(type_word) then
- return types[type_word]
+ return types
+ end
+end
+
+redef class ObjcModel
+ redef fun knows_type(objc_type) do return super or
+ nit_to_java_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
+
+ # 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]
+ # Generate code
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")
+ 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
+"""
+
+ file.write "\n"
+
+ # Constructor or constructors
+ write_constructors(classe, file)
+
+ # Attributes
for attribute in classe.attributes do
- nit_attribute_generator(attribute, file)
+ write_attribute(attribute, file)
end
+
+ # 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
- 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")
+ 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
+
+ write_method_signature(method, file)
+ write_objc_method_call(method, file)
end
- file.write "\nend\n"
+
+ file.write """
+end
+"""
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.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.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 = ""
+
+ # 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.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))
+
+ 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.to_nit_type}"
+ end
+
+ 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")
- end
+ file.write """
+{{{c}}} return [[{{{class_name}}} alloc] {{{params.join(" ")}}}];
+{{{c}}} `}
- fun objc_attribute_getter_generator(attribute: ObjcAttribute, file: FileWriter) do
- file.write(""" self.""" + attribute.name + " = value;\n")
+"""
end
- fun objc_method_generator(method: ObjcMethod, file: FileWriter) do
- if method.return_type != "void" then file.write("return ")
- file.write "[self "
+ 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 "
+
+ # 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
+
+ var c = method.comment_str
+
+ file.write """
+{{{c}}} {{{ret}}}[self {{{params.join(" ")}}}];
+{{{c}}} `}
+
+"""
+ end
+end
+
+redef class Text
+ # Nit equivalent to this type
+ private fun to_nit_type: String
+ do
+ var types = sys.nit_to_java_types
+
+ if types.has_key(self) then
+ return types[self]
+ else
+ return to_s
end
- file.write "];\n"
end
end
+
+redef class Property
+ private fun comment_str: String do if is_commented then
+ return "#"
+ else return ""
+end