model: use the robust `intro_mmodule` instead of `intro.mmodule`.
[nit.git] / src / compiler / java_compiler.nit
index 1cc6b22..84b5a47 100644 (file)
@@ -36,9 +36,12 @@ redef class ToolContext
        # Where to output tmp files
        var opt_compile_dir = new OptionString("Directory used to generate temporary files", "--compile-dir")
 
+       # Compile using ant instead of make (faster, but no error display)
+       var opt_ant = new OptionBool("Batch with ant (faster, but no error display)", "--ant")
+
        redef init do
                super
-               option_context.add_option(opt_output, opt_compile_dir)
+               option_context.add_option(opt_output, opt_compile_dir, opt_ant)
        end
 end
 
@@ -72,7 +75,11 @@ redef class ModelBuilder
                time0 = time1
                toolcontext.info("*** COMPILING JAVA ***", 1)
 
-               build_with_make(compiler, jfiles)
+               if toolcontext.opt_ant.value then
+                       build_with_ant(compiler, jfiles)
+               else
+                       build_with_make(compiler, jfiles)
+               end
                write_shell_script(compiler)
 
                time1 = get_time
@@ -83,14 +90,32 @@ redef class ModelBuilder
        fun write_java_files(compiler: JavaCompiler): Array[String] do
                var jfiles = new Array[String]
                for f in compiler.files do
-                       var file = new FileWriter.open("{compiler.compile_dir}/{f.filename}")
+                       var filepath = "{compiler.compile_dir}/{f.filename}"
+                       var file = cache_file(filepath)
                        for line in f.lines do file.write(line)
-                       file.close
+                       close_cache(filepath, file)
                        jfiles.add(f.filename)
                end
                return jfiles
        end
 
+       # Cache a file as `{filepath}.tmp` and replace the original if different
+       private fun cache_file(filepath: String): FileWriter do
+               if toolcontext.opt_ant.value and filepath.file_exists then
+                       return new FileWriter.open("{filepath}.tmp")
+               else
+                       return new FileWriter.open(filepath)
+               end
+       end
+
+       # Close the writer and move tmp file to original if modified
+       private fun close_cache(filepath: String, file: FileWriter) do
+               file.close
+               if "{filepath}.tmp".file_exists then
+                       sys.system("if ! diff {filepath}.tmp {filepath} > /dev/null; then mv {filepath}.tmp {filepath}; else rm {filepath}.tmp; fi")
+               end
+       end
+
        # Compile Java generated files using `make`
        fun build_with_make(compiler: JavaCompiler, jfiles: Array[String]) do
                write_manifest(compiler)
@@ -107,6 +132,23 @@ redef class ModelBuilder
                if res != 0 then toolcontext.error(null, "make failed! Error code: {res}.")
        end
 
+       # Compile Java sources using `ant`
+       fun build_with_ant(compiler: JavaCompiler, jfiles: Array[String]) do
+               compile_antfile(compiler, jfiles)
+               var outname = compiler.outname.to_path.filename
+               var antpath = "{compiler.compile_dir}/{outname}.xml"
+               self.toolcontext.info("ant jar -f {antpath}", 2)
+               var res
+               if self.toolcontext.verbose_level >= 3 then
+                       res = sys.system("ant jar -f {antpath} 2>&1")
+               else
+                       res = sys.system("ant jar -f {antpath} 2>&1 > /dev/null")
+               end
+               if res != 0 then
+                       toolcontext.error(null, "ant compile failed! Error code: {res}.")
+               end
+       end
+
        # Write the Makefile used to compile Java generated files into an executable jar
        fun write_makefile(compiler: JavaCompiler, jfiles: Array[String]) do
                # list class files from jfiles
@@ -137,6 +179,31 @@ redef class ModelBuilder
                toolcontext.info("Generated makefile: {makename}", 2)
        end
 
+       # The Ant `build.xml` script used to compile build the final jar
+       fun compile_antfile(compiler: JavaCompiler, jfiles: Array[String]) do
+               var compile_dir = compiler.compile_dir
+               var outname = compiler.outname.to_path.filename
+               var outpath = (sys.getcwd / compiler.outname).simplify_path
+               var antname = "{compile_dir}/{outname}.xml"
+               var antfile = new FileWriter.open(antname)
+               var jname = compiler.mainmodule.jname
+               antfile.write("<project>")
+               antfile.write(" <target name=\"compile\">")
+               antfile.write("  <mkdir dir=\"classes\"/>")
+               antfile.write("  <javac includes=\"{compiler.mainmodule.jname}_Main.java {jfiles.join(" ")}\" srcdir=\".\" destdir=\"classes\"/>")
+               antfile.write(" </target>")
+               antfile.write(" <target name=\"jar\" depends=\"compile\">")
+               antfile.write("  <jar destfile=\"{outpath}.jar\" basedir=\"classes\">")
+               antfile.write("   <manifest>")
+               antfile.write("    <attribute name=\"Main-Class\" value=\"{jname}_Main\"/>")
+               antfile.write("   </manifest>")
+               antfile.write("  </jar>")
+               antfile.write(" </target>")
+               antfile.write("</project>")
+               antfile.close
+               toolcontext.info("Generated antfile: {antname}", 2)
+       end
+
        # Write the Java manifest file
        private fun write_manifest(compiler: JavaCompiler) do
                var compile_dir = compiler.compile_dir
@@ -634,6 +701,14 @@ class JavaCompilerVisitor
        # Compile a statement (if any)
        fun stmt(nexpr: nullable AExpr) do
                if nexpr == null then return
+               if nexpr.mtype == null and not nexpr.is_typed then
+                       # Untyped expression.
+                       # Might mean dead code or invalid code
+                       # so aborts
+                       add_abort("FATAL: bad statement executed.")
+                       return
+               end
+
                var old = self.current_node
                current_node = nexpr
                nexpr.stmt(self)
@@ -651,6 +726,19 @@ class JavaCompilerVisitor
                        res = nexpr.expr(self)
                end
 
+               if res == null then
+                       # Untyped expression.
+                       # Might mean dead code or invalid code.
+                       # so aborts
+                       add_abort("FATAL: bad expression executed.")
+                       # and return a placebo result to please the C compiler
+                       if mtype == null then mtype = compiler.mainmodule.object_type
+                       res = null_instance
+
+                       self.current_node = old
+                       return res
+               end
+
                if mtype != null then
                        mtype = anchor(mtype)
                        res = autobox(res, mtype)
@@ -696,6 +784,13 @@ class JavaCompilerVisitor
        # Used by aborts, asserts, casts, etc.
        fun add_abort(message: String) do
                add("System.err.print(\"Runtime error: {message}\");")
+               add_raw_abort
+       end
+
+       # Abort without displaying the cause.
+       #
+       # Used to customizable errors.
+       private fun add_raw_abort do
                var node = current_node
                if node != null then
                        add("System.err.print(\" ({node.location.short_location})\");")
@@ -704,6 +799,15 @@ class JavaCompilerVisitor
                add("System.exit(1);")
        end
 
+       # Add a dynamic cast
+       fun add_cast(value: RuntimeVariable, mtype: MType) do
+               var res = type_test(value, mtype)
+               add("if (!{res}) \{")
+               add("System.err.print(\"Runtime error: Cast failed. Expected `{mtype.to_s.escape_to_c}`, got `\" + {value}.rtclass.class_name + \"`\");")
+               add_raw_abort
+               add("\}")
+       end
+
        # Types handling
 
        # Anchor a type to the main module and the current receiver
@@ -1226,7 +1330,7 @@ end
 redef class MClass
 
        # Runtime name
-       private fun rt_name: String do return "RTClass_{intro.mmodule.jname}_{jname}"
+       private fun rt_name: String do return "RTClass_{intro_mmodule.jname}_{jname}"
 
        # Generate a Java RTClass for a Nit MClass
        fun compile_to_java(v: JavaCompilerVisitor) do
@@ -1323,43 +1427,62 @@ redef class MMethodDef
                var modelbuilder = v.compiler.modelbuilder
                var node = modelbuilder.mpropdef2node(self)
 
+               var recv = mclassdef.bound_mtype
+               var arguments = new Array[RuntimeVariable]
+               var frame = new JavaStaticFrame(v, self, recv, arguments)
+               v.frame = frame
+
+               var selfvar = v.decl_var("self", recv)
+               arguments.add(selfvar)
+               var boxed = v.new_expr("args[0]", v.compiler.mainmodule.object_type)
+               v.add "{selfvar} = {v.autobox(boxed, recv)};"
+
+               var msignature = self.msignature
+               var ret = null
+               if msignature != null then
+                       ret = msignature.return_mtype
+                       if ret != null then
+                               var retvar = v.decl_var("ret", ret)
+                               if ret.name == "Int" then v.add "{retvar} = 0;"
+                               if ret.name == "Float" then v.add "{retvar} = 0.0;"
+                               if ret.name == "Bool" then v.add "{retvar} = false;"
+                               if ret.name == "Char" then v.add "{retvar} = 0;"
+                               if ret.name == "Byte" then v.add "{retvar} = 0;"
+                               frame.returnvar = retvar
+                       end
+               end
+               frame.returnlabel = v.get_name("RET_LABEL")
+
+               v.current_node = node
                if is_abstract then
                        v.add_abort("Abstract method `{mproperty.name}` called on `\"  + {selfvar}.rtclass.class_name +\"`")
                        v.add("return null;")
                        return
                end
+               v.current_node = null
+
+               v.add("{frame.returnlabel.as(not null)}: \{")
 
                if node isa APropdef then
-                       node.compile_to_java(v, self)
+                       node.compile_to_java(v, self, arguments)
                else if node isa AClassdef then
-                       node.compile_to_java(v, self)
+                       node.compile_to_java(v, self, arguments)
                else
                        abort
                end
+
+               v.add("\}")
+               if ret != null then
+                       v.add("return {v.autobox(frame.returnvar.as(not null), v.compiler.mainmodule.object_type)};")
+               else
+                       v.add("return null;")
+               end
        end
 end
 
 redef class AClassdef
-       private fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef) do
+       private fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable]) do
                if mpropdef == self.mfree_init then
-                       var recv = mpropdef.mclassdef.bound_mtype
-                       var arguments = new Array[RuntimeVariable]
-                       var frame = new JavaStaticFrame(v, mpropdef, recv, arguments)
-                       v.frame = frame
-
-                       var selfvar = v.decl_var("self", recv)
-                       arguments.add(selfvar)
-                       var boxed = v.new_expr("args[0];", v.compiler.mainmodule.object_type)
-                       v.add "{selfvar} = {v.unbox(boxed, recv)};"
-
-                       var msignature = mpropdef.msignature
-                       var ret = null
-                       if msignature != null then
-                               ret = msignature.return_mtype
-                               if ret != null then frame.returnvar = v.new_var(ret)
-                       end
-                       frame.returnlabel = v.get_name("RET_LABEL")
-
                        assert mpropdef.mproperty.is_root_init
                        if not mpropdef.is_intro then
                                v.supercall(mpropdef, arguments.first.mtype.as(MClassType), arguments)
@@ -1367,54 +1490,31 @@ redef class AClassdef
                else
                        abort
                end
-               v.add("return null;")
        end
 end
 
 redef class APropdef
 
        # Compile that property definition to java code
-       fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef) do
+       fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable]) do
                v.info("NOT YET IMPLEMENTED {class_name}::compile_to_java")
-               v.add("return null;")
        end
 end
 
 redef class AMethPropdef
-       redef fun compile_to_java(v, mpropdef) do
-               var recv = mpropdef.mclassdef.bound_mtype
-               var arguments = new Array[RuntimeVariable]
-               var frame = new JavaStaticFrame(v, mpropdef, recv, arguments)
-               v.frame = frame
-
-               var selfvar = v.decl_var("self", recv)
-               arguments.add(selfvar)
-               var boxed = v.new_expr("args[0];", v.compiler.mainmodule.object_type)
-               v.add "{selfvar} = {v.unbox(boxed, recv)};"
-
-               var msignature = mpropdef.msignature
-               var ret = null
-               if msignature != null then
-                       ret = msignature.return_mtype
-                       if ret != null then frame.returnvar = v.new_var(ret)
-               end
-               frame.returnlabel = v.get_name("RET_LABEL")
-
-               if not mpropdef.is_intern and msignature != null then
+       redef fun compile_to_java(v, mpropdef, arguments) do
+               if mpropdef.msignature != null then
                        var i = 0
-                       for mparam in msignature.mparameters do
+                       for mparam in mpropdef.msignature.as(not null).mparameters do
                                var variable = n_signature.as(not null).n_params[i].variable
                                if variable == null then continue
                                var argvar = v.variable(variable)
-                               boxed = v.new_expr("args[{i + 1}];", v.compiler.mainmodule.object_type)
-                               v.add "{argvar} = {v.unbox(boxed, mparam.mtype)};"
+                               v.assign(argvar, v.new_expr("args[{i + 1}]", v.compiler.mainmodule.object_type))
                                arguments.add(argvar)
                                i += 1
                        end
                end
 
-               v.add("{frame.returnlabel.as(not null)}: \{")
-
                # Call the implicit super-init
                var auto_super_inits = self.auto_super_inits
                if auto_super_inits != null then
@@ -1433,24 +1533,11 @@ redef class AMethPropdef
                        v.supercall(mpropdef, arguments.first.mtype.as(MClassType), arguments)
                end
 
-               compile_inside_to_java(v, mpropdef)
-               v.add("\}")
-
-               if ret != null then
-                       if ret.is_java_primitive then
-                               boxed = v.box(frame.returnvar.as(not null), v.compiler.mainmodule.object_type)
-                               v.add("return {boxed};")
-                       else
-                               v.add("return {frame.returnvar.as(not null)};")
-                       end
-               else
-                       v.add("return null;")
-               end
-               v.frame = null
+               compile_inside_to_java(v, mpropdef, arguments)
        end
 
        # Compile the inside of the method body
-       private fun compile_inside_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef) do
+       private fun compile_inside_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable]) do
                # Compile intern methods
                if mpropdef.is_intern then
                        if compile_intern_to_java(v, mpropdef, arguments) then return
@@ -1501,10 +1588,10 @@ redef class AMethPropdef
                        else if pname == "%" then
                                v.ret(v.new_expr("{arguments[0]} % {arguments[1]}", ret.as(not null)))
                                return true
-                       else if pname == "lshift" then
+                       else if pname == "<<" then
                                v.ret(v.new_expr("{arguments[0]} << {arguments[1]}", ret.as(not null)))
                                return true
-                       else if pname == "rshift" then
+                       else if pname == ">>" then
                                v.ret(v.new_expr("{arguments[0]} >> {arguments[1]}", ret.as(not null)))
                                return true
                        else if pname == "==" then
@@ -1605,10 +1692,10 @@ redef class AMethPropdef
                        else if pname == "%" then
                                v.ret(v.new_expr("(byte)({arguments[0]} % {arguments[1]})", ret.as(not null)))
                                return true
-                       else if pname == "lshift" then
+                       else if pname == "<<" then
                                v.ret(v.new_expr("(byte)({arguments[0]} << {arguments[1]})", ret.as(not null)))
                                return true
-                       else if pname == "rshift" then
+                       else if pname == ">>" then
                                v.ret(v.new_expr("(byte)({arguments[0]} >> {arguments[1]})", ret.as(not null)))
                                return true
                        else if pname == "==" then
@@ -2064,6 +2151,78 @@ redef class AVarAssignExpr
 end
 
 
+redef class AAssertExpr
+       redef fun stmt(v) do
+               var cond = v.expr_bool(self.n_expr)
+               v.add("if (!{cond}) \{")
+               v.stmt(self.n_else)
+               var nid = self.n_id
+               if nid != null then
+                       v.add_abort("Assert '{nid.text}' failed")
+               else
+                       v.add_abort("Assert failed")
+               end
+               v.add("\}")
+       end
+end
+
+redef class AImpliesExpr
+       redef fun expr(v) do
+               var res = v.new_var(mtype.as(not null))
+               var i1 = v.expr_bool(n_expr)
+               v.add("if (!{i1}) \{")
+               v.add("{res} = true;")
+               v.add("\} else \{")
+               var i2 = v.expr_bool(n_expr2)
+               v.add("{res} = {i2};")
+               v.add("\}")
+               return res
+       end
+end
+
+redef class AOrElseExpr
+       redef fun expr(v)
+       do
+               var res = v.new_var(self.mtype.as(not null))
+               var i1 = v.expr(self.n_expr, null)
+               v.add("if ({i1} != null && !{i1}.is_null()) \{")
+               v.assign(res, i1)
+               v.add("\} else \{")
+               var i2 = v.expr(self.n_expr2, null)
+               v.assign(res, i2)
+               v.add("\}")
+               return res
+       end
+end
+
+redef class AOrExpr
+       redef fun expr(v) do
+               var res = v.new_var(self.mtype.as(not null))
+               var i1 = v.expr_bool(self.n_expr)
+               v.add("if ({i1}) \{")
+               v.add("{res} = true;")
+               v.add("\} else \{")
+               var i2 = v.expr_bool(self.n_expr2)
+               v.add("{res} = {i2};")
+               v.add("\}")
+               return res
+       end
+end
+
+redef class AAndExpr
+       redef fun expr(v) do
+               var res = v.new_var(self.mtype.as(not null))
+               var i1 = v.expr_bool(self.n_expr)
+               v.add("if (!{i1}) \{")
+               v.add("{res} = false;")
+               v.add("\} else \{")
+               var i2 = v.expr_bool(self.n_expr2)
+               v.add("{res} = {i2};")
+               v.add("\}")
+               return res
+       end
+end
+
 redef class ANotExpr
        redef fun expr(v) do
                var cond = v.expr_bool(self.n_expr)
@@ -2071,12 +2230,17 @@ redef class ANotExpr
        end
 end
 
-redef class AIntExpr
-       redef fun expr(v) do return v.int_instance(self.value.as(not null))
-end
-
-redef class AByteExpr
-       redef fun expr(v) do return v.byte_instance(self.value.as(not null))
+redef class AIntegerExpr
+       redef fun expr(v) do
+               if value isa Int then
+                       return v.int_instance(self.value.as(Int))
+               else if value isa Byte then
+                       return v.byte_instance(self.value.as(Byte))
+               else
+                       # Should not happen
+                       abort
+               end
+       end
 end
 
 redef class AFloatExpr
@@ -2099,6 +2263,27 @@ redef class ANullExpr
        redef fun expr(v) do return v.null_instance
 end
 
+redef class AAsCastExpr
+       redef fun expr(v)
+       do
+               var i = v.expr(n_expr, null)
+               v.add_cast(i, mtype.as(not null))
+               return i
+       end
+end
+
+redef class AAsNotnullExpr
+       redef fun expr(v) do
+               var i = v.expr(n_expr, null)
+               if i.mtype.is_java_primitive then return i
+
+               v.add("if ({i} == null || {i}.is_null()) \{")
+               v.add_abort("Cast failed")
+               v.add("\}")
+               return i
+       end
+end
+
 redef class AIsaExpr
        redef fun expr(v)
        do