X-Git-Url: http://nitlanguage.org diff --git a/src/compiler/java_compiler.nit b/src/compiler/java_compiler.nit index 3d1384d..84b5a47 100644 --- a/src/compiler/java_compiler.nit +++ b/src/compiler/java_compiler.nit @@ -25,6 +25,7 @@ module java_compiler import rapid_type_analysis +import transform import frontend redef class ToolContext @@ -35,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 @@ -71,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 @@ -82,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) @@ -106,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 @@ -136,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("") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write(" ") + antfile.write("") + 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 @@ -194,10 +262,872 @@ class JavaCompiler return file end + # Kind of visitor to use + type VISITOR: JavaCompilerVisitor + + # Initialize a visitor specific for the compiler engine + fun new_visitor(filename: String): VISITOR do + return new JavaCompilerVisitor(self, new_file(filename)) + end + + # RuntimeModel representation + private var rt_model: JavaRuntimeModel is lazy do return new JavaRuntimeModel + # Compile Nit code to Java fun do_compilation do - modelbuilder.toolcontext.info("NOT YET IMPLEMENTED", 0) + # compile java classes used to represents the runtime model of the program + rt_model.compile_rtmodel(self) + compile_box_kinds + + # compile class structures + compile_mclasses_to_java + + # compile method structures + compile_mmethods_to_java + + # compile main + compile_main_function + end + + # Prepare the boxes used to represent Java primitive types + fun compile_box_kinds do + # Collect all bas box class + # FIXME: this is not completely fine with a separate compilation scheme + for classname in ["Int", "Bool", "Byte", "Char", "Float"] do + var classes = mainmodule.model.get_mclasses_by_name(classname) + if classes == null then continue + assert classes.length == 1 else print classes.join(", ") + box_kinds.add(classes.first.mclass_type) + end + end + + # Types of boxes used to represent Java primitive types + var box_kinds = new Array[MClassType] + + # Generate a `RTClass` for each `MClass` found in model + # + # This is a global phase because we need to know all the program to build + # attributes, fill vft and type table. + fun compile_mclasses_to_java do + for mclass in mainmodule.model.mclasses do + mclass.compile_to_java(new_visitor("{mclass.rt_name}.java")) + end + end + + # Generate a `RTMethod` for each `MMethodDef` found in model + # + # This is a separate phase. + fun compile_mmethods_to_java do + for mmodule in mainmodule.in_importation.greaters do + for mclassdef in mmodule.mclassdefs do + for mdef in mclassdef.mpropdefs do + if mdef isa MMethodDef then + mdef.compile_to_java(new_visitor("{mdef.rt_name}.java")) + end + end + end + end + end + + # Generate Java main that call Sys.main + fun compile_main_function do + var v = new_visitor("{mainmodule.jname}_Main.java") + v.add("public class {mainmodule.jname}_Main \{") + v.add(" public static void main(String[] args) \{") + + var main_type = mainmodule.sys_type + if main_type != null then + var mainmodule = v.compiler.mainmodule + var glob_sys = v.init_instance(main_type) + var main_init = mainmodule.try_get_primitive_method("init", main_type.mclass) + if main_init != null then + v.send(main_init, [glob_sys]) + end + var main_method = mainmodule.try_get_primitive_method("run", main_type.mclass) or else + mainmodule.try_get_primitive_method("main", main_type.mclass) + if main_method != null then + v.send(main_method, [glob_sys]) + end + end + v.add(" \}") + v.add("\}") + end +end + +# The class visiting the AST +# +# A visitor is attached to one JavaCodeFile it writes into. +class JavaCompilerVisitor + super Visitor + + # JavaCompiler used with this visitor + type COMPILER: JavaCompiler + + # The associated compiler + var compiler: JavaCompiler + + # The file to write generated code into + var file: JavaCodeFile + + # Names handling + + private var names = new HashSet[String] + private var last: Int = 0 + + # Return a new name based on `s` and unique in the visitor + fun get_name(s: String): String do + if not self.names.has(s) then + self.names.add(s) + return s + end + var i = self.last + 1 + loop + var s2 = s + i.to_s + if not self.names.has(s2) then + self.last = i + self.names.add(s2) + return s2 + end + i = i + 1 + end + end + + # Return an unique and stable identifier associated with an escapemark + fun escapemark_name(e: nullable EscapeMark): String do + assert e != null + var frame = self.frame + assert frame != null + if frame.escapemark_names.has_key(e) then return frame.escapemark_names[e] + var name = e.name + if name == null then name = "label" + name = get_name(name) + frame.escapemark_names[e] = name + return name + end + + # Insert a C label for associated with an escapemark + fun add_escape_label(e: nullable EscapeMark) do + if e == null then return + if e.escapes.is_empty then return + add("BREAK_{escapemark_name(e)}: ") + end + + # Variables handling + + # Registered variables + protected var variables = new HashMap[Variable, RuntimeVariable] + + # Return the local RuntimeVariable associated to a Nit local variable + fun variable(variable: Variable): RuntimeVariable do + if variables.has_key(variable) then + return variables[variable] + else + var name = get_name("var_{variable.name}") + var mtype = variable.declared_type.as(not null) + mtype = anchor(mtype) + var res = decl_var(name, mtype) + variables[variable] = res + return res + end + end + + # Return a new uninitialized local RuntimeVariable with `name` + fun decl_var(name: String, mtype: MType): RuntimeVariable do + var res = new RuntimeVariable(name, mtype, mtype) + res.is_boxed = not mtype.is_java_primitive + add("{mtype.java_type} {name} /* : {mtype} */;") + return res + end + + # Return a new uninitialized local RuntimeVariable + fun new_var(mtype: MType): RuntimeVariable do + mtype = anchor(mtype) + var name = self.get_name("var") + return decl_var(name, mtype) + end + + # Calls handling + + # The current `JavaStaticFrame` + var frame: nullable JavaStaticFrame = null is writable + + # Return a new local RuntimeVariable initialized from `args[0]` + fun new_recv(mtype: MType): RuntimeVariable do + var res = new_var(mtype) + add("{res} = args[0];") + return res + end + + # Calls handling + + # Compile a call within a callsite + fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do + var initializers = callsite.mpropdef.initializers + if not initializers.is_empty then + var recv = arguments.first + + var i = 1 + for p in initializers do + if p isa MMethod then + var args = [recv] + var msignature = p.intro.msignature + if msignature != null then + for x in msignature.mparameters do + args.add arguments[i] + i += 1 + end + end + send(p, args) + else if p isa MAttribute then + info("NOT YET IMPLEMENTED {class_name}::compile_callsite for MAttribute `{p}`") + #self.write_attribute(p, recv, arguments[i]) + i += 1 + else abort + end + assert i == arguments.length + + return send(callsite.mproperty, [recv]) + end + + return send(callsite.mproperty, arguments) + end + + # Evaluate `args` as expressions in the call of `mpropdef` on `recv`. + # + # This method is used to manage varargs in signatures and returns the real array + # of runtime variables to use in the call. + fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable] do + var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null) + var res = new Array[RuntimeVariable] + res.add(recv) + + if msignature.arity == 0 then return res + + if map == null then + assert args.length == msignature.arity + for ne in args do + res.add expr(ne, null) + end + return res + end + + # Eval in order of arguments, not parameters + var exprs = new Array[RuntimeVariable].with_capacity(args.length) + for ne in args do + exprs.add expr(ne, null) + end + + # Fill `res` with the result of the evaluation according to the mapping + for i in [0..msignature.arity[ do + var param = msignature.mparameters[i] + var j = map.map.get_or_null(i) + if j == null then + # default value + res.add(null_instance) + continue + end + if param.is_vararg and map.vararg_decl > 0 then + var vararg = exprs.sub(j, map.vararg_decl) + var elttype = param.mtype + var arg = self.vararg_instance(mpropdef, recv, vararg, elttype) + res.add(arg) + continue + end + res.add exprs[j] + end + return res + end + + # Generate a static call on a method definition (no receiver needed). + fun static_call(mmethoddef: MMethodDef, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do + var res: nullable RuntimeVariable + var ret = mmethoddef.msignature.as(not null).return_mtype + if ret == null then + res = null + else + ret = ret.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true) + res = self.new_var(ret) + end + + # Autobox arguments + adapt_signature(mmethoddef, arguments) + + var rt_name = mmethoddef.rt_name + if res == null then + add("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});") + return null + end + var ress = new_expr("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type) + assign(res, ress) + return res + end + + # Generate a polymorphic send for `method` with `arguments` + fun send(mmethod: MMethod, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do + # Shortcut calls on primitives + if arguments.first.mcasttype.is_java_primitive then + return monomorphic_send(mmethod, arguments.first.mcasttype, arguments) + end + # Polymorphic send + return table_send(mmethod, arguments) + end + + + # Handle common special cases before doing the effective method invocation + # This methods handle the `==` and `!=` methods and the case of the null receiver. + # Note: a { is open in the generated C, that enclose and protect the effective method invocation. + # Client must not forget to close the } after them. + # + # The value returned is the result of the common special cases. + # If not null, client must compile it with the result of their own effective method invocation. + # + # If `before_send` can shortcut the whole message sending, a dummy `if(0){` + # is generated to cancel the effective method invocation that will follow + # TODO: find a better approach + private fun before_send(res: nullable RuntimeVariable, mmethod: MMethodDef, arguments: Array[RuntimeVariable]) do + var bool_type = compiler.mainmodule.bool_type + var recv = arguments.first + var consider_null = mmethod.name == "==" or mmethod.name == "!=" or mmethod.name == "is_same_instance" + if recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType then + add("if ({recv} == null || {recv}.is_null()) \{") + if mmethod.name == "==" or mmethod.name == "is_same_instance" then + if res == null then res = new_var(bool_type) + var arg = arguments[1] + if arg.mcasttype isa MNullableType then + add("{res} = ({arg} == null || {arg}.is_null());") + else if arg.mcasttype isa MNullType then + add("{res} = true; /* is null */") + else + add("{res} = false; /* {arg.inspect} cannot be null */") + end + else if mmethod.name == "!=" then + if res == null then res = new_var(bool_type) + # res = self.new_var(bool_type) + var arg = arguments[1] + if arg.mcasttype isa MNullableType then + add("{res} = ({arg} != null && !{arg}.is_null());") + else if arg.mcasttype isa MNullType then + add("{res} = false; /* is null */") + else + add("{res} = true; /* {arg.inspect} cannot be null */") + end + else + add_abort("Receiver is null") + ret(null_instance) + end + add("\} else \{") + else + add "\{" + add "/* recv ({recv}) cannot be null since it's a {recv.mcasttype}" + end + if consider_null then + var arg = arguments[1] + if arg.mcasttype isa MNullType then + if res == null then res = new_var(bool_type) + if mmethod.name == "!=" then + add("{res} = true; /* arg is null and recv is not */") + else # `==` and `is_same_instance` + add("{res} = false; /* arg is null but recv is not */") + end + add("\}") # closes the null case + add("if (false) \{") # what follow is useless, Javac will drop it + end + end + end + + # Perform a method call through vft + private fun table_send(mmethod: TableCallable, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do + var mdef: MMethodDef + var name: String + if mmethod isa MMethod then + mdef = mmethod.intro + name = mmethod.full_name + else if mmethod isa MMethodDef then + mdef = mmethod + name = mmethod.full_name + else + abort + end + + var recv = arguments.first + var rect = mdef.mclassdef.bound_mtype + var msignature = mdef.msignature.as(not null) + msignature = msignature.resolve_for(rect, rect, compiler.mainmodule, true) + adapt_signature(mdef, arguments) + + var res: nullable RuntimeVariable + var ret = msignature.return_mtype + if ret == null then + res = null + else + res = self.new_var(ret) + end + + before_send(res, mdef, arguments) + + add "/* concrete call to {mdef} */" + if res != null then + var ress = new_expr("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type) + assign(res, ress) + else + add("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});") + end + + add("\}") # closes the null case + + return res + end + + # Generate a super call from a method definition + fun supercall(m: MMethodDef, recvtype: MClassType, args: Array[RuntimeVariable]): nullable RuntimeVariable do + return table_send(m, args) + end + + # Generate a monomorphic send for the method `m`, the type `t` and the arguments `args` + fun monomorphic_send(m: MMethod, t: MType, args: Array[RuntimeVariable]): nullable RuntimeVariable do + assert t isa MClassType + var propdef = m.lookup_first_definition(self.compiler.mainmodule, t) + return self.static_call(propdef, args) + end + + # Code generation + + # Add a line (will be suffixed by `\n`) + fun add(line: String) do file.lines.add("{line}\n") + + # Add a new partial line (no `\n` suffix) + fun addn(line: String) do file.lines.add(line) + + # 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) + current_node = old + end + + # Compile an expression an return its result + # `mtype` is the expected return type, pass null if no specific type is expected. + fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable do + var old = current_node + current_node = nexpr + + var res = null + if nexpr.mtype != null then + 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) + end + + current_node = old + return res + end + + # Alias for `self.expr(nexpr, self.bool_type)` + fun expr_bool(nexpr: AExpr): RuntimeVariable do + return expr(nexpr, compiler.mainmodule.bool_type) + end + + # Correctly assign a left and a right value + # Boxing and unboxing is performed if required + fun assign(left, right: RuntimeVariable) do + add("{left} = {autobox(right, left.mtype)};") + end + + # Generate a return with `value` + fun ret(value: RuntimeVariable) do + var frame = self.frame + assert frame != null + var returnvar = frame.returnvar + if returnvar != null then + assign(returnvar, value) + end + self.add("break {frame.returnlabel.as(not null)};") + end + + # Return a new local RuntimeVariable initialized with the Java expression `jexpr`. + # + # `mtype` is used for the Java return variable initialization. + fun new_expr(jexpr: String, mtype: MType): RuntimeVariable do + var res = new_var(mtype) + add("{res} = {jexpr};") + return res + end + + # Generate generic abort + # + # 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})\");") + end + add("System.err.println(\"\");") + 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 + fun anchor(mtype: MType): MType do + if not mtype.need_anchor then return mtype + return mtype.anchor_to(compiler.mainmodule, frame.as(not null).receiver) + end + + # Adapt the arguments of a method according to targetted `MMethodDef` + fun adapt_signature(m: MMethodDef, args: Array[RuntimeVariable]) do + var msignature = m.msignature.as(not null).resolve_for( + m.mclassdef.bound_mtype, + m.mclassdef.bound_mtype, + m.mclassdef.mmodule, true) + args.first = autobox(args.first, compiler.mainmodule.object_type) + for i in [0..msignature.arity[ do + args[i+1] = autobox(args[i + 1], compiler.mainmodule.object_type) + end + end + + # Box primitive `value` to `mtype`. + private fun box(value: RuntimeVariable, mtype: MType): RuntimeVariable do + if value.is_boxed then return value + var obj_type = compiler.mainmodule.object_type + if value.mtype isa MNullType then + return new_expr("new RTVal(null, null)", compiler.mainmodule.model.null_type) + end + var mbox = value.mtype.as(MClassType).mclass + return new_expr("new RTVal({mbox.rt_name}.get{mbox.rt_name}(), {value})", obj_type) + end + + # Unbox primitive `value` to `mtype`. + private fun unbox(value: RuntimeVariable, mtype: MType): RuntimeVariable do + if not value.is_boxed then return value + if not mtype.is_java_primitive then return value + if compiler.box_kinds.has(mtype) then + return new_expr("({mtype.java_type}){value}.value", mtype) + else + info "NOT YET IMPLEMENTED unbox for {value} ({mtype})" + abort + end end + + # Box or unbox primitive `value` to `mtype` if needed. + private fun autobox(value: RuntimeVariable, mtype: MType): RuntimeVariable do + if mtype.is_java_primitive then return unbox(value, mtype) + return box(value, mtype) + end + + # Can this `value` be a primitive Java value? + private fun can_be_primitive(value: RuntimeVariable): Bool do + var t = value.mcasttype.undecorate + if not t isa MClassType then return false + var k = t.mclass.kind + return k == interface_kind or t.is_java_primitive + end + + # Generate a polymorphic subtype test + fun type_test(value: RuntimeVariable, mtype: MType): RuntimeVariable do + add("/* {value.inspect} isa {mtype} */") + var res = self.new_var(compiler.mainmodule.bool_type) + + # check color is in table + var maybenull = (value.mcasttype isa MNullableType or value.mcasttype isa MNullType) + if maybenull then + add("if({value} == null || {value}.is_null()) \{") + add("{res} = true && {mtype isa MNullableType};") + add("\} else \{") + end + if mtype isa MNullableType then mtype = mtype.mtype + var mclass = mtype.as(MClassType).mclass + add("{res} = {value}.rtclass.supers.get(\"{mclass.jname}\") == {mclass.rt_name}.get{mclass.rt_name}();") + if maybenull then + add("\}") + end + return res + end + + # Generate the code required to dynamically check if 2 objects share the same runtime type + fun is_same_type_test(value1, value2: RuntimeVariable): RuntimeVariable do + var res = self.new_var(compiler.mainmodule.bool_type) + add("{res} = {value1}.rtclass == {value2}.rtclass;") + return res + end + + # Native instances + + # Generate an integer value + fun int_instance(value: Int): RuntimeVariable do + var t = compiler.mainmodule.int_type + return new RuntimeVariable(value.to_s, t, t) + end + + # Generate a byte value + fun byte_instance(value: Byte): RuntimeVariable do + var t = compiler.mainmodule.byte_type + return new RuntimeVariable(value.to_s, t, t) + end + + # Generate a char value + fun char_instance(value: Char): RuntimeVariable do + var t = compiler.mainmodule.char_type + return new RuntimeVariable("'{value.to_s.escape_to_c}'", t, t) + end + + # Generate a float value + # + # FIXME pass a Float, not a string + fun float_instance(value: String): RuntimeVariable do + var t = compiler.mainmodule.float_type + return new RuntimeVariable(value.to_s, t, t) + end + + # Generate an integer value + fun bool_instance(value: Bool): RuntimeVariable do + var t = compiler.mainmodule.bool_type + return new RuntimeVariable(value.to_s, t, t) + end + + # Generate the `null` value + fun null_instance: RuntimeVariable do + var t = compiler.mainmodule.model.null_type + return new RuntimeVariable("null", t, t) + end + + # Get an instance of a array for a vararg + fun vararg_instance(mpropdef: MPropDef, recv: RuntimeVariable, varargs: Array[RuntimeVariable], elttype: MType): RuntimeVariable do + # TODO handle dynamic types + info("NOT YET IMPLEMENTED vararg_instance") + return null_instance + # TODO return array_instance(varargs, elttype) + end + + # Nit instances + + # Generate a alloc-instance + init-attributes + fun init_instance(mtype: MClassType): RuntimeVariable do + var rt_name = mtype.mclass.rt_name + var res = new_expr("new RTVal({rt_name}.get{rt_name}())", mtype) + generate_init_attr(self, res, mtype) + return res + end + + # Generate code that initialize the attributes on a new instance + fun generate_init_attr(v: JavaCompilerVisitor, recv: RuntimeVariable, mtype: MClassType) do + var cds = mtype.collect_mclassdefs(v.compiler.mainmodule).to_a + v.compiler.mainmodule.linearize_mclassdefs(cds) + for cd in cds do + for npropdef in v.compiler.modelbuilder.collect_attr_propdef(cd) do + npropdef.init_expr(v, recv) + end + end + end + + # Generate a Nit "is" for two runtime_variables + fun equal_test(value1, value2: RuntimeVariable): RuntimeVariable do + var res = new_var(compiler.mainmodule.bool_type) + if value2.mtype.is_java_primitive and not value1.mtype.is_java_primitive then + var tmp = value1 + value1 = value2 + value2 = tmp + end + if value1.mtype.is_java_primitive then + if value2.mtype == value1.mtype then + add("{res} = {value1} == {value2}; /* == with two primitives */") + else if value2.mtype.is_java_primitive then + add("{res} = true; /* incompatible types {value1.mtype} vs. {value2.mtype}*/") + # else if value1.mtype.is_tagged then + # add("{res} = ({value2} != NULL) && ({autobox(value2, value1.mtype)} == {value1});") + else + var rt_name = value1.mtype.as(MClassType).mclass.rt_name + add("{res} = ({value2} != null) && ({value2}.rtclass == {rt_name}.get{rt_name}());") + add("if ({res}) \{") + add("{res} = ({self.autobox(value2, value1.mtype)} == {value1});") + add("\}") + end + return res + end + var maybe_null = true + var test = new Array[String] + var t1 = value1.mcasttype + if t1 isa MNullableType then + test.add("{value1} != null && !{value1}.is_null()") + t1 = t1.mtype + else + maybe_null = false + end + var t2 = value2.mcasttype + if t2 isa MNullableType then + test.add("{value2} != null && !{value2}.is_null()") + t2 = t2.mtype + else + maybe_null = false + end + + var incompatible = false + var primitive + if t1.is_java_primitive then + primitive = t1 + if t1 == t2 then + # No need to compare class + else if t2.is_java_primitive then + incompatible = true + else if can_be_primitive(value2) then + if t1.is_java_primitive then + self.add("{res} = {value1} == {value2}; /* t1 is primitive and t2 can be */") + return res + end + # if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then + # test.add("(!{extract_tag(value2)})") + # end + test.add("{value1}.rtclass == {value2}.rtclass") + else + incompatible = true + end + else if t2.is_java_primitive then + primitive = t2 + if can_be_primitive(value1) then + if t2.is_java_primitive then + self.add("{res} = {value1} == {value2}; /* t2 is primitive and t1 can be */") + return res + end + test.add("{value1}.rtclass == {value2}.rtclass") + else + incompatible = true + end + else + primitive = null + end + + if incompatible then + if maybe_null then + self.add("{res} = {value1} == {value2}; /* incompatible types {t1} vs. {t2}; but may be NULL*/") + return res + else + self.add("{res} = false; /* incompatible types {t1} vs. {t2}; cannot be NULL */") + return res + end + end + if primitive != null then + if primitive.is_java_primitive then + self.add("{res} = {value1} == {value2};") + return res + end + test.add("({value1}.value == {value2}.value") + else if can_be_primitive(value1) and can_be_primitive(value2) then + test.add("{value1}.rtclass == {value2}.rtclass") + var s = new Array[String] + for b in compiler.box_kinds do + var rt_name = b.mclass.rt_name + s.add "({value1}.rtclass == {rt_name}.get{rt_name}()) && ({value1}.value.equals({value2}.value))" + if b.mclass.name == "Float" then + s.add "({value1}.rtclass == RTClass_kernel_Float.getRTClass_kernel_Float() && {value1}.rtclass == {value2}.rtclass && Math.abs((double)({value1}.value)) == 0.0 && Math.abs((double)({value2}.value)) == 0.0)" + end + end + if s.is_empty then + self.add("{res} = {value1} == {value2}; /* both can be primitive */") + return res + end + test.add("({s.join(" || ")})") + else + self.add("{res} = {value1} == {value2}; /* no primitives */") + return res + end + self.add("{res} = {value1} == {value2} || ({test.join(" && ")});") + return res + end + + # Attributes + + # Generate a polymorphic attribute is_set test + fun isset_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do + # TODO self.check_recv_notnull(recv) + var res = new_var(compiler.mainmodule.bool_type) + + # What is the declared type of the attribute? + var mtype = a.intro.static_mtype.as(not null) + var intromclassdef = a.intro.mclassdef + mtype = mtype.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true) + + if mtype isa MNullableType then + add("{res} = true; /* easy isset: {a} on {recv.inspect} */") + return res + end + add("{res} = {recv}.attrs.get(\"{a.jname}\") != null; /* {a} on {recv.inspect} */") + return res + end + + # Generate a polymorphic attribute read + fun read_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do + # TODO check_recv_notnull(recv) + # TODO compile_check(v) + # What is the declared type of the attribute? + var ret = a.intro.static_mtype.as(not null) + var intromclassdef = a.intro.mclassdef + ret = ret.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true) + + # Check for Uninitialized attribute + if not ret isa MNullableType then check_attribute(a, recv) + + return new_expr("{recv}.attrs.get(\"{a.jname}\")", ret) + end + + # Generate a polymorphic attribute write + fun write_attribute(a: MAttribute, recv: RuntimeVariable, value: RuntimeVariable) do + # TODO check_recv_notnull(recv) + add "{recv}.attrs.put(\"{a.jname}\", {autobox(value, compiler.mainmodule.object_type)});" + end + + # Check uninitialized attribute + fun check_attribute(a: MAttribute, recv: RuntimeVariable) do + add "if({recv}.attrs.get(\"{a.jname}\") == null) \{" + add_abort "Uninitialized attribute {a.name}" + add "\}" + end + + # Utils + + # Display a info message + fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0) end # A file containing Java code. @@ -214,3 +1144,1164 @@ redef class MEntity # A Java compatible name for `self` private fun jname: String do return name.to_cmangle end + +# Handler for runtime classes generation +# +# We need 3 kinds of runtime structures: +# * `RTClass` to represent a global class +# * `RTMethod` to represent a method definition +# * `RTVal` to represent runtime variables +class JavaRuntimeModel + + # Compile JavaRuntimeModel structures + fun compile_rtmodel(compiler: JavaCompiler) do + compile_rtclass(compiler) + compile_rtmethod(compiler) + compile_rtval(compiler) + end + + # Compile the abstract runtime class structure + # + # Runtime classes have 3 attributes: + # * `class_name`: the class name as a String + # * `vft`: the virtual function table for the class (flattened) + # * `supers`: the super type table (used for type tests) + fun compile_rtclass(compiler: JavaCompiler) do + var v = compiler.new_visitor("RTClass.java") + v.add("import java.util.HashMap;") + v.add("public abstract class RTClass \{") + v.add(" public String class_name;") + v.add(" public HashMap vft = new HashMap<>();") + v.add(" public HashMap supers = new HashMap<>();") + v.add(" protected RTClass() \{\}") + v.add("\}") + end + + # Compile the abstract runtime method structure + # + # Method body is executed through the `exec` method: + # * `exec` always take an array of RTVal as arg, the first one must be the receiver + # * `exec` always returns a RTVal (or null if the Nit return type is void) + fun compile_rtmethod(compiler: JavaCompiler) do + var v = compiler.new_visitor("RTMethod.java") + v.add("public abstract class RTMethod \{") + v.add(" protected RTMethod() \{\}") + v.add(" public abstract RTVal exec(RTVal[] args);") + v.add("\}") + end + + # Compile the runtime value structure + # + # RTVal both represents object instances and primitives values: + # * object instances: + # * `rtclass` the class of the RTVal is instance of + # * `attrs` contains the attributes of the instance + # * primitive values: + # * `rtclass` represents the class of the primitive value Nit type + # * `value` contains the primitive value of the instance + # * null values: + # * they must have both `rtclass` and `value` as null + fun compile_rtval(compiler: JavaCompiler) do + var v = compiler.new_visitor("RTVal.java") + v.add("import java.util.HashMap;") + v.add("public class RTVal \{") + v.add(" public RTClass rtclass;") + v.add(" public HashMap attrs = new HashMap<>();") + v.add(" Object value;") + v.add(" public RTVal(RTClass rtclass) \{") + v.add(" this.rtclass = rtclass;") + v.add(" \}") + v.add(" public RTVal(RTClass rtclass, Object value) \{") + v.add(" this.rtclass = rtclass;") + v.add(" this.value = value;") + v.add(" \}") + v.add(" public boolean is_null() \{ return rtclass == null && value == null; \}") + v.add("\}") + end +end + +# A runtime variable hold a runtime value in Java. +# Runtime variables are associated to Nit local variables and intermediate results in Nit expressions. +class RuntimeVariable + + # The name of the variable in the Java code + var name: String + + # The static type of the variable (as declard in Java) + var mtype: MType + + # The current casted type of the variable (as known in Nit) + var mcasttype: MType is writable + + # If the variable exaclty a mcasttype? + # false (usual value) means that the variable is a mcasttype or a subtype. + var is_exact: Bool = false is writable + + # Is this variable declared as a RTVal or a Java primitive one? + var is_boxed = false + + redef fun to_s do return name + + redef fun inspect + do + var exact_str + if self.is_exact then + exact_str = " exact" + else + exact_str = "" + end + var type_str + if self.mtype == self.mcasttype then + type_str = "{mtype}{exact_str}" + else + type_str = "{mtype}({mcasttype}{exact_str})" + end + return "<{name}:{type_str}>" + end +end + +# The static context of a visited property in a `JavaCompilerVisitor` +class JavaStaticFrame + # The associated visitor + var visitor: JavaCompilerVisitor + + # The executed property. + # A Method in case of a call, an attribute in case of a default initialization. + var mpropdef: MPropDef + + # The static type of the receiver + var receiver: MClassType + + # Arguments of the method (the first is the receiver) + var arguments: Array[RuntimeVariable] + + # The runtime_variable associated to the return (in a function) + var returnvar: nullable RuntimeVariable = null is writable + + # The label at the end of the property + var returnlabel: nullable String = null is writable + + # Labels associated to a each escapemarks. + # Because of inlinings, escape-marks must be associated to their context (the frame) + private var escapemark_names = new HashMap[EscapeMark, String] +end + +redef class Location + # Return a shortened version of the location with `"{file}:{line_start}"` + fun short_location: String do + var file = self.file + if file == null then return ":{line_start}" + return "{file.filename.escape_to_c}:{line_start}" + end +end + +redef class MType + # Return the Java type associated to a given Nit static type + fun java_type: String do return "RTVal" + + # Is the associated Java type a primitive one? + # + # ENSURE `result == (java_type != "Object")` + var is_java_primitive: Bool is lazy do return java_type != "RTVal" +end + +redef class MClassType + + redef var java_type is lazy do + if mclass.name == "Int" then + return "int" + else if mclass.name == "Bool" then + return "boolean" + else if mclass.name == "Char" then + return "char" + else if mclass.name == "Float" then + return "double" + else if mclass.name == "Byte" then + return "byte" + else if mclass.name == "NativeString" then + return "String" + else if mclass.name == "NativeArray" then + return "Array" + end + return "RTVal" + end +end + +redef class MClass + + # Runtime name + 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 + v.add("public class {rt_name} extends RTClass \{") + v.add(" protected static RTClass instance;") + v.add(" private {rt_name}() \{") + v.add(" this.class_name = \"{name}\";") + compile_vft(v) + compile_type_table(v) + v.add(" \}") + v.add(" public static RTClass get{rt_name}() \{") + v.add(" if(instance == null) \{") + v.add(" instance = new {rt_name}();") + v.add(" \}") + v.add(" return instance;") + v.add(" \}") + v.add("\}") + end + + # Compile the virtual function table for the mclass + private fun compile_vft(v: JavaCompilerVisitor) do + # TODO handle generics + if mclass_type.need_anchor then return + var mclassdefs = mclass_type.collect_mclassdefs(v.compiler.mainmodule).to_a + v.compiler.mainmodule.linearize_mclassdefs(mclassdefs) + + var mainmodule = v.compiler.mainmodule + for mclassdef in mclassdefs.reversed do + for mprop in mclassdef.intro_mproperties do + var mpropdef = mprop.lookup_first_definition(mainmodule, intro.bound_mtype) + if not mpropdef isa MMethodDef then continue + var rt_name = mpropdef.rt_name + v.add("this.vft.put(\"{mprop.full_name}\", {rt_name}.get{rt_name}());") + + # fill super next definitions + while mpropdef.has_supercall do + var prefix = mpropdef.full_name + mpropdef = mpropdef.lookup_next_definition(mainmodule, intro.bound_mtype) + rt_name = mpropdef.rt_name + v.add("this.vft.put(\"{prefix}\", {rt_name}.get{rt_name}());") + end + end + end + end + + # Compile the type table for the MClass + fun compile_type_table(v: JavaCompilerVisitor) do + for pclass in in_hierarchy(v.compiler.mainmodule).greaters do + if pclass == self then + v.add("supers.put(\"{pclass.jname}\", this);") + else + v.add("supers.put(\"{pclass.jname}\", {pclass.rt_name}.get{pclass.rt_name}());") + end + end + end +end + +# Used as a common type between MMethod and MMethodDef for `table_send` +private interface TableCallable +end + +redef class MMethod + super TableCallable +end + +redef class MMethodDef + super TableCallable + + # Runtime name + private fun rt_name: String do + return "RTMethod_{mclassdef.mmodule.jname}_{mclassdef.mclass.jname}_{mproperty.jname}" + end + + # Generate a Java RTMethod for `self` + fun compile_to_java(v: JavaCompilerVisitor) do + v.add("public class {rt_name} extends RTMethod \{") + v.add(" protected static RTMethod instance;") + v.add(" public static RTMethod get{rt_name}() \{") + v.add(" if(instance == null) \{") + v.add(" instance = new {rt_name}();") + v.add(" \}") + v.add(" return instance;") + v.add(" \}") + v.add(" @Override") + v.add(" public RTVal exec(RTVal[] args) \{") + compile_inside_to_java(v) + v.add(" \}") + v.add("\}") + end + + # Compile the body of this function + fun compile_inside_to_java(v: JavaCompilerVisitor) do + + 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, arguments) + else if node isa AClassdef then + 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, arguments: Array[RuntimeVariable]) do + if mpropdef == self.mfree_init then + assert mpropdef.mproperty.is_root_init + if not mpropdef.is_intro then + v.supercall(mpropdef, arguments.first.mtype.as(MClassType), arguments) + end + else + abort + end + end +end + +redef class APropdef + + # Compile that property definition to java code + fun compile_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable]) do + v.info("NOT YET IMPLEMENTED {class_name}::compile_to_java") + end +end + +redef class AMethPropdef + redef fun compile_to_java(v, mpropdef, arguments) do + if mpropdef.msignature != null then + var i = 0 + 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) + v.assign(argvar, v.new_expr("args[{i + 1}]", v.compiler.mainmodule.object_type)) + arguments.add(argvar) + i += 1 + end + end + + # Call the implicit super-init + var auto_super_inits = self.auto_super_inits + if auto_super_inits != null then + var args = [arguments.first] + for auto_super_init in auto_super_inits do + assert auto_super_init.mproperty != mpropdef.mproperty + args.clear + for i in [0..auto_super_init.msignature.arity+1[ do + args.add(arguments[i]) + end + assert auto_super_init.mproperty != mpropdef.mproperty + v.compile_callsite(auto_super_init, args) + end + end + if auto_super_call then + v.supercall(mpropdef, arguments.first.mtype.as(MClassType), arguments) + end + + 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, arguments: Array[RuntimeVariable]) do + # Compile intern methods + if mpropdef.is_intern then + if compile_intern_to_java(v, mpropdef, arguments) then return + v.info("NOT YET IMPLEMENTED compile_intern for {mpropdef}") + v.ret(v.null_instance) + return + end + + # Compile block if any + var n_block = n_block + if n_block != null then + v.stmt(n_block) + return + end + end + + # Compile an intern method using Java primitives + fun compile_intern_to_java(v: JavaCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable]): Bool do + var pname = mpropdef.mproperty.name + var cname = mpropdef.mclassdef.mclass.name + var ret = mpropdef.msignature.as(not null).return_mtype + if cname == "Int" then + if pname == "output" then + v.add("System.out.println({arguments[0]});") + v.ret(v.null_instance) + return true + else if pname == "object_id" then + v.ret(arguments.first) + return true + else if pname == "+" then + v.ret(v.new_expr("{arguments[0]} + {arguments[1]}", ret.as(not null))) + return true + else if pname == "-" then + v.ret(v.new_expr("{arguments[0]} - {arguments[1]}", ret.as(not null))) + return true + else if pname == "unary -" then + v.ret(v.new_expr("-{arguments[0]}", ret.as(not null))) + return true + else if pname == "unary +" then + v.ret(arguments[0]) + return true + else if pname == "*" then + v.ret(v.new_expr("{arguments[0]} * {arguments[1]}", ret.as(not null))) + return true + else if pname == "/" then + v.ret(v.new_expr("{arguments[0]} / {arguments[1]}", ret.as(not null))) + return true + else if pname == "%" then + v.ret(v.new_expr("{arguments[0]} % {arguments[1]}", ret.as(not null))) + return true + else if pname == "<<" then + v.ret(v.new_expr("{arguments[0]} << {arguments[1]}", ret.as(not null))) + return true + else if pname == ">>" then + v.ret(v.new_expr("{arguments[0]} >> {arguments[1]}", ret.as(not null))) + return true + else if pname == "==" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "!=" then + var res = v.equal_test(arguments[0], arguments[1]) + v.ret(v.new_expr("!{res}", ret.as(not null))) + return true + else if pname == "<" then + v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null))) + return true + else if pname == ">" then + v.ret(v.new_expr("{arguments[0]} > {arguments[1]}", ret.as(not null))) + return true + else if pname == "<=" then + v.ret(v.new_expr("{arguments[0]} <= {arguments[1]}", ret.as(not null))) + return true + else if pname == ">=" then + v.ret(v.new_expr("{arguments[0]} >= {arguments[1]}", ret.as(not null))) + return true + else if pname == "to_f" then + v.ret(v.new_expr("(double){arguments[0]}", ret.as(not null))) + return true + else if pname == "to_b" then + v.ret(v.new_expr("(byte){arguments[0]}", ret.as(not null))) + return true + else if pname == "ascii" then + v.ret(v.new_expr("(char){arguments[0]}", ret.as(not null))) + return true + end + else if cname == "Char" then + if pname == "output" then + v.add("System.out.print({arguments[0]});") + v.ret(v.null_instance) + return true + else if pname == "object_id" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "successor" then + v.ret(v.new_expr("(char)({arguments[0]} + {arguments[1]})", ret.as(not null))) + return true + else if pname == "predecessor" then + v.ret(v.new_expr("(char)({arguments[0]} - {arguments[1]})", ret.as(not null))) + return true + else if pname == "==" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "!=" then + var res = v.equal_test(arguments[0], arguments[1]) + v.ret(v.new_expr("!{res}", ret.as(not null))) + return true + else if pname == "<" then + v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null))) + return true + else if pname == ">" then + v.ret(v.new_expr("{arguments[0]} > {arguments[1]}", ret.as(not null))) + return true + else if pname == "<=" then + v.ret(v.new_expr("{arguments[0]} <= {arguments[1]}", ret.as(not null))) + return true + else if pname == ">=" then + v.ret(v.new_expr("{arguments[0]} >= {arguments[1]}", ret.as(not null))) + return true + else if pname == "to_i" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "ascii" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + end + else if cname == "Byte" then + if pname == "output" then + v.add("System.out.println({arguments[0]});") + v.ret(v.null_instance) + return true + else if pname == "object_id" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "+" then + v.ret(v.new_expr("(byte)({arguments[0]} + {arguments[1]})", ret.as(not null))) + return true + else if pname == "-" then + v.ret(v.new_expr("(byte)({arguments[0]} - {arguments[1]})", ret.as(not null))) + return true + else if pname == "unary -" then + v.ret(v.new_expr("(byte)(-{arguments[0]})", ret.as(not null))) + return true + else if pname == "unary +" then + v.ret(arguments[0]) + return true + else if pname == "*" then + v.ret(v.new_expr("(byte)({arguments[0]} * {arguments[1]})", ret.as(not null))) + return true + else if pname == "/" then + v.ret(v.new_expr("(byte)({arguments[0]} / {arguments[1]})", ret.as(not null))) + return true + else if pname == "%" then + v.ret(v.new_expr("(byte)({arguments[0]} % {arguments[1]})", ret.as(not null))) + return true + else if pname == "<<" then + v.ret(v.new_expr("(byte)({arguments[0]} << {arguments[1]})", ret.as(not null))) + return true + else if pname == ">>" then + v.ret(v.new_expr("(byte)({arguments[0]} >> {arguments[1]})", ret.as(not null))) + return true + else if pname == "==" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "!=" then + var res = v.equal_test(arguments[0], arguments[1]) + v.ret(v.new_expr("!{res}", ret.as(not null))) + return true + else if pname == "<" then + v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null))) + return true + else if pname == ">" then + v.ret(v.new_expr("{arguments[0]} > {arguments[1]}", ret.as(not null))) + return true + else if pname == "<=" then + v.ret(v.new_expr("{arguments[0]} <= {arguments[1]}", ret.as(not null))) + return true + else if pname == ">=" then + v.ret(v.new_expr("{arguments[0]} >= {arguments[1]}", ret.as(not null))) + return true + else if pname == "to_i" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "to_f" then + v.ret(v.new_expr("(double){arguments[0]}", ret.as(not null))) + return true + else if pname == "ascii" then + v.ret(v.new_expr("{arguments[0]}", ret.as(not null))) + return true + end + else if cname == "Bool" then + if pname == "output" then + v.add("System.out.println({arguments[0]});") + v.ret(v.null_instance) + return true + else if pname == "object_id" then + v.ret(v.new_expr("{arguments[0]}?1:0", ret.as(not null))) + return true + else if pname == "==" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "!=" then + var res = v.equal_test(arguments[0], arguments[1]) + v.ret(v.new_expr("!{res}", ret.as(not null))) + return true + end + else if cname == "Float" then + if pname == "output" then + v.add "if({arguments[0]} == Double.POSITIVE_INFINITY) \{" + v.add "System.out.println(\"inf\");" + v.add "\} else if({arguments[0]} == Double.POSITIVE_INFINITY) \{" + v.add "System.out.println(\"-inf\");" + v.add "\} else \{" + var df = v.get_name("df") + v.add "java.text.DecimalFormat {df} = new java.text.DecimalFormat(\"0.000000\");" + v.add "System.out.println({df}.format({arguments[0]}));" + v.add "\}" + v.ret(v.null_instance) + return true + else if pname == "object_id" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "+" then + v.ret(v.new_expr("{arguments[0]} + {arguments[1]}", ret.as(not null))) + return true + else if pname == "-" then + v.ret(v.new_expr("{arguments[0]} - {arguments[1]}", ret.as(not null))) + return true + else if pname == "unary -" then + v.ret(v.new_expr("-{arguments[0]}", ret.as(not null))) + return true + else if pname == "unary +" then + v.ret(arguments[0]) + return true + else if pname == "succ" then + v.ret(v.new_expr("{arguments[0]} + 1", ret.as(not null))) + return true + else if pname == "prec" then + v.ret(v.new_expr("{arguments[0]} - 1", ret.as(not null))) + return true + else if pname == "*" then + v.ret(v.new_expr("{arguments[0]} * {arguments[1]}", ret.as(not null))) + return true + else if pname == "/" then + v.ret(v.new_expr("{arguments[0]} / {arguments[1]}", ret.as(not null))) + return true + else if pname == "==" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "!=" then + var res = v.equal_test(arguments[0], arguments[1]) + v.ret(v.new_expr("!{res}", ret.as(not null))) + return true + else if pname == "<" then + v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null))) + return true + else if pname == ">" then + v.ret(v.new_expr("{arguments[0]} > {arguments[1]}", ret.as(not null))) + return true + else if pname == "<=" then + v.ret(v.new_expr("{arguments[0]} <= {arguments[1]}", ret.as(not null))) + return true + else if pname == ">=" then + v.ret(v.new_expr("{arguments[0]} >= {arguments[1]}", ret.as(not null))) + return true + else if pname == "to_i" then + v.ret(v.new_expr("(int){arguments[0]}", ret.as(not null))) + return true + else if pname == "to_b" then + v.ret(v.new_expr("(byte){arguments[0]}", ret.as(not null))) + return true + end + end + if pname == "exit" then + v.add("System.exit({arguments[1]});") + v.ret(v.null_instance) + return true + else if pname == "sys" then + # TODO singleton + var main_type = v.compiler.mainmodule.sys_type.as(not null) + var sys = main_type.mclass + v.ret(v.new_expr("new RTVal({sys.rt_name}.get{sys.rt_name}())", main_type)) + return true + else if pname == "object_id" then + v.ret(v.new_expr("{arguments[0]}.hashCode()", ret.as(not null))) + return true + else if pname == "is_same_type" then + v.ret(v.is_same_type_test(arguments[0], arguments[1])) + return true + else if pname == "is_same_instance" then + v.ret(v.equal_test(arguments[0], arguments[1])) + return true + else if pname == "output_class_name" then + v.add("System.out.println({arguments[0]}.rtclass.class_name);") + v.ret(v.null_instance) + return true + end + return false + end +end + +redef class AAttrPropdef + redef fun compile_to_java(v, mpropdef, arguments) do + v.current_node = self + if mpropdef == mreadpropdef then + compile_getter(v, mpropdef, arguments) + else if mpropdef == mwritepropdef then + compile_setter(v, mpropdef, arguments) + else + abort + end + v.current_node = null + end + + # Compile the setter method + private fun compile_setter(v: JavaCompilerVisitor, mpropdef: MPropDef, arguments: Array[RuntimeVariable]) do + var mtype = v.compiler.mainmodule.object_type + var recv = arguments.first + var val = v.new_expr("args[1]", mtype) + v.write_attribute(self.mpropdef.as(not null).mproperty, recv, val) + v.ret v.null_instance + end + + # Compile the getter method + private fun compile_getter(v: JavaCompilerVisitor, mpropdef: MPropDef, arguments: Array[RuntimeVariable]) do + var recv = arguments.first + v.ret v.read_attribute(self.mpropdef.as(not null).mproperty, recv) + end + + private fun init_expr(v: JavaCompilerVisitor, recv: RuntimeVariable) do + if has_value and not is_lazy and not n_expr isa ANullExpr then evaluate_expr(v, recv) + end + + # Evaluate, store and return the default value of the attribute + private fun evaluate_expr(v: JavaCompilerVisitor, recv: RuntimeVariable): RuntimeVariable do + var old = v.frame + var frame = new JavaStaticFrame(v, self.mreadpropdef.as(not null), recv.mcasttype.undecorate.as(MClassType), [recv]) + v.frame = frame + + var value + var mtype = self.mtype + assert mtype != null + + var nexpr = self.n_expr + var nblock = self.n_block + if nexpr != null then + value = v.expr(nexpr, mtype) + else if nblock != null then + value = v.new_var(mtype) + frame.returnvar = value + frame.returnlabel = v.get_name("RET_LABEL") + v.add("{frame.returnlabel.as(not null)}: \{") + v.stmt(nblock) + v.add("\}") + else + abort + end + + v.write_attribute(self.mpropdef.as(not null).mproperty, recv, value) + v.frame = old + return value + end +end + +redef class AExpr + # Try to compile self as an expression + # Do not call this method directly, use `v.expr` instead + private fun expr(v: JavaCompilerVisitor): nullable RuntimeVariable do + v.info("NOT YET IMPLEMENTED {class_name}::expr") + return null + end + + # Try to compile self as a statement + # Do not call this method directly, use `v.stmt` instead + private fun stmt(v: JavaCompilerVisitor) do expr(v) +end + +redef class ABlockExpr + redef fun stmt(v) + do + for e in self.n_expr do v.stmt(e) + end + redef fun expr(v) + do + var last = self.n_expr.last + for e in self.n_expr do + if e == last then break + v.stmt(e) + end + return v.expr(last, null) + end +end + +redef class ASendExpr + redef fun expr(v) do + var recv = v.expr(n_expr, null) + var callsite = callsite.as(not null) + var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, raw_arguments) + return v.compile_callsite(callsite, args) + end +end + +redef class ANewExpr + redef fun expr(v) + do + var mtype = self.recvtype + assert mtype != null + + if mtype.mclass.name == "NativeArray" then + # TODO handle native arrays + v.info("NOT YET IMPLEMENTED new NativeArray") + end + + var recv = v.init_instance(mtype) + + var callsite = self.callsite + if callsite == null then return recv + + var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs) + var res2 = v.compile_callsite(callsite, args) + if res2 != null then + return res2 + end + return recv + end +end + +redef class ASuperExpr + redef fun expr(v) + do + var frame = v.frame + assert frame != null + var recv = frame.arguments.first + + var callsite = self.callsite + if callsite != null then + var args + + if self.n_args.n_exprs.is_empty then + # Add automatic arguments for the super init call + args = [recv] + for i in [0..callsite.msignature.arity[ do + args.add(frame.arguments[i+1]) + end + else + args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs) + end + + # Super init call + var res = v.compile_callsite(callsite, args) + return res + end + + var mpropdef = self.mpropdef.as(not null) + + var args + if self.n_args.n_exprs.is_empty then + args = frame.arguments + else + args = v.varargize(mpropdef, signaturemap, recv, self.n_args.n_exprs) + end + + # Standard call-next-method + return v.supercall(mpropdef, recv.mtype.as(MClassType), args) + end +end + +redef class ASelfExpr + redef fun expr(v) do return v.frame.as(not null).arguments.first +end + +redef class AImplicitSelfExpr + redef fun expr(v) do return v.frame.as(not null).arguments.first +end + +redef class AAttrExpr + redef fun expr(v) do + var recv = v.expr(self.n_expr, null) + var mproperty = self.mproperty.as(not null) + return v.read_attribute(mproperty, recv) + end +end + +redef class AAttrAssignExpr + redef fun expr(v) do + var recv = v.expr(self.n_expr, null) + var i = v.expr(self.n_value, null) + var mproperty = self.mproperty.as(not null) + v.write_attribute(mproperty, recv, i) + return i + end +end + +redef class AAttrReassignExpr + redef fun stmt(v) do + var recv = v.expr(self.n_expr, null) + var value = v.expr(self.n_value, null) + var mproperty = self.mproperty.as(not null) + var attr = v.read_attribute(mproperty, recv) + var res = v.compile_callsite(self.reassign_callsite.as(not null), [attr, value]) + assert res != null + v.write_attribute(mproperty, recv, res) + end +end + +redef class AIssetAttrExpr + redef fun expr(v) do + var recv = v.expr(self.n_expr, null) + var mproperty = self.mproperty.as(not null) + return v.isset_attribute(mproperty, recv) + end +end + +redef class AReturnExpr + redef fun stmt(v) do + var nexpr = self.n_expr + var frame = v.frame + assert frame != null + if nexpr != null then + v.ret(v.expr(nexpr, frame.returnvar.as(not null).mtype)) + else + v.ret(v.null_instance) + end + end +end + +redef class AIfExpr + redef fun stmt(v) do + var cond = v.expr_bool(self.n_expr) + v.add("if ({cond})\{") + v.stmt(self.n_then) + v.add("\} else \{") + v.stmt(self.n_else) + v.add("\}") + end + + redef fun expr(v) do + var res = v.new_var(self.mtype.as(not null)) + var cond = v.expr_bool(self.n_expr) + v.add("if ({cond})\{") + v.assign(res, v.expr(self.n_then.as(not null), null)) + v.add("\} else \{") + v.assign(res, v.expr(self.n_else.as(not null), null)) + v.add("\}") + return res + end +end + +redef class ADoExpr + redef fun stmt(v) + do + v.add_escape_label(break_mark) + v.add "\{" + v.stmt(self.n_block) + v.add "\}" + end +end + +redef class AWhileExpr + redef fun stmt(v) + do + v.add_escape_label(break_mark) + v.add_escape_label(continue_mark) + v.add("for(;;) \{") + var cond = v.expr_bool(self.n_expr) + v.add("if (!{cond}) break;") + v.stmt(self.n_block) + v.add("\}") + end +end + +redef class ALoopExpr + redef fun stmt(v) + do + v.add_escape_label(break_mark) + v.add_escape_label(continue_mark) + v.add("for(;;) \{") + v.stmt(self.n_block) + v.add("\}") + end +end + +redef class AEscapeExpr + redef fun stmt(v) do v.add("break BREAK_{v.escapemark_name(escapemark)};") +end + +redef class AVardeclExpr + redef fun stmt(v) do + var variable = self.variable.as(not null) + var ne = self.n_expr + var decl = v.variable(variable) + if ne != null then + var i = v.expr(ne, variable.declared_type) + v.assign(decl, i) + end + end +end + +redef class AVarExpr + redef fun expr(v) do + return v.variable(self.variable.as(not null)) + end +end + +redef class AVarAssignExpr + redef fun expr(v) do + var variable = self.variable.as(not null) + var i = v.expr(self.n_value, variable.declared_type) + v.assign(v.variable(variable), i) + return i + end +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) + return v.new_expr("!{cond}", self.mtype.as(not null)) + end +end + +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 + redef fun expr(v) do return v.float_instance("{self.n_float.text}") # FIXME use value, not n_float +end + +redef class ACharExpr + redef fun expr(v) do return v.char_instance(self.value.as(not null)) +end + +redef class ATrueExpr + redef fun expr(v) do return v.bool_instance(true) +end + +redef class AFalseExpr + redef fun expr(v) do return v.bool_instance(false) +end + +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 + var i = v.expr(self.n_expr, null) + var cast_type = self.cast_type + if cast_type == null then return null # no-no on broken node + return v.type_test(i, cast_type) + end +end + +redef class AParExpr + redef fun expr(v) do return v.expr(self.n_expr, null) +end + +redef class AAbortExpr + redef fun stmt(v) do v.add_abort("Aborted") +end + +redef class ADebugTypeExpr + redef fun stmt(v) do end # do nothing +end