nitc :: NaiveInterpreter :: defaultinit
# The visitor that interprets the Nit Program by walking on the AST
class NaiveInterpreter
# The modelbuilder that know the AST and its associations with the model
var modelbuilder: ModelBuilder
# The main module of the program (used to lookup method)
var mainmodule: MModule is writable
# The command line arguments of the interpreted program
# arguments.first is the program name
# arguments[1] is the first argument
var arguments: Array[String]
# The main Sys instance
var mainobj: nullable Instance is noinit
# Name of all supported functional names
var routine_types: Set[String] = new HashSet[String]
init
do
if mainmodule.model.get_mclasses_by_name("Bool") != null then
self.true_instance = new PrimitiveInstance[Bool](mainmodule.bool_type, true)
init_instance_primitive(self.true_instance)
self.false_instance = new PrimitiveInstance[Bool](mainmodule.bool_type, false)
init_instance_primitive(self.false_instance)
end
self.null_instance = new PrimitiveInstance[nullable Object](mainmodule.model.null_type, null)
routine_types.add("RoutineRef")
for name in ["Proc", "Fun", "ProcRef", "FunRef"] do
# 20 is a magic number = upper limit of the arity of each functional class.
# i.e. Proc0, Proc1, ... Proc19
for i in [0..20[ do
routine_types.add("{name}{i}")
end
end
end
# Starts the interpreter on the main module of a program
fun start(mainmodule: MModule) do
var interpreter = self
var sys_type = mainmodule.sys_type
if sys_type == null then return # no class Sys
var mainobj = new MutableInstance(sys_type)
interpreter.mainobj = mainobj
interpreter.init_instance(mainobj)
var initprop = mainmodule.try_get_primitive_method("init", sys_type.mclass)
if initprop != null then
interpreter.send(initprop, [mainobj])
end
var mainprop = mainmodule.try_get_primitive_method("run", sys_type.mclass) or else
mainmodule.try_get_primitive_method("main", sys_type.mclass)
if mainprop != null then
interpreter.send(mainprop, [mainobj])
end
end
# Subtype test in the context of the mainmodule
fun is_subtype(sub, sup: MType): Bool
do
return sub.is_subtype(self.mainmodule, current_receiver_class, sup)
end
# Get a primitive method in the context of the main module
fun force_get_primitive_method(name: String, recv: MType): MMethod
do
assert recv isa MClassType
return self.modelbuilder.force_get_primitive_method(current_node, name, recv.mclass, self.mainmodule)
end
# Is a return, a break or a continue executed?
# Set this mark to skip the evaluation until a labeled statement catch it with `is_escape`
var escapemark: nullable EscapeMark = null
# The count of `catch` blocs that have been encountered and can catch an abort
var catch_count = 0 is writable
# The last error thrown on abort/runtime error where catch_count > 0
var last_error: nullable FatalError = null
# Is a return or a break or a continue executed?
# Use this function to know if you must skip the evaluation of statements
fun is_escaping: Bool do return escapemark != null
# The value associated with the current return/break/continue, if any.
# Set the value when you set a escapemark.
# Read the value when you catch a mark or reach the end of a method
var escapevalue: nullable Instance = null
# If there is a break/continue and is associated with `escapemark`, then return true and clear the mark.
# If there is no break/continue or if `escapemark` is null then return false.
# Use this function to catch a potential break/continue.
fun is_escape(escapemark: nullable EscapeMark): Bool
do
if escapemark != null and self.escapemark == escapemark then
self.escapemark = null
return true
else
return false
end
end
# Evaluate `n` as an expression in the current context.
# Return the value of the expression.
# If `n` cannot be evaluated, then aborts.
fun expr(n: AExpr): nullable Instance
do
var frame = self.frame
var old = frame.current_node
frame.current_node = n
#n.debug("IN Execute expr")
var i = n.expr(self)
if i == null and not self.is_escaping then
n.debug("inconsitance: no value and not escaping.")
end
var implicit_cast_to = n.implicit_cast_to
if i != null and implicit_cast_to != null then
var mtype = self.unanchor_type(implicit_cast_to)
if not self.is_subtype(i.mtype, mtype) then n.fatal(self, "Cast failed. Expected `{implicit_cast_to}`, got `{i.mtype}`")
end
#n.debug("OUT Execute expr: value is {i}")
#if not is_subtype(i.mtype, n.mtype.as(not null)) then n.debug("Expected {n.mtype.as(not null)} got {i}")
frame.current_node = old
return i
end
# Evaluate `n` as a statement in the current context.
# Do nothing if `n` is null.
# If `n` cannot be evaluated, then aborts.
fun stmt(n: nullable AExpr)
do
if n == null then return
if n.comprehension != null then
var comprehension = frame.comprehension.as(not null)
var i = expr(n)
if i != null then comprehension.add(i)
return
end
var frame = self.frame
var old = frame.current_node
frame.current_node = n
n.stmt(self)
frame.current_node = old
end
# Map used to store values of nodes that must be evaluated once in the system (`AOnceExpr`)
var onces: Map[ANode, Instance] = new HashMap[ANode, Instance]
# Return the boolean instance associated with `val`.
fun bool_instance(val: Bool): Instance
do
if val then return self.true_instance else return self.false_instance
end
# Return the integer instance associated with `val`.
fun int_instance(val: Int): Instance
do
var t = mainmodule.int_type
var instance = new PrimitiveInstance[Int](t, val)
init_instance_primitive(instance)
return instance
end
# Return the byte instance associated with `val`.
fun byte_instance(val: Byte): Instance
do
var t = mainmodule.byte_type
var instance = new PrimitiveInstance[Byte](t, val)
init_instance_primitive(instance)
return instance
end
# Return the int8 instance associated with `val`.
fun int8_instance(val: Int8): Instance
do
var t = mainmodule.int8_type
var instance = new PrimitiveInstance[Int8](t, val)
init_instance_primitive(instance)
return instance
end
# Return the int16 instance associated with `val`.
fun int16_instance(val: Int16): Instance
do
var t = mainmodule.int16_type
var instance = new PrimitiveInstance[Int16](t, val)
init_instance_primitive(instance)
return instance
end
# Return the uint16 instance associated with `val`.
fun uint16_instance(val: UInt16): Instance
do
var t = mainmodule.uint16_type
var instance = new PrimitiveInstance[UInt16](t, val)
init_instance_primitive(instance)
return instance
end
# Return the int32 instance associated with `val`.
fun int32_instance(val: Int32): Instance
do
var t = mainmodule.int32_type
var instance = new PrimitiveInstance[Int32](t, val)
init_instance_primitive(instance)
return instance
end
# Return the uint32 instance associated with `val`.
fun uint32_instance(val: UInt32): Instance
do
var t = mainmodule.uint32_type
var instance = new PrimitiveInstance[UInt32](t, val)
init_instance_primitive(instance)
return instance
end
# Return the char instance associated with `val`.
fun char_instance(val: Char): Instance
do
var t = mainmodule.char_type
var instance = new PrimitiveInstance[Char](t, val)
init_instance_primitive(instance)
return instance
end
# Return the float instance associated with `val`.
fun float_instance(val: Float): Instance
do
var t = mainmodule.float_type
var instance = new PrimitiveInstance[Float](t, val)
init_instance_primitive(instance)
return instance
end
# The unique instance of the `true` value.
var true_instance: Instance is noinit
# The unique instance of the `false` value.
var false_instance: Instance is noinit
# The unique instance of the `null` value.
var null_instance: Instance is noinit
# Return a new array made of `values`.
# The dynamic type of the result is Array[elttype].
fun array_instance(values: Array[Instance], elttype: MType): Instance
do
assert not elttype.need_anchor
var nat = new PrimitiveInstance[Array[Instance]](mainmodule.native_array_type(elttype), values)
init_instance_primitive(nat)
var mtype = mainmodule.array_type(elttype)
var res = new MutableInstance(mtype)
self.init_instance(res)
self.send(self.force_get_primitive_method("with_native", mtype), [res, nat, self.int_instance(values.length)])
return res
end
# Return a instance associated to a primitive class
# Current primitive classes are `Int`, `Bool`, and `String`
fun value_instance(object: Object): Instance
do
if object isa Int then
return int_instance(object)
else if object isa Bool then
return bool_instance(object)
else if object isa String then
return string_instance(object)
else
abort
end
end
# Return a new C string initialized with `txt`
fun c_string_instance(txt: String): Instance
do
var instance = c_string_instance_len(txt.byte_length+1)
var val = instance.val
val[txt.byte_length] = 0
txt.to_cstring.copy_to(val, txt.byte_length, 0, 0)
return instance
end
# Return a new C string initialized with `txt`
fun c_string_instance_from_ns(txt: CString, len: Int): Instance
do
var instance = c_string_instance_len(len)
var val = instance.val
txt.copy_to(val, len, 0, 0)
return instance
end
# Return a new C string instance sharing the same data space as `txt`
fun c_string_instance_fast_cstr(txt: CString, from: Int): Instance
do
var ncstr = txt.fast_cstring(from)
var t = mainmodule.c_string_type
var instance = new PrimitiveInstance[CString](t, ncstr)
init_instance_primitive(instance)
return instance
end
# Return a new C string initialized of `length`
fun c_string_instance_len(length: Int): PrimitiveInstance[CString]
do
var val = new CString(length)
var t = mainmodule.c_string_type
var instance = new PrimitiveInstance[CString](t, val)
init_instance_primitive(instance)
return instance
end
# Return a new String instance for `txt`
fun string_instance(txt: String): Instance
do
var nat = c_string_instance(txt)
var res = self.send(self.force_get_primitive_method("to_s_unsafe", nat.mtype), [nat, self.int_instance(txt.byte_length), self.int_instance(txt.length), self.false_instance, self.false_instance])
assert res != null
return res
end
# The virtual type of the frames used in the execution engine
type FRAME: Frame
# The current frame used to store local variables of the current method executed
fun frame: FRAME do return frames.first
# The stack of all frames. The first one is the current one.
var frames = new List[FRAME]
# Return a stack trace. One line per function
fun stack_trace: String
do
var b = new FlatBuffer
b.append(",---- Stack trace -- - - -\n")
for f in frames do
b.append("| {f.mpropdef} ({f.current_node.location})\n")
end
b.append("`------------------- - - -")
return b.to_s
end
# The current node, used to print errors, debug and stack-traces
fun current_node: nullable ANode
do
if frames.is_empty then return null
return frames.first.current_node
end
# The dynamic type of the current `self`
fun current_receiver_class: MClassType
do
return frames.first.arguments.first.mtype.as(MClassType)
end
# Initialize the environment for a call and return a new Frame
# *`node` The AST node
# *`mpropdef` The corresponding mpropdef
# *`args` Arguments of the call
fun new_frame(node: ANode, mpropdef: MPropDef, args: Array[Instance]): FRAME
do
return new InterpreterFrame(node, mpropdef, args)
end
# Exit the program with a message
fun fatal(message: String)
do
var node = current_node
if node == null then
print message
else
node.fatal(self, message)
end
exit(1)
end
# Debug on the current node
fun debug(message: String)
do
var node = current_node
if node == null then
print message
else
node.debug(message)
end
end
# Retrieve the value of the variable in the current frame
fun read_variable(v: Variable): Instance
do
var f = frames.first.as(InterpreterFrame)
return f.map[v]
end
# Assign the value of the variable in the current frame
fun write_variable(v: Variable, value: Instance)
do
var f = frames.first.as(InterpreterFrame)
f.map[v] = value
end
# Store known methods, used to trace methods as they are reached
var discover_call_trace: Set[MMethodDef] = new HashSet[MMethodDef]
# Consumes an iterator of expressions and tries to map each element to
# its corresponding Instance.
#
# If any AExprs doesn't resolve to an Instance, then it returns null.
# Otherwise return an array of instances
fun aexprs_to_instances(aexprs: Iterator[AExpr]): nullable Array[Instance]
do
var accumulator = new Array[Instance]
for aexpr in aexprs do
var instance = expr(aexpr)
if instance == null then return null
accumulator.push(instance)
end
return accumulator
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 instances to use in the call.
# Return `null` if one of the evaluation of the arguments return null.
fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: Instance, args: SequenceRead[AExpr]): nullable Array[Instance]
do
var msignature = mpropdef.msignature.as(not null)
var res = new Array[Instance]
res.add(recv)
if msignature.arity == 0 then return res
if map == null then
assert args.length == msignature.arity else debug("Expected {msignature.arity} args, got {args.length}")
var rest_args = aexprs_to_instances(args.iterator)
if rest_args == null then return null
res.append(rest_args)
return res
end
# Eval in order of arguments, not parameters
var exprs = aexprs_to_instances(args.iterator)
if exprs == null then return null
# 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.anchor_to(self.mainmodule, recv.mtype.as(MClassType))
var arg = self.array_instance(vararg, elttype)
res.add(arg)
continue
end
res.add exprs[j]
end
return res
end
# Execute `mpropdef` for a `args` (where `args[0]` is the receiver).
# Return a value if `mpropdef` is a function, or null if it is a procedure.
# The call is direct/static. There is no message-sending/late-binding.
fun call(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
do
if self.modelbuilder.toolcontext.opt_discover_call_trace.value and not self.discover_call_trace.has(mpropdef) then
self.discover_call_trace.add mpropdef
self.debug("Discovered {mpropdef}")
end
assert args.length == mpropdef.msignature.arity + 1 else debug("Invalid arity for {mpropdef}. {args.length} arguments given.")
# Look for the AST node that implements the property
var val = mpropdef.constant_value
var node = modelbuilder.mpropdef2node(mpropdef)
if mpropdef.is_abstract then
if node != null then
self.frames.unshift new_frame(node, mpropdef, args)
end
fatal("Abstract method `{mpropdef.mproperty.name}` called on `{args.first.mtype}`")
abort
end
if node isa APropdef then
self.parameter_check(node, mpropdef, args)
return node.call(self, mpropdef, args)
else if node isa AClassdef then
self.parameter_check(node, mpropdef, args)
return node.call(self, mpropdef, args)
else if node != null then
fatal("Fatal Error: method {mpropdef} associated to unexpected AST node {node.location}")
abort
else if val != null then
return value_instance(val)
else
fatal("Fatal Error: method {mpropdef} not found in the AST")
abort
end
end
# Execute type checks of covariant parameters
fun parameter_check(node: ANode, mpropdef: MMethodDef, args: Array[Instance])
do
var msignature = mpropdef.msignature.as(not null)
for i in [0..msignature.arity[ do
var mp = msignature.mparameters[i]
# skip test for vararg since the array is instantiated with the correct polymorphic type
if mp.is_vararg then continue
# skip if the cast is not required
var origmtype = mpropdef.mproperty.intro.msignature.mparameters[i].mtype
if not origmtype.need_anchor then continue
#print "{mpropdef}: {mpropdef.mproperty.intro.msignature.mparameters[i]}"
# get the parameter type
var mtype = mp.mtype
var anchor = args.first.mtype.as(MClassType)
var amtype = mtype.anchor_to(self.mainmodule, anchor)
if not args[i+1].mtype.is_subtype(self.mainmodule, anchor, amtype) then
node.fatal(self, "Cast failed. Expected `{mtype}`, got `{args[i+1].mtype}`")
end
end
end
# Common code for runtime injected calls and normal calls
fun send_commons(mproperty: MMethod, args: Array[Instance], mtype: MType): nullable Instance
do
if mtype isa MNullType then
if mproperty.name == "==" or mproperty.name == "is_same_instance" then
return self.bool_instance(args[0] == args[1])
else if mproperty.name == "!=" then
return self.bool_instance(args[0] != args[1])
end
#fatal("Receiver is null. {mproperty}. {args.join(" ")} {self.frame.current_node.class_name}")
fatal("Receiver is null")
end
return null
end
# Execute a full `callsite` for given `args`
# Use this method, instead of `send` to execute and control the additional behavior of the call-sites
fun callsite(callsite: nullable CallSite, arguments: Array[Instance]): nullable Instance
do
if callsite == null then return null
return send(callsite.mproperty, arguments)
end
# Execute `mproperty` for a `args` (where `args[0]` is the receiver).
# Return a value if `mproperty` is a function, or null if it is a procedure.
# The call is polymorphic. There is a message-sending/late-binding according to the receiver (args[0]).
fun send(mproperty: MMethod, args: Array[Instance]): nullable Instance
do
var recv = args.first
var mtype = recv.mtype
var ret = send_commons(mproperty, args, mtype)
if ret != null then return ret
var propdef = mproperty.lookup_first_definition(self.mainmodule, mtype)
return self.call(propdef, args)
end
# Read the attribute `mproperty` of an instance `recv` and return its value.
# If the attribute in not yet initialized, then aborts with an error message.
fun read_attribute(mproperty: MAttribute, recv: Instance): Instance
do
assert recv isa MutableInstance
if not recv.attributes.has_key(mproperty) then
fatal("Uninitialized attribute {mproperty.name}")
abort
end
return recv.attributes[mproperty]
end
# Replace in `recv` the value of the attribute `mproperty` by `value`
fun write_attribute(mproperty: MAttribute, recv: Instance, value: Instance)
do
assert recv isa MutableInstance
recv.attributes[mproperty] = value
end
# Is the attribute `mproperty` initialized the instance `recv`?
fun isset_attribute(mproperty: MAttribute, recv: Instance): Bool
do
assert recv isa MutableInstance
return recv.attributes.has_key(mproperty)
end
# Collect attributes of a type in the order of their init
fun collect_attr_propdef(mtype: MType): Array[AAttrPropdef]
do
var cache = self.collect_attr_propdef_cache
if cache.has_key(mtype) then return cache[mtype]
var res = new Array[AAttrPropdef]
var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
self.mainmodule.linearize_mclassdefs(cds)
for cd in cds do
res.add_all(modelbuilder.collect_attr_propdef(cd))
end
cache[mtype] = res
return res
end
private var collect_attr_propdef_cache = new HashMap[MType, Array[AAttrPropdef]]
# Fill the initial values of the newly created instance `recv`.
# `recv.mtype` is used to know what must be filled.
fun init_instance(recv: Instance)
do
for npropdef in collect_attr_propdef(recv.mtype) do
npropdef.init_expr(self, recv)
end
end
# A hook to initialize a `PrimitiveInstance`
fun init_instance_primitive(recv: Instance) do end
# This function determines the correct type according to the receiver of the current propdef (self).
fun unanchor_type(mtype: MType): MType
do
return mtype.anchor_to(self.mainmodule, current_receiver_class)
end
# Placebo instance used to mark internal error result when `null` already have a meaning.
# TODO: replace with multiple return or something better
var error_instance = new MutableInstance(modelbuilder.model.null_type) is lazy
end
src/interpreter/naive_interpreter.nit:58,1--700,3