2020-10-21 22:49:08 +02:00
# Copyright 2020 Mattia Giambirtone
#
# 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.
2020-10-21 22:34:04 +02:00
2020-10-25 12:45:03 +01:00
## A stack-based bytecode virtual machine implementation.
## This is the entire runtime environment for JAPL
2020-12-26 17:01:03 +01:00
{. experimental : " implicitDeref " . }
2021-01-09 18:03:47 +01:00
## Standard library imports
2020-08-08 16:19:44 +02:00
import strformat
2021-01-09 18:03:47 +01:00
## Our modules
2020-10-25 12:45:03 +01:00
import config
2021-01-31 10:51:29 +01:00
when not SKIP_STDLIB_INIT :
import stdlib
2020-08-27 18:15:45 +02:00
import compiler
2020-10-23 17:14:55 +02:00
import meta / opcode
2020-10-25 12:45:03 +01:00
import meta / frame
2020-12-26 17:01:03 +01:00
import types / baseObject
import types / japlString
import types / japlNil
import types / exception
import types / numbers
import types / boolean
import types / methods
2021-01-16 11:47:01 +01:00
import types / typeutils
2020-12-26 17:01:03 +01:00
import types / function
2021-01-05 00:32:17 +01:00
import types / native
2021-03-18 15:09:36 +01:00
import types / arrayList
2021-04-18 13:45:38 +02:00
import types / simpleHashMap
2021-02-28 18:09:19 +01:00
import multibyte
2021-02-28 17:00:12 +01:00
when DEBUG_TRACE_VM :
2021-03-18 15:09:36 +01:00
import util / debug
import terminal
2021-02-28 17:00:12 +01:00
2020-10-19 16:19:49 +02:00
2020-08-27 18:15:45 +02:00
type
KeyboardInterrupt * = object of CatchableError
2020-10-26 22:55:20 +01:00
## Custom exception to handle Ctrl+C
2021-09-30 19:38:35 +02:00
InterpretResult * = enum
2020-10-26 22:55:20 +01:00
## All possible interpretation results
2020-10-25 17:47:53 +01:00
Ok ,
CompileError ,
RuntimeError
2021-03-18 15:09:36 +01:00
VM * = object
2020-10-26 22:55:20 +01:00
## A wrapper around the virtual machine
2021-01-17 16:54:55 +01:00
## functionality. Using custom heap allocated
## types for everything might sound excessive,
## but bad things happen when nim's GC puts its
## hands on JAPL-owned objects, so it was decided
## to reduce the GC's impact to a minimal
2020-10-25 12:45:03 +01:00
lastPop * : ptr Obj
2021-01-17 16:54:55 +01:00
source * : ptr String
frames * : ptr ArrayList [ CallFrame ]
stack * : ptr ArrayList [ ptr Obj ]
objects * : ptr ArrayList [ ptr Obj ]
2021-04-18 13:45:38 +02:00
globals * : ptr SimpleHashMap
2021-01-16 15:11:09 +01:00
cached * : array [ 6 , ptr Obj ]
2021-01-17 16:54:55 +01:00
file * : ptr String
2020-08-07 17:11:06 +02:00
2020-08-23 12:10:08 +02:00
2020-08-19 18:15:48 +02:00
func handleInterrupt ( ) {. noconv . } =
2020-10-21 18:29:08 +02:00
## Raises an appropriate exception
## to let us catch and handle
## Ctrl+C gracefully
2020-08-19 18:15:48 +02:00
raise newException ( KeyboardInterrupt , " Ctrl+C " )
2020-08-08 16:19:44 +02:00
2021-03-18 15:09:36 +01:00
proc initStack * ( self : var VM ) =
2021-01-17 16:54:55 +01:00
## Initializes the VM's stack, frame stack
## and objects arraylist
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Resetting the stack "
2021-01-17 16:54:55 +01:00
self . stack = newArrayList [ ptr Obj ] ( )
self . objects = newArrayList [ ptr Obj ] ( )
self . frames = newArrayList [ CallFrame ] ( )
2021-04-07 00:25:11 +02:00
proc resetStack * ( self : VM ) =
2021-01-17 16:54:55 +01:00
## Resets the VM's stack to a blank state
while self . stack . len ( ) > = 1 :
discard self . stack . pop ( )
while self . frames . len ( ) > = 1 :
discard self . frames . pop ( )
2020-09-03 19:24:18 +02:00
2021-01-09 18:03:47 +01:00
proc getBoolean ( self : VM , kind : bool ) : ptr Obj =
2020-12-21 18:37:08 +01:00
## Tiny little optimization for booleans
## which are pre-allocated on startup
if kind :
return self . cached [ 0 ]
else :
return self . cached [ 1 ]
2021-01-09 18:03:47 +01:00
proc error * ( self : VM , error : ptr JAPLException ) =
2020-10-21 18:29:08 +02:00
## Reports runtime errors with a nice traceback
2021-01-09 18:03:47 +01:00
# TODO: Once we have proper exceptions,
# this procedure will be used to report
# those that were not catched and managed
# to climb the call stack to the first
# frame (the global code object)
# Exceptions are objects too and they need to
# be freed like any other entity in JAPL
2021-01-17 16:54:55 +01:00
self . objects . append ( error ) # TODO -> Move this somewhere else to mark exceptions even before they are raised
2020-09-01 18:09:36 +02:00
var previous = " " # All this stuff seems overkill, but it makes the traceback look nicer
2021-01-09 18:03:47 +01:00
var repCount = 0 # and if we are here we are far beyond a point where performance matters anyway
2020-09-01 18:09:36 +02:00
var mainReached = false
var output = " "
2021-01-16 15:11:09 +01:00
stderr . write ( " An unhandled exception occurred, traceback below: \n " )
2020-10-16 12:38:07 +02:00
for frame in reversed ( self . frames ) :
2020-09-01 18:09:36 +02:00
if mainReached :
break
var function = frame . function
var line = function . chunk . lines [ frame . ip ]
if function . name = = nil :
2021-01-16 15:11:09 +01:00
output = & " File ' {self.file} ' , line {line}, in <module>: "
2020-09-01 18:09:36 +02:00
mainReached = true
else :
2020-09-03 19:24:18 +02:00
output = & " File ' {self.file} ' , line {line}, in {stringify(function.name)}(): "
2020-09-01 18:09:36 +02:00
if output ! = previous :
if repCount > 0 :
2020-09-08 00:06:21 +02:00
stderr . write ( & " ...previous line repeated {repCount} more times... \n " )
2020-09-01 18:09:36 +02:00
repCount = 0
previous = output
2020-09-08 00:06:21 +02:00
stderr . write ( & " {output} \n " )
2020-09-01 18:09:36 +02:00
else :
repCount + = 1
2020-09-08 00:06:21 +02:00
stderr . write ( error . stringify ( ) )
stderr . write ( " \n " )
2020-09-03 19:24:18 +02:00
self . resetStack ( )
2020-08-13 17:53:23 +02:00
2020-08-08 16:19:44 +02:00
2021-01-09 18:03:47 +01:00
proc pop * ( self : VM ) : ptr Obj =
2020-10-26 22:55:20 +01:00
## Pops an object off the stack
2020-10-16 12:38:07 +02:00
result = self . stack . pop ( )
2020-08-07 17:11:06 +02:00
2021-01-09 18:03:47 +01:00
proc push * ( self : VM , obj : ptr Obj ) =
2020-10-25 12:45:03 +01:00
## Pushes an object onto the stack
2021-01-17 16:54:55 +01:00
self . stack . append ( obj )
2020-12-26 17:01:03 +01:00
if obj notin self . objects and obj notin self . cached :
2021-01-17 16:54:55 +01:00
self . objects . append ( obj )
2020-08-08 16:19:44 +02:00
2021-01-16 15:11:09 +01:00
proc push * ( self : VM , ret : returnType ) : bool =
2021-01-16 11:47:01 +01:00
## Pushes a return value from a builtin
## method onto the stack and handles errors
2021-01-16 15:11:09 +01:00
result = true
2021-01-16 11:47:01 +01:00
case ret . kind :
of returnTypes . Object :
self . push ( ret . result )
of returnTypes . Exception :
self . error ( cast [ ptr JAPLException ] ( ret . result ) )
2021-01-16 15:11:09 +01:00
result = false
2021-01-16 11:47:01 +01:00
of returnTypes . True :
self . push ( self . cached [ 0 ] )
of returnTypes . False :
self . push ( self . cached [ 1 ] )
of returnTypes . Nil :
self . push ( self . cached [ 2 ] )
of returnTypes . Inf :
self . push ( self . cached [ 3 ] )
of returnTypes . nInf :
self . push ( self . cached [ 4 ] )
of returnTypes . NotANumber :
self . push ( self . cached [ 5 ] )
2021-01-09 18:03:47 +01:00
proc peek * ( self : VM , distance : int ) : ptr Obj =
2020-10-25 12:45:03 +01:00
## Peeks an object (at a given distance from the
2020-10-21 18:29:08 +02:00
## current index) from the stack
2021-01-17 16:54:55 +01:00
return self . stack [ self . stack . high ( ) - distance ]
2020-08-14 08:25:05 +02:00
2021-03-18 15:09:36 +01:00
proc call ( self : var VM , function : ptr Function , argCount : int ) : bool =
2020-10-21 18:29:08 +02:00
## Sets up the call frame and performs error checking
## when calling callables
2021-01-16 18:14:22 +01:00
if argCount < function . arity :
var arg : string
if function . arity > 1 :
arg = " s "
self . error ( newTypeError ( & " function ' {stringify(function.name)} ' takes at least {function.arity} argument{arg}, got {argCount} " ) )
return false
elif argCount > function . arity and ( argCount - function . arity ) - function . optionals ! = 0 :
self . error ( newTypeError ( & " function ' {stringify(function.name)} ' takes at least {function.arity} arguments and at most {function.arity + function.optionals}, got {argCount} " ) )
2020-09-01 18:09:36 +02:00
return false
2021-01-17 16:54:55 +01:00
if self . frames . len ( ) = = FRAMES_MAX :
2020-09-04 09:23:37 +02:00
self . error ( newRecursionError ( " max recursion depth exceeded " ) )
2020-09-01 18:09:36 +02:00
return false
2020-12-26 20:05:45 +01:00
let slot = self . stack . high ( ) - argCount
2021-01-17 16:54:55 +01:00
var frame = CallFrame ( function : function , ip : 0 , slot : slot , stack : self . stack )
self . frames . append ( frame )
2020-09-01 18:09:36 +02:00
return true
2021-01-05 09:35:18 +01:00
2021-03-18 15:09:36 +01:00
proc call ( self : var VM , native : ptr Native , argCount : int ) : bool =
2021-01-09 18:03:47 +01:00
## Does the same as self.call, but with native functions
2021-01-05 09:35:18 +01:00
if argCount ! = native . arity and native . arity ! = - 1 :
2021-01-05 00:32:17 +01:00
self . error ( newTypeError ( & " function ' {stringify(native.name)} ' takes {native.arity} argument(s), got {argCount} " ) )
return false
let slot = self . stack . high ( ) - argCount + 1
var args : seq [ ptr Obj ]
for i in countup ( slot , self . stack . high ( ) ) :
args . add ( self . stack [ i ] )
let nativeResult = native . nimproc ( args )
for i in countup ( slot - 1 , self . stack . high ( ) ) :
discard self . pop ( ) # TODO once stack is a custom datatype,
# just reduce its length
2021-01-11 13:17:01 +01:00
case nativeResult . kind :
of retNative . True :
self . push ( self . getBoolean ( true ) )
of retNative . False :
self . push ( self . getBoolean ( false ) )
of retNative . Object :
self . push ( nativeResult . result )
of retNative . Nil :
self . push ( self . cached [ 2 ] )
of retNative . Inf :
self . push ( self . cached [ 3 ] )
of retNative . nInf :
self . push ( self . cached [ 4 ] )
of retNative . NotANumber :
self . push ( self . cached [ 5 ] )
of retNative . Exception :
self . error ( cast [ ptr JaplException ] ( nativeResult . result ) )
return false
2021-01-05 00:32:17 +01:00
return true
2020-09-01 18:09:36 +02:00
2021-01-05 09:35:18 +01:00
2021-03-18 15:09:36 +01:00
proc callObject ( self : var VM , callee : ptr Obj , argCount : uint8 ) : bool =
2020-10-21 18:29:08 +02:00
## Wrapper around call() to do type checking
2020-10-26 22:55:20 +01:00
if callee . isCallable ( ) :
case callee . kind :
of ObjectType . Function :
2021-01-05 00:32:17 +01:00
return self . call ( cast [ ptr Function ] ( callee ) , int ( argCount ) )
of ObjectType . Native :
return self . call ( cast [ ptr Native ] ( callee ) , int ( argCount ) )
2020-10-26 22:55:20 +01:00
else : # TODO: Classes
discard # Unreachable
else :
2020-10-27 14:38:28 +01:00
self . error ( newTypeError ( & " object of type ' {callee.typeName()} ' is not callable " ) )
2020-10-26 22:55:20 +01:00
return false
2020-09-01 18:09:36 +02:00
2021-01-09 18:03:47 +01:00
2021-03-18 15:09:36 +01:00
proc defineGlobal * ( self : var VM , name : string , value : ptr Obj ) =
2021-01-09 18:03:47 +01:00
## Adds a key-value couple to the VM's global scope
2021-04-18 13:45:38 +02:00
self . globals [ name . asStr ( ) ] = value
2020-08-28 22:04:02 +02:00
2021-01-09 18:03:47 +01:00
2020-12-26 17:01:03 +01:00
proc readByte ( self : CallFrame ) : uint8 =
2020-12-21 21:53:45 +01:00
## Reads a single byte from the given
## frame's chunk of bytecode
inc ( self . ip )
2021-01-16 18:14:22 +01:00
result = self . function . chunk . code [ self . ip - 1 ]
2020-12-21 21:53:45 +01:00
proc readBytes ( self : CallFrame ) : int =
## Reads and decodes 3 bytes from the
## given frame's chunk into an integer
var arr = [ self . readByte ( ) , self . readByte ( ) , self . readByte ( ) ]
2020-12-26 17:01:03 +01:00
copyMem ( result . addr , unsafeAddr ( arr ) , sizeof ( arr ) )
2020-12-21 21:53:45 +01:00
proc readShort ( self : CallFrame ) : uint16 =
## Reads a 16 bit number from the
## given frame's chunk
2021-02-28 18:09:19 +01:00
fromDouble ( [ self . readByte ( ) , self . readByte ( ) ] )
2020-12-21 21:53:45 +01:00
2021-04-07 00:25:11 +02:00
2020-12-21 21:53:45 +01:00
proc readConstant ( self : CallFrame ) : ptr Obj =
## Reads a constant from the given
## frame's constant table
var arr = [ self . readByte ( ) , self . readByte ( ) , self . readByte ( ) ]
var idx : int
2021-01-16 11:47:01 +01:00
copyMem ( idx . addr , arr . addr , sizeof ( arr ) )
2020-12-21 21:53:45 +01:00
result = self . function . chunk . consts [ idx ]
2021-01-12 12:10:15 +01:00
2021-01-16 11:47:01 +01:00
2021-02-28 17:00:12 +01:00
when DEBUG_TRACE_VM :
2021-04-07 00:25:11 +02:00
proc showRuntime * ( self : VM , frame : CallFrame , iteration : uint64 ) =
2021-02-28 17:00:12 +01:00
## Shows debug information about the current
## state of the virtual machine
let view = frame . getView ( )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgMagenta )
if iteration > 1 :
echo " " # To separate different iterations
2021-02-28 17:00:12 +01:00
stdout . write ( " DEBUG - VM: General information \n " )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgGreen )
stdout . write ( & " DEBUG - VM: \t Iteration -> " )
setForegroundColor ( fgYellow )
stdout . write ( & " {iteration} \n " )
setForegroundColor ( fgGreen )
stdout . write ( " DEBUG - VM: \t Stack -> " )
setForegroundColor ( fgYellow )
stdout . write ( " [ " )
2021-02-28 17:00:12 +01:00
for i , v in self . stack :
stdout . write ( stringify ( v ) )
if i < self . stack . high ( ) :
stdout . write ( " , " )
2021-03-18 15:09:36 +01:00
stdout . write ( " ] " )
setForegroundColor ( fgGreen )
stdout . write ( " \n DEBUG - VM: \t Globals -> " )
setForegroundColor ( fgYellow )
stdout . write ( " { " )
var i = 0
for k , v in self . globals . pairs ( ) :
2021-02-28 17:00:12 +01:00
stdout . write ( & " ' {k} ' : {stringify(v)} " )
if i < self . globals . len ( ) - 1 :
stdout . write ( " , " )
2021-03-18 15:09:36 +01:00
i + = 1
stdout . write ( " } " )
setForegroundColor ( fgMagenta )
stdout . write ( " \n DEBUG - VM: Frame information \n " )
setForegroundColor ( fgGreen )
2021-02-28 17:00:12 +01:00
stdout . write ( " DEBUG - VM: \t Type -> " )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgYellow )
2021-02-28 17:00:12 +01:00
if frame . function . name = = nil :
stdout . write ( " main \n " )
else :
stdout . write ( & " function, ' {frame.function.name.stringify()} ' \n " )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgGreen )
stdout . write ( & " DEBUG - VM: \t Count -> " )
setForegroundColor ( fgYellow )
stdout . write ( & " {self.frames.len()} \n " )
setForegroundColor ( fgGreen )
stdout . write ( & " DEBUG - VM: \t Length -> " )
setForegroundColor ( fgYellow )
stdout . write ( & " {view.len} \n " )
setForegroundColor ( fgGreen )
2021-02-28 17:00:12 +01:00
stdout . write ( " DEBUG - VM: \t Table -> " )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgYellow )
2021-02-28 17:00:12 +01:00
stdout . write ( " [ " )
for i , e in frame . function . chunk . consts :
stdout . write ( stringify ( e ) )
if i < len ( frame . function . chunk . consts ) - 1 :
stdout . write ( " , " )
2021-03-18 15:09:36 +01:00
stdout . write ( " ] " )
setForegroundColor ( fgGreen )
stdout . write ( " \n DEBUG - VM: \t Stack view -> " )
setForegroundColor ( fgYellow )
2021-02-28 17:00:12 +01:00
stdout . write ( " [ " )
for i , e in view :
stdout . write ( stringify ( e ) )
if i < len ( view ) - 1 :
stdout . write ( " , " )
stdout . write ( " ] \n " )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgMagenta )
2021-02-28 17:00:12 +01:00
echo " DEBUG - VM: Current instruction "
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgGreen )
2021-02-28 17:00:12 +01:00
discard disassembleInstruction ( frame . function . chunk , frame . ip - 1 )
2021-03-18 15:09:36 +01:00
setForegroundColor ( fgDefault )
2021-01-12 09:55:41 +01:00
2020-12-21 21:53:45 +01:00
2021-03-18 15:09:36 +01:00
proc run ( self : var VM ) : InterpretResult =
2020-10-21 18:29:08 +02:00
## Chews trough bytecode instructions executing
2020-12-21 21:53:45 +01:00
## them one at a time: this is the runtime's
2020-10-21 18:29:08 +02:00
## main loop
2021-01-17 16:54:55 +01:00
var frame = self . frames [ self . frames . high ( ) ]
2021-01-16 18:14:22 +01:00
var instruction : OpCode
2021-01-11 13:17:01 +01:00
when DEBUG_TRACE_VM :
2021-01-12 09:55:41 +01:00
var iteration : uint64 = 0
2020-08-07 17:11:06 +02:00
while true :
2021-01-16 18:14:22 +01:00
instruction = OpCode ( frame . readByte ( ) )
2020-10-21 18:29:08 +02:00
{. computedgoto . } # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
when DEBUG_TRACE_VM : # Insight inside the VM
2021-01-11 13:17:01 +01:00
iteration + = 1
2021-01-12 09:55:41 +01:00
self . showRuntime ( frame , iteration )
2021-01-16 18:14:22 +01:00
case instruction : # Main OpCodes dispatcher
2020-10-17 16:55:04 +02:00
of OpCode . Constant :
2021-01-14 22:37:11 +01:00
# Loads a constant from the chunk's constant
# table
2020-12-21 21:53:45 +01:00
self . push ( frame . readConstant ( ) )
2020-10-27 18:59:36 +01:00
of OpCode . Negate :
2021-01-14 22:37:11 +01:00
# Performs unary negation
2020-12-26 17:01:03 +01:00
let operand = self . pop ( )
2020-10-27 14:38:28 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( operand . negate ( ) ) :
return RuntimeError
2020-10-27 14:38:28 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported unary operator ' - ' for object of type ' {operand.typeName()} ' " ) )
2020-10-27 14:38:28 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Shl :
# Bitwise left-shift
2020-10-28 17:19:41 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . binaryShl ( right ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' << ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Shr :
# Bitwise right-shift
2020-10-28 17:19:41 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . binaryShr ( right ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' >> ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Xor :
# Bitwise xor
2020-10-28 17:19:41 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . binaryXor ( right ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' ^ ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Bor :
# Bitwise or
2020-10-28 17:19:41 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . binaryOr ( right ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' & ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Bnot :
# Bitwise not
2020-12-26 17:01:03 +01:00
var operand = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( operand . binaryNot ( ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported unary operator ' ~ ' for object of type ' {operand.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2021-01-14 22:37:11 +01:00
of OpCode . Band :
# Bitwise and
2020-10-28 17:19:41 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-28 17:19:41 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . binaryAnd ( right ) ) :
return RuntimeError
2020-10-28 17:19:41 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' & ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-28 17:19:41 +01:00
return RuntimeError
2020-10-27 18:59:36 +01:00
of OpCode . Add :
2021-01-14 22:37:11 +01:00
# Binary +
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . sum ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' + ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Subtract :
2021-01-14 22:37:11 +01:00
# Binary -
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . sub ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' - ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Divide :
2021-01-14 22:37:11 +01:00
# Binary /
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . trueDiv ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' / ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Multiply :
2021-01-14 22:37:11 +01:00
# Binary *
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . mul ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' * ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Mod :
2021-01-14 22:37:11 +01:00
# Binary % (modulo division)
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . divMod ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' % ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Pow :
2021-01-14 22:37:11 +01:00
# Binary ** (exponentiation)
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2020-10-27 18:59:36 +01:00
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . pow ( right ) ) :
return RuntimeError
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' ** ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . True :
2020-12-21 18:37:08 +01:00
self . push ( cast [ ptr Bool ] ( self . getBoolean ( true ) ) )
2020-10-17 16:55:04 +02:00
of OpCode . False :
2020-12-21 18:37:08 +01:00
self . push ( cast [ ptr Bool ] ( self . getBoolean ( false ) ) )
2020-10-17 16:55:04 +02:00
of OpCode . Nil :
2020-12-21 21:53:45 +01:00
self . push ( cast [ ptr Nil ] ( self . cached [ 2 ] ) )
2020-10-17 16:55:04 +02:00
of OpCode . Nan :
2020-12-21 21:53:45 +01:00
self . push ( cast [ ptr NotANumber ] ( self . cached [ 4 ] ) )
2020-10-17 16:55:04 +02:00
of OpCode . Inf :
2020-12-21 21:53:45 +01:00
self . push ( cast [ ptr Infinity ] ( self . cached [ 3 ] ) )
2020-10-17 16:55:04 +02:00
of OpCode . Not :
2021-01-12 09:55:41 +01:00
self . push ( self . getBoolean ( self . pop ( ) . isFalsey ( ) ) )
2020-10-17 16:55:04 +02:00
of OpCode . Equal :
2021-01-14 22:37:11 +01:00
# Compares object equality
2020-10-27 14:38:28 +01:00
# Here order doesn't matter, because if a == b
# then b == a (at least in *most* languages, sigh)
2020-12-21 22:43:40 +01:00
self . push ( self . getBoolean ( self . pop ( ) . eq ( self . pop ( ) ) ) )
2020-10-27 14:38:28 +01:00
# Doesn't this chain of calls look beautifully
# intuitive?
2020-10-17 16:55:04 +02:00
of OpCode . Less :
2021-01-14 22:37:11 +01:00
# Binary less (<)
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2021-02-26 12:27:10 +01:00
var comp : tuple [ result : bool , obj : ptr Obj ]
2020-10-27 18:59:36 +01:00
try :
2021-02-26 12:27:10 +01:00
comp = left . lt ( right )
if system . ` = = ` ( comp . obj , nil ) :
self . push ( self . getBoolean ( comp . result ) )
else :
self . push ( comp . obj )
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' < ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . Greater :
2021-01-14 22:37:11 +01:00
# Binary greater (>)
2020-10-27 18:59:36 +01:00
var right = self . pop ( )
2020-12-21 21:53:45 +01:00
var left = self . pop ( )
2021-02-26 12:27:10 +01:00
var comp : tuple [ result : bool , obj : ptr Obj ]
2020-10-27 18:59:36 +01:00
try :
2021-02-26 12:27:10 +01:00
comp = left . gt ( right )
if system . ` = = ` ( comp . obj , nil ) :
self . push ( self . getBoolean ( comp . result ) )
else :
self . push ( comp . obj )
except NotImplementedError :
self . error ( newTypeError ( & " unsupported binary operator ' > ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
return RuntimeError
of OpCode . LessOrEqual :
var right = self . pop ( )
var left = self . pop ( )
var comp : tuple [ result : bool , obj : ptr Obj ]
try :
comp = left . lt ( right )
if not comp . result and left = = right :
comp . result = true
if system . ` = = ` ( comp . obj , nil ) :
self . push ( self . getBoolean ( comp . result ) )
else :
self . push ( comp . obj )
except NotImplementedError :
self . error ( newTypeError ( & " unsupported binary operator ' < ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
return RuntimeError
of OpCode . GreaterOrEqual :
var right = self . pop ( )
var left = self . pop ( )
var comp : tuple [ result : bool , obj : ptr Obj ]
try :
comp = left . gt ( right )
if not comp . result and left = = right :
comp . result = true
if system . ` = = ` ( comp . obj , nil ) :
self . push ( self . getBoolean ( comp . result ) )
else :
self . push ( comp . obj )
2020-10-27 18:59:36 +01:00
except NotImplementedError :
2020-12-26 17:01:03 +01:00
self . error ( newTypeError ( & " unsupported binary operator ' > ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
2020-10-27 18:59:36 +01:00
return RuntimeError
2021-01-09 18:03:47 +01:00
of OpCode . Is :
2021-01-14 22:37:11 +01:00
# Implements object identity (i.e. same pointer)
2021-01-09 18:03:47 +01:00
# This is implemented internally for obvious
2021-02-26 12:27:10 +01:00
# reasons and works on any pair of objects, which
# is why we call nim's system.== operator and NOT
2021-04-07 00:25:11 +02:00
# our custom one
2021-01-09 18:03:47 +01:00
var right = self . pop ( )
var left = self . pop ( )
2021-02-26 12:27:10 +01:00
self . push ( self . getBoolean ( system . ` = = ` ( left , right ) ) )
2021-01-12 12:10:15 +01:00
of OpCode . As :
2021-01-14 22:37:11 +01:00
# Implements type casting (TODO: Only allow classes)
2021-01-12 12:10:15 +01:00
var right = self . pop ( )
var left = self . pop ( )
try :
2021-01-16 15:11:09 +01:00
if not self . push ( objAs ( left , right . kind ) ) :
return RuntimeError
2021-01-12 12:10:15 +01:00
except NotImplementedError :
self . error ( newTypeError ( & " unsupported binary operator ' as ' for objects of type ' {left.typeName()} ' and ' {right.typeName()} ' " ) )
return RuntimeError
2020-12-21 21:53:45 +01:00
of OpCode . GetItem :
2021-01-14 22:37:11 +01:00
# Implements expressions such as a[b]
2020-12-21 21:53:45 +01:00
# TODO: More generic method
2021-01-16 11:47:01 +01:00
var right = self . pop ( )
var left = self . pop ( )
try :
2021-01-16 15:11:09 +01:00
if not self . push ( left . getItem ( right ) ) :
return RuntimeError
2021-01-16 11:47:01 +01:00
except NotImplementedError :
self . error ( newTypeError ( & " object of type ' {left.typeName()} ' does not support getItem expressions " ) )
2020-10-26 22:55:20 +01:00
return RuntimeError
2020-12-21 21:53:45 +01:00
of OpCode . Slice :
2021-01-14 22:37:11 +01:00
# Implements expressions such as a[b:c]
2021-01-16 11:47:01 +01:00
var right = self . pop ( )
var left = self . pop ( )
var operand = self . pop ( )
try :
2021-01-16 15:11:09 +01:00
if not self . push ( operand . Slice ( right , left ) ) :
return RuntimeError
2021-01-16 11:47:01 +01:00
except NotImplementedError :
self . error ( newTypeError ( & " object of type ' {operand.typeName()} ' does not support slicing " ) )
2020-10-26 22:55:20 +01:00
return RuntimeError
2020-10-17 16:55:04 +02:00
of OpCode . DefineGlobal :
2021-01-14 22:37:11 +01:00
# Defines a global variable
2021-04-18 13:45:38 +02:00
var name = cast [ ptr String ] ( frame . readConstant ( ) )
2021-01-11 08:40:28 +01:00
self . globals [ name ] = self . peek ( 0 )
2020-12-21 21:53:45 +01:00
discard self . pop ( )
2020-10-17 16:55:04 +02:00
of OpCode . GetGlobal :
2021-01-14 22:37:11 +01:00
# Retrieves a global variable
2021-04-18 13:45:38 +02:00
var constant = cast [ ptr String ] ( frame . readConstant ( ) )
2021-01-11 08:40:28 +01:00
if constant notin self . globals :
self . error ( newReferenceError ( & " undefined name ' {constant} ' " ) )
return RuntimeError
else :
self . push ( self . globals [ constant ] )
2020-10-17 16:55:04 +02:00
of OpCode . SetGlobal :
2021-01-14 22:37:11 +01:00
# Changes the value of an already defined global variable
2021-04-18 13:45:38 +02:00
var constant = cast [ ptr String ] ( frame . readConstant ( ) )
2021-01-11 08:40:28 +01:00
if constant notin self . globals :
self . error ( newReferenceError ( & " assignment to undeclared name ' {constant} ' " ) )
return RuntimeError
2020-08-15 11:27:04 +02:00
else :
2021-01-11 08:40:28 +01:00
self . globals [ constant ] = self . peek ( 0 )
2020-10-17 16:55:04 +02:00
of OpCode . DeleteGlobal :
2021-01-14 22:37:11 +01:00
# Deletes a global variable
2020-12-26 17:01:03 +01:00
# TODO: Inspect potential issues with the GC
2021-04-18 13:45:38 +02:00
var constant = cast [ ptr String ] ( frame . readConstant ( ) )
2021-01-11 08:40:28 +01:00
if constant notin self . globals :
self . error ( newReferenceError ( & " undefined name ' {constant} ' " ) )
return RuntimeError
2020-08-15 11:38:36 +02:00
else :
2021-01-11 08:40:28 +01:00
self . globals . del ( constant )
2020-10-17 16:55:04 +02:00
of OpCode . GetLocal :
2021-01-14 22:37:11 +01:00
# Retrieves a local variable
2021-01-16 11:47:01 +01:00
self . push ( frame [ frame . readBytes ( ) ] )
2020-10-17 16:55:04 +02:00
of OpCode . SetLocal :
2021-01-14 22:37:11 +01:00
# Changes the value of an already defined local variable
2021-01-16 11:47:01 +01:00
frame [ frame . readBytes ( ) ] = self . peek ( 0 )
2020-10-17 16:55:04 +02:00
of OpCode . DeleteLocal :
2021-04-07 00:25:11 +02:00
# Deletes a local variable
2020-12-26 17:01:03 +01:00
# TODO: Inspect potential issues with the GC
2021-01-16 11:47:01 +01:00
frame . delete ( frame . readBytes ( ) )
2020-10-17 16:55:04 +02:00
of OpCode . Pop :
2021-01-14 22:37:11 +01:00
# Pops an item off the stack
2020-08-19 13:24:37 +02:00
self . lastPop = self . pop ( )
2020-10-17 16:55:04 +02:00
of OpCode . JumpIfFalse :
2021-01-14 22:37:11 +01:00
# Skips a certain amount of
# bytecode instructions
# if the object at the top of
# our stack is falsey
2020-12-21 21:53:45 +01:00
let jmpOffset = int frame . readShort ( )
2020-08-19 12:40:01 +02:00
if isFalsey ( self . peek ( 0 ) ) :
2020-12-21 18:37:08 +01:00
frame . ip + = int jmpOffset
2020-10-17 16:55:04 +02:00
of OpCode . Jump :
2021-01-14 22:37:11 +01:00
# Jumps a certain amount of bytecode
# instructions, unconditionally
2020-12-21 21:53:45 +01:00
frame . ip + = int frame . readShort ( )
2020-10-17 16:55:04 +02:00
of OpCode . Loop :
2021-01-14 22:37:11 +01:00
# Loops back a certain amount of
# bytecode instructions, unconditionally
2020-12-21 21:53:45 +01:00
frame . ip - = int frame . readShort ( )
2020-10-17 16:55:04 +02:00
of OpCode . Call :
2021-01-14 22:37:11 +01:00
# Implements functions call
2020-12-21 21:53:45 +01:00
var argCount = frame . readByte ( )
2020-10-26 22:55:20 +01:00
if not self . callObject ( self . peek ( int argCount ) , argCount ) :
return RuntimeError
2021-01-17 16:54:55 +01:00
frame = self . frames [ self . frames . high ( ) ]
2020-10-17 16:55:04 +02:00
of OpCode . Break :
2021-01-11 08:40:28 +01:00
discard # Unused (the compiler converts it to other stuff before it arrives here)
2020-10-17 16:55:04 +02:00
of OpCode . Return :
2021-01-14 22:37:11 +01:00
# Handles returning values from the callee to the caller
# and sets up the stack to proceed with execution
2020-09-01 18:09:36 +02:00
var retResult = self . pop ( )
2021-02-26 12:27:10 +01:00
# Pops the function's frame
2020-10-16 12:38:07 +02:00
discard self . frames . pop ( )
2021-01-17 16:54:55 +01:00
if self . frames . len ( ) = = 0 :
2020-09-01 18:09:36 +02:00
discard self . pop ( )
return OK
2021-01-17 16:54:55 +01:00
discard frame . clear ( )
2020-09-01 18:09:36 +02:00
self . push ( retResult )
2021-01-17 16:54:55 +01:00
frame = self . frames [ self . frames . high ( ) ]
2020-08-27 18:15:45 +02:00
2021-01-09 18:03:47 +01:00
proc freeObjects ( self : VM ) =
2020-10-26 22:55:20 +01:00
## Frees all the allocated objects
2020-10-21 18:29:08 +02:00
## from the VM
2021-01-09 18:03:47 +01:00
when DEBUG_TRACE_ALLOCATION :
var runtimeObjCount = len ( self . objects )
var cacheCount = len ( self . cached )
var runtimeFreed = 0
var cachedFreed = 0
2020-09-03 19:24:18 +02:00
for obj in reversed ( self . objects ) :
2021-01-17 16:54:55 +01:00
freeObject ( obj )
2020-09-03 19:24:18 +02:00
discard self . objects . pop ( )
2021-01-09 18:03:47 +01:00
when DEBUG_TRACE_ALLOCATION :
runtimeFreed + = 1
2020-12-21 18:37:08 +01:00
for cached_obj in self . cached :
2021-01-17 16:54:55 +01:00
freeObject ( cached_obj )
2021-01-09 18:03:47 +01:00
when DEBUG_TRACE_ALLOCATION :
cachedFreed + = 1
2020-10-19 16:19:49 +02:00
when DEBUG_TRACE_ALLOCATION :
2020-12-28 10:09:52 +01:00
echo & " DEBUG - VM: Freed {runtimeFreed + cachedFreed} objects out of {runtimeObjCount + cacheCount} ({cachedFreed}/{cacheCount} cached objects, {runtimeFreed}/{runtimeObjCount} runtime objects) "
2020-08-27 18:15:45 +02:00
2021-01-09 18:03:47 +01:00
proc freeVM * ( self : VM ) =
2020-10-21 18:29:08 +02:00
## Tears down the VM
2020-08-19 18:15:48 +02:00
unsetControlCHook ( )
2020-08-27 18:15:45 +02:00
try :
2020-10-19 16:19:49 +02:00
self . freeObjects ( )
2021-01-17 16:54:55 +01:00
freeObject ( self . objects )
freeObject ( self . stack )
freeObject ( self . frames )
2021-01-09 18:03:47 +01:00
except NilAccessDefect :
2020-10-19 16:19:49 +02:00
stderr . write ( " A fatal error occurred -> could not free memory, segmentation fault \n " )
2020-08-27 18:15:45 +02:00
quit ( 71 )
2020-12-26 17:01:03 +01:00
when DEBUG_TRACE_ALLOCATION :
if self . objects . len > 0 :
2020-12-28 10:09:52 +01:00
echo & " DEBUG - VM: Warning, {self.objects.len} objects were not freed "
2021-01-16 18:14:22 +01:00
echo " DEBUG - VM: The virtual machine has shut down "
2020-08-19 18:15:48 +02:00
2020-08-07 17:11:06 +02:00
2021-04-07 00:25:11 +02:00
proc initCache ( self : var VM ) =
2020-12-21 21:53:45 +01:00
## Initializes the static cache for singletons
2021-01-09 18:03:47 +01:00
## such as true and false
# TODO -> Make sure that every operation
# concerning singletons ALWAYS returns
2021-04-07 00:25:11 +02:00
# these cached objects in order to
2021-01-09 18:03:47 +01:00
# implement proper object identity
# in a quicker way than it is done
# for equality
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Initializing singletons cache "
2021-04-07 00:25:11 +02:00
self . cached =
2020-12-21 22:43:40 +01:00
[
2020-12-26 17:01:03 +01:00
true . asBool ( ) . asObj ( ) ,
false . asBool ( ) . asObj ( ) ,
asNil ( ) . asObj ( ) ,
asInf ( ) . asObj ( ) ,
2021-01-11 13:17:01 +01:00
nil ,
2020-12-26 17:01:03 +01:00
asNan ( ) . asObj ( )
2020-12-21 22:43:40 +01:00
]
2021-01-11 13:17:01 +01:00
# We cache -inf as well
let nInf = asInf ( )
nInf . isNegative = true
self . cached [ 4 ] = nInf . asObj ( )
2021-03-18 15:09:36 +01:00
proc initStdlib * ( vm : var VM ) =
2021-01-11 13:17:01 +01:00
## Initializes the VM's standard library by defining builtin
## functions that do not require imports. An arity of -1
## means that the function is variadic (or that it can
## take a different number of arguments according to
## how it's called) and should be handled by the nim
## procedure accordingly
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM and not SKIP_STDLIB_INIT or not DEBUG_TRACE_VM :
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Initializing stdlib "
vm . defineGlobal ( " print " , newNative ( " print " , natPrint , - 1 ) )
2021-02-09 16:59:34 +01:00
vm . defineGlobal ( " printErr " , newNative ( " printErr " , natPrintErr , - 1 ) )
2021-01-16 18:14:22 +01:00
vm . defineGlobal ( " clock " , newNative ( " clock " , natClock , 0 ) )
vm . defineGlobal ( " round " , newNative ( " round " , natRound , - 1 ) )
vm . defineGlobal ( " toInt " , newNative ( " toInt " , natToInt , 1 ) )
vm . defineGlobal ( " toString " , newNative ( " toString " , natToString , 1 ) )
vm . defineGlobal ( " type " , newNative ( " type " , natType , 1 ) )
2021-02-04 12:03:10 +01:00
vm . defineGlobal ( " readLine " , newNative ( " readLine " , natReadline , - 1 ) )
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM and SKIP_STDLIB_INIT :
echo " DEBUG - VM: Skipping stdlib initialization "
2020-12-21 21:53:45 +01:00
2020-09-01 18:09:36 +02:00
proc initVM * ( ) : VM =
2021-01-14 22:37:11 +01:00
## Initializes the Virtual Machine by
## creating the cache, setting signal
## handlers, loading the standard
## library and preparing the stack
## and internal data structures
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo & " DEBUG - VM: Initializing the virtual machine, {JAPL_VERSION_STRING} "
2021-04-18 13:45:38 +02:00
result = VM ( globals : newSimpleHashMap ( ) )
2021-01-17 16:54:55 +01:00
result . initStack ( )
2020-12-21 22:43:40 +01:00
result . initCache ( )
2021-01-17 16:54:55 +01:00
result . initStdlib ( )
2021-01-14 22:37:11 +01:00
setControlCHook ( handleInterrupt )
result . lastPop = cast [ ptr Nil ] ( result . cached [ 2 ] )
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo & " DEBUG - VM: Initialization complete, compiled with the following constants: FRAMES_MAX={FRAMES_MAX}, ARRAY_GROW_FACTOR={ARRAY_GROW_FACTOR}, MAP_LOAD_FACTOR={MAP_LOAD_FACTOR} "
2021-01-14 22:37:11 +01:00
2020-09-01 18:09:36 +02:00
2021-03-18 15:09:36 +01:00
proc interpret * ( self : var VM , source : string , file : string ) : InterpretResult =
2020-10-21 18:29:08 +02:00
## Interprets a source string containing JAPL code
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo & " DEBUG - VM: Preparing to run ' {file} ' "
2020-09-03 19:24:18 +02:00
self . resetStack ( )
2021-01-17 16:54:55 +01:00
self . source = source . asStr ( )
self . file = file . asStr ( )
self . objects . append ( self . source )
self . objects . append ( self . file )
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo & " DEBUG - VM: Compiling ' {file} ' "
2020-12-28 10:09:52 +01:00
var compiler = initCompiler ( SCRIPT , file = file )
var compiled = compiler . compile ( source )
# Here we take into account that self.interpret() might
2021-01-14 22:37:11 +01:00
# get called multiple times (like in the REPL) and we don't wanna loose
# what we allocated before, so we merge everything we already
# allocated and everything the compiler allocated at compile time
2021-01-17 16:54:55 +01:00
self . objects . extend ( compiler . objects )
2020-08-28 22:04:02 +02:00
if compiled = = nil :
2021-01-14 22:37:11 +01:00
# Compile-time error
2020-12-28 10:09:52 +01:00
compiler . freeCompiler ( )
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Result -> CompileError "
2020-10-26 22:55:20 +01:00
return CompileError
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Compilation successful "
2021-01-09 18:03:47 +01:00
# Since in JAPL all code runs in some
# sort of function, we push our global
# "code object" and call it like any
# other function
2020-10-25 12:45:03 +01:00
self . push ( compiled )
2020-10-26 22:55:20 +01:00
discard self . callObject ( compiled , 0 )
2020-09-01 21:02:47 +02:00
try :
2021-01-16 15:11:09 +01:00
result = self . run ( )
2021-01-14 22:37:11 +01:00
except KeyboardInterrupt : # TODO: Better handling
2020-09-01 21:02:47 +02:00
self . error ( newInterruptedError ( " " ) )
2021-01-16 18:14:22 +01:00
when DEBUG_TRACE_VM :
echo " DEBUG - VM: Result -> RuntimeError "
2020-10-26 22:55:20 +01:00
return RuntimeError
2020-10-19 16:19:49 +02:00
when DEBUG_TRACE_VM :
2021-01-16 18:14:22 +01:00
echo & " DEBUG - VM: Result -> {result} "
2021-04-18 13:45:38 +02:00