contrib/jwrapper: Added code generation module with code conversion maps
authorFrédéric Vachon <fredvac@gmail.com>
Sun, 13 Jul 2014 21:11:46 +0000 (17:11 -0400)
committerFrédéric Vachon <fredvac@gmail.com>
Mon, 21 Jul 2014 16:05:06 +0000 (12:05 -0400)
Signed-off-by: Frédéric Vachon <fredvac@gmail.com>

contrib/jwrapper/src/code_generator.nit [new file with mode: 0644]
contrib/jwrapper/src/jtype_converter.nit [new file with mode: 0644]

diff --git a/contrib/jwrapper/src/code_generator.nit b/contrib/jwrapper/src/code_generator.nit
new file mode 100644 (file)
index 0000000..9c5cbc2
--- /dev/null
@@ -0,0 +1,310 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
+#
+# 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.
+
+# Services to generate extern class `in "Java"`
+module code_generator
+
+intrude import types
+
+class CodeGenerator
+
+       var file_out: OFStream
+       fun code_warehouse: CodeWarehouse do return once new CodeWarehouse
+
+       init (file_name: String)
+       do
+               file_out = new OFStream.open(file_name)
+       end
+
+       fun gen_class_header(full_class_name: Array[String])
+       do
+               file_out.write("extern class Native{full_class_name.last} in \"Java\" `\{ {full_class_name.join(".")} `\}")
+               file_out.write("\tsuper JavaObject\n\tredef type SELF: Native{full_class_name.last}\n\n")
+       end
+
+       fun gen_variable(jid: String, jtype: JavaType)
+       do
+               file_out.write("\tvar {jid.to_snake_case}: {jtype.to_nit_type}\n")
+       end
+       
+       fun gen_method(jparam_list: Array[JavaType], jreturn_type: JavaType, jmethod_id: String)
+       do
+               var java_params = ""
+               var nit_params  = ""
+               var nit_id = "arg"
+               var nit_id_no = 0
+               var nit_types = new Array[NitType]
+               var comment = "" 
+
+               # Parameters
+               for i in [0..jparam_list.length[ do
+                       var jparam = jparam_list[i]
+                       var nit_type = jparam.to_nit_type
+                       var cast = ""
+
+                       if not jparam.is_collection then cast = jparam.param_cast
+
+                       nit_types.add(nit_type)
+                       nit_type.arg_id = "{nit_id}{nit_id_no}"
+
+                       if i == jparam_list.length - 1 then
+                               java_params += "{cast}{nit_id}{nit_id_no}"
+                               nit_params  += "{nit_id}{nit_id_no}: {nit_type}"
+                       else
+                               java_params += "{cast}{nit_id}{nit_id_no}" + ", "
+                               nit_params  += "{nit_id}{nit_id_no}: {nit_type}, "
+                       end
+
+                       nit_id_no += 1
+                       # Comment if one type is unknown
+                       if not nit_type.is_complete then comment = "#"
+               end
+
+               # Method identifier
+               var method_id = jmethod_id.to_snake_case
+               var nit_signature = new Array[String]
+
+               nit_signature.add "\tfun {method_id}"
+
+               if not jparam_list.is_empty then
+                       nit_signature.add "({nit_params})"
+               end
+
+               var return_type = null
+
+               if not jreturn_type.is_void then
+                       return_type = jreturn_type.to_nit_type
+                       if not return_type.is_complete then comment = "#"
+                       nit_signature.add ": {return_type} "
+               end
+
+               file_out.write(comment + nit_signature.join(""))
+
+               var param_to_copy = param_to_copy(jparam_list, nit_types)
+
+               # Copy one parameter, the return value, one parameter and the return value or nothing
+               if return_type != null then
+                       if return_type.is_complete and jreturn_type.is_collection then
+                               if param_to_copy != null then
+                                       var rtype_couple = new Couple[JavaType, NitType](jreturn_type, return_type)
+                                       file_out.write(code_warehouse.param_return_copy(rtype_couple, param_to_copy, jmethod_id, java_params))
+                               else
+                                       file_out.write(code_warehouse.return_type_copy(jreturn_type, return_type, jmethod_id, java_params))
+                               end
+                       else if param_to_copy != null then
+                               file_out.write(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, true))
+                       else
+                               file_out.write(" in \"Java\" `\{\n\t\t{comment}return {jreturn_type.return_cast} recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+                       end
+               else if jreturn_type.is_void then
+                       if param_to_copy != null then
+                               file_out.write(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, false))
+                       else
+                               file_out.write(" in \"Java\" `\{\n\t\t{comment}recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+                       end
+               else
+                       file_out.write(" in \"Java\" `\{\n\t\t{comment}recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+               end
+       end
+
+       # Only one collection type parameter can be copied
+       # If there's none or more than one then `null` is returned
+       fun param_to_copy(jtypes: Array[JavaType], ntypes: Array[NitType]): nullable Couple[JavaType, NitType]
+       do
+               var counter = 0
+               var couple = null
+               for i in [0..jtypes.length[ do
+                       if jtypes[i].is_collection and ntypes[i].is_complete then
+                               counter += 1
+                               if counter > 1 then return null
+                               couple = new Couple[JavaType, NitType](jtypes[i], ntypes[i])
+                       end
+               end
+
+               return couple
+       end
+end
+
+# Contains raw code mostly used to copy collections
+class CodeWarehouse
+
+       # Collection as return value
+       fun return_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String): String
+       do
+               var narray_id = "nit_array"
+               var loop_ = create_loop(java_type, nit_type, false, "java_array", narray_id)
+               var imports = create_imports(nit_type, false)
+
+               return """{{{imports}}} in "Java" `{ 
+               {{{java_type.to_s}}} java_array = recv.{{{jmethod_id}}}({{{params_id}}});
+               int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
+
+               {{{loop_}}}
+
+               return {{{narray_id}}};
+       `}
+       """
+       end
+
+       # Collection as parameter
+       fun param_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String, has_return: Bool): String
+       do
+               var narray_id = "nit_array"
+               var jarray_id = "java_array"
+               var loop_ = create_loop(java_type, nit_type, true, jarray_id, narray_id)
+               var imports = create_imports(nit_type, true)
+               var jtype = java_type.to_s
+               var jinstanciation = create_array_instance(java_type, nit_type, jarray_id)
+               var return_str = ""
+               
+               if has_return then
+                       return_str = "return "
+               end
+
+               params_id = params_id.replace(nit_type.arg_id, jarray_id)
+
+               return """{{{imports}}} in "Java" `{ 
+               {{{jinstanciation}}}
+               int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
+
+               {{{loop_}}}
+
+               {{{return_str}}}recv.{{{jmethod_id}}}({{{params_id}}});
+       `}
+       """
+       end
+
+       # One collection parameter and the return type will be copied
+       fun param_return_copy(return_types, param_types: Couple[JavaType, NitType], jmethod_id, params_id: String): String
+       do
+               var narray_id = "nit_array"
+               var narray_id2 = "nit_array2"
+
+               var r_jtype = return_types.first
+               var r_ntype = return_types.second
+
+               var p_jtype = param_types.first
+               var p_ntype = param_types.second
+
+               var r_loop = create_loop(r_jtype, r_ntype, false, "java_array", narray_id)
+               var p_loop = create_loop(p_jtype, p_ntype, true, "java_array2", narray_id2)
+
+               var imports = new Array[String]
+               
+               # Avoid import duplication
+               if p_ntype.to_s != r_ntype.to_s then
+                       imports.add create_imports(p_ntype, true)
+               end
+
+               imports.add create_imports(r_ntype, false)
+
+               params_id = params_id.replace(p_ntype.arg_id, narray_id)
+
+               var jinstanciation = create_array_instance(p_jtype, p_ntype, "java_array")
+
+               return """{{{imports.join(", ")}}} in "Java" `{
+               {{{jinstanciation}}}
+
+               {{{p_loop}}}
+
+               {{{r_jtype.to_s}}} java_array2 = recv.{{{jmethod_id}}}({{{params_id}}});
+               int {{{narray_id2}}} = new_{{{r_ntype.id}}}_of_{{{r_ntype.generic_params.join("_")}}}();
+
+               {{{r_loop}}}
+
+               return {{{narray_id2}}};
+       `}
+       """
+       end
+
+       private fun create_array_instance(java_type: JavaType, nit_type: NitType, jarray_id: String): String
+       do
+               var jtype = java_type.to_s
+               var instanciation = ""
+
+               if java_type.is_primitive_array then
+                       instanciation = "{jtype} {jarray_id} = new {java_type.full_id}[Array_of_{nit_type.generic_params[0]}_length({nit_type.arg_id})];"
+               else
+                       instanciation = "{jtype} {jarray_id} = new {jtype}();"
+               end
+
+               return instanciation
+       end
+
+       private fun create_imports(nit_type: NitType, is_param: Bool): String
+       do
+               var imports = ""
+               var ntype = nit_type.to_s
+               var gen_type = nit_type.generic_params.join(", ")
+
+               if not is_param then
+                       if nit_type.is_map then
+                               imports = """import {{{ntype}}}, {{{ntype}}}.[]="""
+                       else
+                               imports = """import {{{ntype}}}, {{{ntype}}}.add"""
+                       end
+               else if nit_type.id == "Array" then
+                       imports = """import {{{ntype}}}.length, {{{ntype}}}.[]"""
+               else if nit_type.is_map then
+                       imports = """import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item, Iterator[{{{gen_type}}}].key"""
+               else
+                       imports = """import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item"""
+               end
+               
+               return imports
+       end
+
+       private fun create_loop(java_type: JavaType, nit_type: NitType, is_param: Bool, jarray_id, narray_id: String): String
+       do
+               var loop_header = ""
+               var loop_body = ""
+               var gen_type = nit_type.generic_params.join("_")
+
+               if is_param then
+                       if java_type.is_primitive_array then
+                               loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
+                               loop_body   = """\t\t\t{{{jarray_id}}}[i] = {{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{nit_type.arg_id}}}, i);"""
+                       else if nit_type.id == "Array" then
+                               loop_header = """int length = Array_of_{{{gen_type}}}_length({{{nit_type.arg_id}}});\n\t\tfor(int i=0; i < length; ++i)"""
+                               loop_body   = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{narray_id}}}, i));"""
+                       else
+                               loop_header = """int itr = {{{nit_type.id}}}_of_{{{gen_type}}}_iterator({{{nit_type.arg_id}}});\n\t\twhile(Iterator_of_{{{gen_type}}}_is_ok(itr)) {"""
+                               if nit_type.is_map then
+                                       var key_cast = java_type.to_cast(java_type.generic_params[0].id, true)
+                                       var value_cast = java_type.to_cast(java_type.generic_params[1].id, true)
+                                       loop_body   = """\t\t\t{{{jarray_id}}}[{{{key_cast}}}iterator_of_{{{nit_type.id}}}_key(itr)] = {{{value_cast}}}iterator_of_{{{nit_type.id}}}_item(itr);\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
+                               else
+                                       loop_body   = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}iterator_of_{{{nit_type.id}}}_item(itr));\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
+                               end
+                       end
+               else
+                       if nit_type.is_map then
+                               var key_cast = java_type.to_cast(java_type.generic_params[0].id, false)
+                               var value_cast = java_type.to_cast(java_type.generic_params[1].id, false)
+                               loop_header = """for (java.util.Map.Entry<{{{java_type.generic_params[0]}}}, {{{java_type.generic_params[1]}}}> e: {{{jarray_id}}})"""
+                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_{{{nit_type.generic_params[1]}}}__index_assign({{{narray_id}}}, {{{key_cast}}}e.getKey(), {{{value_cast}}}e.getValue()); """
+                       else if java_type.is_iterable then
+                               loop_header = """for ({{{java_type.generic_params[0]}}} e: {{{jarray_id}}})"""
+                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}e);"""
+                       else
+                               loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
+                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}{{{jarray_id}}}[i]);"""
+                       end
+               end
+
+               return loop_header + "\n" + loop_body
+       end
+end
diff --git a/contrib/jwrapper/src/jtype_converter.nit b/contrib/jwrapper/src/jtype_converter.nit
new file mode 100644 (file)
index 0000000..95adbb4
--- /dev/null
@@ -0,0 +1,99 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
+#
+# 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.
+
+# Services to convert java type to nit type and get casts if needed
+module jtype_converter
+
+class JavaTypeConverter
+
+       var type_map = new HashMap[String, String]
+       var param_cast_map = new HashMap[String, String]
+       var return_cast_map = new HashMap[String, String]
+
+       init
+       do
+               # Java type to nit type
+               type_map["byte"] = "Int"
+               type_map["Byte"] = "Int"
+               type_map["short"] = "Int"
+               type_map["Short"] = "Int"
+               type_map["int"] = "Int"
+               type_map["Integer"] = "Int"
+               type_map["long"] = "Int"
+               type_map["Long"] = "Int"
+               type_map["char"] = "Char"
+               type_map["Character"] = "Char"
+               type_map["float"] = "Float"
+               type_map["Float"] = "Float"
+               type_map["double"] = "Float"
+               type_map["Double"] = "Float"
+               type_map["boolean"] = "Bool"
+               type_map["Boolean"] = "Bool"
+               type_map["Object"] = "JavaObject"
+               type_map["Bundle"] = "NativeBundle"
+               type_map["String"] = "JavaString"
+               type_map["CharSequence"] = "JavaString"
+
+               # Collections
+               type_map["List"] = "Array"
+               type_map["ArrayList"] = "Array"
+               type_map["LinkedList"] = "List"
+               type_map["Vector"] = "Array"
+       
+               type_map["Set"] = "HashSet"
+               type_map["SortedSet"] = "Still have to make my mind on this one"
+               type_map["HashSet"] = "HashSet"
+               type_map["TreeSet"] = "HashSet"
+               type_map["LinkedHashSet"] = "HashSet"
+               type_map["Map"] = "HashMap"
+               type_map["SortedMap"] = "RBTreeMap"
+               type_map["HashMap"] = "HashMap"
+               type_map["TreeMap"] = "RBTreeMap"
+               type_map["Hashtable"] = "HashMap"
+               type_map["LinkedHashMap"] = "HashMap"
+
+               # Cast if the type is given as a parameter
+               param_cast_map["byte"] = "(byte)"
+               param_cast_map["Byte"] = "(Byte)"
+               param_cast_map["short"] = "(short)"
+               param_cast_map["Short"] = "(short)"
+               param_cast_map["float"] = "(float)"
+               param_cast_map["Float"] = "(float)"
+               # FIXME: Uncomment as soon as Nit `Int` will be equivalent to Java `long`
+               # param_cast_map["int"] = "int"
+               # param_cast_map["Integer"] = "int"
+
+               # Cast if the type is given as a return value
+               return_cast_map["CharSequence"] = "(String)"
+               # FIXME: Erase as soon as the Nit `Int` type will become a Java `long`
+               return_cast_map["long"] = "(int)"
+       end
+
+       fun to_nit_type(java_type: String): nullable String
+       do
+               return self.type_map.get_or_null(java_type)
+       end
+
+       fun cast_as_param(java_type: String): String
+       do
+               return self.param_cast_map.get_or_default(java_type, "")
+       end
+       
+       fun cast_as_return(java_type: String): String
+       do
+               return self.return_cast_map.get_or_default(java_type, "")
+       end
+end