# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Compile Nit code to Java code # # 3 runtime structures are used to represent Nit instance in Java generated code: # * `RTClass` to represent a class, it's super-type table and its VFT # * `RTMethod` to reprensent a compiled method definition # * `RTVal` to reprensent a Nit instance, the null value or a native value # # More details are given in the documentation of these 3 classes. # # TODO Factorize with `abstract_compiler` module java_compiler import rapid_type_analysis import transform import frontend redef class ToolContext # Where to output the generated binary var opt_output = new OptionString("Output file", "-o", "--output") # 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, opt_ant) end end redef class ModelBuilder # Start the Java compiler fun run_java_compiler(mainmodule: MModule, runtime_type_analysis: RapidTypeAnalysis) do var time0 = get_time toolcontext.info("*** GENERATING JAVA ***", 1) var compiler = new JavaCompiler(mainmodule, self, runtime_type_analysis) compiler.do_compilation var time1 = get_time toolcontext.info("*** END GENERATING JAVA: {time1-time0} ***", 2) write_and_make(compiler) end # Write Java code and compile it into an executable jar fun write_and_make(compiler: JavaCompiler) do var time0 = get_time toolcontext.info("*** WRITING JAVA ***", 1) compiler.compile_dir.mkdir var jfiles = write_java_files(compiler) var time1 = get_time toolcontext.info("*** END WRITING JAVA: {time1-time0} ***", 2) time0 = time1 toolcontext.info("*** COMPILING JAVA ***", 1) 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 toolcontext.info("*** END COMPILING JAVA: {time1-time0} ***", 2) end # Write files managed by `compiler` into concrete files fun write_java_files(compiler: JavaCompiler): Array[String] do var jfiles = new Array[String] for f in compiler.files do var filepath = "{compiler.compile_dir}/{f.filename}" var file = cache_file(filepath) for line in f.lines do file.write(line) 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) write_makefile(compiler, jfiles) var compile_dir = compiler.compile_dir var outname = compiler.outname.to_path.filename toolcontext.info("make -N -C {compile_dir} -f {outname}.mk", 2) var res if toolcontext.verbose_level >= 3 then res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1") else res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1 > /dev/null") end 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 var ofiles = new List[String] for f in jfiles do ofiles.add(f.strip_extension(".java") + ".class") var compile_dir = compiler.compile_dir var outname = compiler.outname.to_path.filename var outpath = (sys.getcwd / compiler.outname).simplify_path var makename = "{compile_dir}/{outname}.mk" var makefile = new FileWriter.open(makename) makefile.write("JC = javac\n") makefile.write("JAR = jar\n\n") makefile.write("all: {outpath}.jar\n\n") makefile.write("{outpath}.jar: {compiler.mainmodule.jname}_Main.class\n") makefile.write("\t$(JAR) cfm {outpath}.jar {outname}.mf {ofiles.join(" ")}\n\n") makefile.write("{compiler.mainmodule.jname}_Main.class:\n") makefile.write("\t$(JC) {jfiles.join(" ")}\n\n") makefile.write("clean:\n") makefile.write("\trm {ofiles.join(" ")} 2>/dev/null\n\n") makefile.close 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 var outname = compiler.outname.to_path.filename var maniffile = new FileWriter.open("{compile_dir}/{outname}.mf") maniffile.write("Manifest-Version: 1.0\n") maniffile.write("Main-Class: {compiler.mainmodule.jname}_Main\n") maniffile.close end # Write a simple bash script that runs the jar like it was a binary generated by nitc private fun write_shell_script(compiler: JavaCompiler) do var outname = compiler.outname var shfile = new FileWriter.open(outname) shfile.write("#!/bin/bash\n") shfile.write("java -jar {outname}.jar \"$@\"\n") shfile.close sys.system("chmod +x {outname}") end end # Compiler that translates Nit code to Java code class JavaCompiler # The main module of the program currently compiled var mainmodule: MModule # Modelbuilder used to know the model and the AST var modelbuilder: ModelBuilder # The result of the RTA (used to know live types and methods) var runtime_type_analysis: RapidTypeAnalysis # Where to generate tmp files var compile_dir: String is lazy do var dir = modelbuilder.toolcontext.opt_compile_dir.value if dir == null then dir = "nitj_compile" return dir end # Name of the generated executable var outname: String is lazy do var name = modelbuilder.toolcontext.opt_output.value if name == null then name = mainmodule.jname return name end # The list of all associated files # Used to generate .java files var files: Array[JavaCodeFile] = new Array[JavaCodeFile] # Force the creation of a new file # The point is to avoid contamination between must-be-compiled-separately files fun new_file(name: String): JavaCodeFile do var file = new JavaCodeFile(name) files.add(file) 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 # 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.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 args[i].vararg_decl > 0 then var vararg = exprs.sub(j, args[i].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. class JavaCodeFile # File name var filename: String # Lines to write var lines: List[String] = new List[String] end 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 != "RTVal")` 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 == "CString" 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.mproperty.is_root_init then 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