2020-08-07 17:11:06 +02:00
import meta / chunk
2020-08-08 16:19:44 +02:00
import meta / valueobject
2020-08-13 23:39:26 +02:00
import types / exceptions
import types / objecttype
2020-08-08 16:19:44 +02:00
import util / debug
import compiler
import strutils
import strformat
2020-08-10 10:48:21 +02:00
import math
2020-08-08 16:19:44 +02:00
import lenientops
2020-08-15 08:52:58 +02:00
import lists
import tables
2020-08-07 17:11:06 +02:00
2020-08-13 17:53:23 +02:00
2020-08-10 10:48:21 +02:00
proc `**` ( a , b : int ) : int = pow ( a . float , b . float ) . int
2020-08-13 17:53:23 +02:00
2020-08-10 10:48:21 +02:00
proc `**` ( a , b : float ) : float = pow ( a , b )
2020-08-07 17:11:06 +02:00
2020-08-13 17:53:23 +02:00
2020-08-07 17:11:06 +02:00
type InterpretResult = enum
OK ,
COMPILE_ERROR ,
RUNTIME_ERROR
2020-08-08 16:19:44 +02:00
2020-08-07 17:11:06 +02:00
type VM = ref object
chunk : Chunk
ip : int
2020-08-08 16:19:44 +02:00
stack * : seq [ Value ]
stackTop * : int
2020-08-15 08:52:58 +02:00
objects * : SinglyLinkedList [ Obj ] # Unused for now
globals * : Table [ string , Value ]
2020-08-08 16:19:44 +02:00
2020-08-13 17:53:23 +02:00
2020-08-15 11:27:04 +02:00
proc error * ( self : VM , error : JAPLException ) =
echo stringify ( error )
2020-08-13 23:39:26 +02:00
# Add code to raise an exception here
2020-08-13 17:53:23 +02:00
2020-08-08 16:19:44 +02:00
2020-08-13 17:53:23 +02:00
proc pop * ( self : VM ) : Value =
2020-08-08 16:19:44 +02:00
result = self . stack . pop ( )
self . stackTop = self . stackTop - 1
2020-08-07 17:11:06 +02:00
2020-08-08 16:19:44 +02:00
proc push * ( self : VM , value : Value ) =
self . stack . add ( value )
self . stackTop = self . stackTop + 1
2020-08-14 08:25:05 +02:00
proc peek * ( self : VM , distance : int ) : Value =
return self . stack [ len ( self . stack ) - distance - 1 ]
2020-08-14 10:20:42 +02:00
proc slice ( self : VM ) : bool =
2020-08-14 10:02:13 +02:00
var idx = self . pop ( )
var peeked = self . pop ( )
case peeked . kind :
of OBJECT :
case peeked . obj . kind :
of STRING :
2020-08-15 08:52:58 +02:00
var str = peeked . obj . str
2020-08-14 10:02:13 +02:00
if idx . kind ! = INTEGER :
self . error ( newTypeError ( " string indeces must be integers! " ) )
return false
elif idx . intValue - 1 > len ( str ) - 1 :
self . error ( newIndexError ( " string index out of bounds " ) )
return false
elif idx . intValue < 0 :
self . error ( newIndexError ( " string index out of bounds " ) )
return false
2020-08-15 08:52:58 +02:00
self . push ( Value ( kind : OBJECT , obj : Obj ( kind : STRING , str : & " {str[idx.intValue]} " ) ) )
2020-08-14 10:02:13 +02:00
return true
else :
self . error ( newTypeError ( & " Unsupported slicing for object of type ' {toLowerAscii( $ (peeked.kind))} ' " ) )
return false
else :
self . error ( newTypeError ( & " Unsupported slicing for object of type ' {toLowerAscii( $ (peeked.kind))} ' " ) )
return false
2020-08-14 10:20:42 +02:00
proc sliceRange ( self : VM ) : bool =
var sliceEnd = self . pop ( )
var sliceStart = self . pop ( )
var popped = self . pop ( )
case popped . kind :
of OBJECT :
case popped . obj . kind :
of STRING :
2020-08-15 08:52:58 +02:00
var str = popped . obj . str
if sliceEnd . kind = = NIL :
sliceEnd = Value ( kind : INTEGER , intValue : len ( str ) - 1 )
if sliceStart . kind = = NIL :
sliceStart = Value ( kind : INTEGER , intValue : 0 )
2020-08-14 10:20:42 +02:00
if sliceStart . kind ! = INTEGER or sliceEnd . kind ! = INTEGER :
self . error ( newTypeError ( " string indeces must be integers! " ) )
return false
elif sliceStart . intValue - 1 > len ( str ) - 1 or sliceEnd . intValue - 1 > len ( str ) - 1 :
self . error ( newIndexError ( " string index out of bounds " ) )
return false
elif sliceStart . intValue < 0 or sliceEnd . intValue < 0 :
self . error ( newIndexError ( " string index out of bounds " ) )
return false
2020-08-15 08:52:58 +02:00
self . push ( Value ( kind : OBJECT , obj : Obj ( kind : STRING , str : str [ sliceStart . intValue .. sliceEnd . intValue ] ) ) )
2020-08-14 10:20:42 +02:00
return true
else :
self . error ( newTypeError ( & " Unsupported slicing for object of type ' {toLowerAscii( $ (popped.kind))} ' " ) )
return false
else :
self . error ( newTypeError ( & " Unsupported slicing for object of type ' {toLowerAscii( $ (popped.kind))} ' " ) )
return false
2020-08-15 11:27:04 +02:00
proc run ( self : VM , debug , repl : bool ) : InterpretResult =
2020-08-07 17:11:06 +02:00
template readByte : untyped =
inc ( self . ip )
self . chunk . code [ self . ip - 1 ]
2020-08-08 16:19:44 +02:00
template readConstant : Value =
self . chunk . consts . values [ int ( readByte ( ) ) ]
template readLongConstant : Value =
var arr = [ readByte ( ) , readByte ( ) , readByte ( ) ]
var idx : int
copyMem ( idx . addr , unsafeAddr ( arr ) , sizeof ( arr ) )
self . chunk . consts . values [ idx ]
2020-08-13 17:53:23 +02:00
template BinOp ( op , check ) =
var rightVal {. inject . } = self . pop ( )
2020-08-13 23:39:26 +02:00
var leftVal {. inject . } = self . pop ( )
2020-08-13 17:53:23 +02:00
if check ( leftVal ) and check ( rightVal ) :
2020-08-13 23:39:26 +02:00
if leftVal . isFloat ( ) and rightVal . isInt ( ) :
var res = ` op ` ( leftVal . toFloat ( ) , float rightVal . toInt ( ) )
if res is bool :
self . push ( Value ( kind : BOOL , boolValue : bool res ) )
else :
self . push ( Value ( kind : DOUBLE , floatValue : float res ) )
elif leftVal . isInt ( ) and rightVal . isFloat ( ) :
var res = ` op ` ( float leftVal . toInt ( ) , rightVal . toFloat ( ) )
if res is bool :
self . push ( Value ( kind : BOOL , boolValue : bool res ) )
2020-08-13 17:53:23 +02:00
else :
2020-08-13 23:39:26 +02:00
self . push ( Value ( kind : DOUBLE , floatValue : float res ) )
elif leftVal . isFloat ( ) and rightVal . isFloat ( ) :
var res = ` op ` ( leftVal . toFloat ( ) , rightVal . toFloat ( ) )
if res is bool :
self . push ( Value ( kind : BOOL , boolValue : bool res ) )
else :
self . push ( Value ( kind : DOUBLE , floatValue : float res ) )
2020-08-10 10:48:21 +02:00
else :
2020-08-13 23:39:26 +02:00
var tmp = ` op ` ( leftVal . toInt ( ) , rightVal . toInt ( ) )
if tmp is int :
self . push ( Value ( kind : INTEGER , intValue : int tmp ) )
elif tmp is bool :
self . push ( Value ( kind : BOOL , boolValue : bool tmp ) )
else :
self . push ( Value ( kind : DOUBLE , floatValue : float tmp ) )
2020-08-08 16:19:44 +02:00
else :
2020-08-13 23:39:26 +02:00
self . error ( newTypeError ( & " Unsupported binary operand for objects of type ' {toLowerAscii( $ (leftVal.kind))} ' and ' {toLowerAscii( $ (rightVal.kind))} ' " ) )
2020-08-13 17:53:23 +02:00
return RUNTIME_ERROR
2020-08-07 17:11:06 +02:00
var instruction : uint8
var opcode : OpCode
while true :
{. computedgoto . }
instruction = readByte ( )
opcode = OpCode ( instruction )
2020-08-08 16:19:44 +02:00
if debug :
stdout . write ( " Current stack status: [ " )
for v in self . stack :
2020-08-13 23:39:26 +02:00
stdout . write ( stringify ( v ) )
2020-08-08 16:19:44 +02:00
stdout . write ( " , " )
stdout . write ( " ] \n " )
2020-08-15 08:52:58 +02:00
stdout . write ( " Global scope status: { " )
for k , v in self . globals . pairs ( ) :
stdout . write ( k )
stdout . write ( " : " )
stdout . write ( stringify ( v ) )
echo " } \n "
2020-08-08 16:19:44 +02:00
discard disassembleInstruction ( self . chunk , self . ip - 1 )
2020-08-07 17:11:06 +02:00
case opcode :
of OP_CONSTANT :
2020-08-08 16:19:44 +02:00
var constant : Value = readConstant ( )
self . push ( constant )
2020-08-07 17:11:06 +02:00
of OP_CONSTANT_LONG :
2020-08-08 16:19:44 +02:00
var constant : Value = readLongConstant ( )
self . push ( constant )
of OP_NEGATE :
var cur = self . pop ( )
case cur . kind :
2020-08-10 10:48:21 +02:00
of DOUBLE :
2020-08-08 16:19:44 +02:00
cur . floatValue = - cur . floatValue
self . push ( cur )
2020-08-10 10:48:21 +02:00
of INTEGER :
2020-08-08 16:19:44 +02:00
cur . intValue = - cur . intValue
self . push ( cur )
else :
echo & " Unsupported unary operator ' - ' for object of type ' {toLowerAscii( $cur .kind)} ' "
return RUNTIME_ERROR
of OP_ADD :
2020-08-14 08:25:05 +02:00
if self . peek ( 0 ) . kind = = OBJECT and self . peek ( 1 ) . kind = = OBJECT :
if self . peek ( 0 ) . obj . kind = = STRING and self . peek ( 1 ) . obj . kind = = STRING :
var r = self . peek ( 0 ) . obj . str
var l = self . peek ( 1 ) . obj . str
self . push ( Value ( kind : OBJECT , obj : Obj ( kind : STRING , str : l [ 0 .. len ( l ) - 2 ] & r [ 1 .. < len ( r ) ] ) ) )
else :
self . error ( newTypeError ( & " Unsupported binary operand for objects of type ' {toLowerAscii( $ (self.peek(0).kind))} ' and ' {toLowerAscii( $ (self.peek(1).kind))} ' " ) )
return RUNTIME_ERROR
else :
BinOp ( ` + ` , isNum )
2020-08-08 16:19:44 +02:00
of OP_SUBTRACT :
2020-08-13 23:39:26 +02:00
BinOp ( ` - ` , isNum )
2020-08-08 16:19:44 +02:00
of OP_DIVIDE :
2020-08-13 23:39:26 +02:00
BinOp ( ` / ` , isNum )
2020-08-08 16:19:44 +02:00
of OP_MULTIPLY :
2020-08-14 08:25:05 +02:00
if self . peek ( 0 ) . kind = = INTEGER and self . peek ( 1 ) . kind = = OBJECT :
if self . peek ( 1 ) . obj . kind = = STRING :
var r = self . peek ( 0 ) . intValue
var l = self . peek ( 1 ) . obj . str
2020-08-15 11:27:04 +02:00
self . push ( Value ( kind : OBJECT , obj : Obj ( kind : STRING , str : l . repeat ( r ) ) ) )
2020-08-14 08:25:05 +02:00
else :
self . error ( newTypeError ( & " Unsupported binary operand for objects of type ' {toLowerAscii( $ (self.peek(0).kind))} ' and ' {toLowerAscii( $ (self.peek(1).kind))} ' " ) )
return RUNTIME_ERROR
elif self . peek ( 0 ) . kind = = OBJECT and self . peek ( 1 ) . kind = = INTEGER :
if self . peek ( 0 ) . obj . kind = = STRING :
var r = self . peek ( 0 ) . obj . str
var l = self . peek ( 1 ) . intValue
2020-08-15 11:27:04 +02:00
self . push ( Value ( kind : OBJECT , obj : Obj ( kind : STRING , str : r . repeat ( l ) ) ) )
2020-08-14 08:25:05 +02:00
else :
self . error ( newTypeError ( & " Unsupported binary operand for objects of type ' {toLowerAscii( $ (self.peek(0).kind))} ' and ' {toLowerAscii( $ (self.peek(1).kind))} ' " ) )
return RUNTIME_ERROR
else :
BinOp ( ` * ` , isNum )
2020-08-10 10:48:21 +02:00
of OP_MOD :
2020-08-13 23:39:26 +02:00
BinOp ( floorMod , isNum )
2020-08-10 10:48:21 +02:00
of OP_POW :
2020-08-13 23:39:26 +02:00
BinOp ( ` * * ` , isNum )
2020-08-13 17:53:23 +02:00
of OP_TRUE :
self . push ( Value ( kind : BOOL , boolValue : true ) )
of OP_FALSE :
self . push ( Value ( kind : BOOL , boolValue : false ) )
of OP_NIL :
self . push ( Value ( kind : NIL ) )
2020-08-13 23:39:26 +02:00
of OP_NOT :
self . push ( Value ( kind : BOOL , boolValue : isFalsey ( self . pop ( ) ) ) )
of OP_EQUAL :
var a = self . pop ( )
var b = self . pop ( )
2020-08-14 10:02:13 +02:00
if a . kind = = DOUBLE and b . kind = = INTEGER :
b = Value ( kind : DOUBLE , floatValue : float b . intValue )
elif b . kind = = DOUBLE and a . kind = = INTEGER :
a = Value ( kind : DOUBLE , floatValue : float a . intValue )
2020-08-13 23:39:26 +02:00
self . push ( Value ( kind : BOOL , boolValue : valuesEqual ( a , b ) ) )
of OP_LESS :
BinOp ( ` < ` , isNum )
of OP_GREATER :
BinOp ( ` > ` , isNum )
2020-08-14 10:02:13 +02:00
of OP_SLICE :
if not self . slice ( ) :
return RUNTIME_ERROR
of OP_SLICE_RANGE :
2020-08-14 10:20:42 +02:00
if not self . sliceRange ( ) :
return RUNTIME_ERROR
2020-08-15 08:52:58 +02:00
of OP_DEFINE_GLOBAL :
if self . chunk . consts . values . len > 255 :
var constant = readLongConstant ( ) . obj . str
self . globals [ constant ] = self . peek ( 0 )
else :
var constant = readConstant ( ) . obj . str
self . globals [ constant ] = self . peek ( 0 )
discard self . pop ( ) # This will help when we have a custom GC
2020-08-15 11:27:04 +02:00
of OP_GET_GLOBAL :
if self . chunk . consts . values . len > 255 :
var constant = readLongConstant ( ) . obj . str
if constant notin self . globals :
self . error ( newReferenceError ( & " undefined name ' {constant} ' " ) )
return RUNTIME_ERROR
else :
self . push ( self . globals [ constant ] )
else :
var constant = readConstant ( ) . obj . str
if constant notin self . globals :
self . error ( newReferenceError ( & " undefined name ' {constant} ' " ) )
return RUNTIME_ERROR
else :
self . push ( self . globals [ constant ] )
of OP_SET_GLOBAL :
if self . chunk . consts . values . len > 255 :
var constant = readLongConstant ( ) . obj . str
if constant notin self . globals :
self . error ( newReferenceError ( & " assignment to undeclared name ' {constant} ' " ) )
return RUNTIME_ERROR
else :
self . globals [ constant ] = self . peek ( 0 )
else :
var constant = readConstant ( ) . obj . str
if constant notin self . globals :
self . error ( newReferenceError ( & " assignment to undeclared name ' {constant} ' " ) )
return RUNTIME_ERROR
else :
self . globals [ constant ] = self . peek ( 0 )
2020-08-15 08:52:58 +02:00
of OP_POP :
2020-08-15 11:27:04 +02:00
var popped = self . pop ( )
if repl :
echo stringify ( popped )
2020-08-08 16:19:44 +02:00
of OP_RETURN :
2020-08-07 17:11:06 +02:00
return OK
2020-08-15 11:27:04 +02:00
proc interpret * ( self : var VM , source : string , debug : bool = false , repl : bool = false ) : InterpretResult =
2020-08-08 16:19:44 +02:00
var chunk = initChunk ( )
2020-08-09 10:09:03 +02:00
var compiler = initCompiler ( chunk )
2020-08-08 16:19:44 +02:00
if not compiler . compile ( source , chunk ) :
return COMPILE_ERROR
2020-08-07 17:11:06 +02:00
self . chunk = chunk
self . ip = 0
2020-08-10 18:39:53 +02:00
if len ( chunk . code ) > 1 :
2020-08-15 11:27:04 +02:00
result = self . run ( debug , repl )
2020-08-08 16:19:44 +02:00
chunk . freeChunk ( )
proc resetStack * ( self : VM ) =
self . stackTop = 0
2020-08-07 17:11:06 +02:00
proc initVM * ( ) : VM =
2020-08-15 08:52:58 +02:00
result = VM ( chunk : initChunk ( ) , ip : 0 , stack : @ [ ] , stackTop : 0 , objects : initSinglyLinkedList [ Obj ] ( ) , globals : initTable [ string , Value ] ( ) )
2020-08-07 17:11:06 +02:00
proc freeVM * ( self : VM ) =
return