model: use the robust `intro_mmodule` instead of `intro.mmodule`.
[nit.git] / src / compiler / java_compiler.nit
index 50c9bc5..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
@@ -1484,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
@@ -1588,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
@@ -2126,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
@@ -2154,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