Added the retNative enum for native functions to handle singletons properly, minor refactoring in the parser and the vm

This commit is contained in:
nocturn9x 2021-01-11 13:17:01 +01:00
parent c882281944
commit 3eeffabe7d
9 changed files with 94 additions and 79 deletions

View File

@ -209,7 +209,7 @@ if __name__ == "__main__":
logging.error("Invalid parameter for --options")
exit()
build(args.path, flags, options, args.override_config, args.skip_tests)
if not args.keep_results_file:
if not args.keep_results_file and not args.skip_tests:
try:
os.remove("testresults.txt")
except Exception as error:

View File

@ -20,12 +20,10 @@ import parseopt
import os
import config
import vm
import stdlib
proc repl() =
var bytecodeVM = initVM()
bytecodeVM.stdlibInit()
echo JAPL_VERSION_STRING
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
when DEBUG_TRACE_VM:
@ -65,6 +63,7 @@ proc main(file: var string = "", fromString: bool = false) =
var source: string
if file == "":
repl()
return # We exit after the REPL has ran
elif not fromString:
var sourceFile: File
try:
@ -80,7 +79,6 @@ proc main(file: var string = "", fromString: bool = false) =
source = file
file = "<string>"
var bytecodeVM = initVM()
bytecodeVM.stdlibInit()
when DEBUG_TRACE_VM:
echo "Debugger enabled, expect verbose output\n"
echo "==== VM Constants ====\n"

View File

@ -205,9 +205,6 @@ proc scanToken(self: Lexer) =
return
elif single == '\n':
self.line += 1
# TODO: Fix this to only emit semicolons where needed
# if self.tokens[^1].kind != TokenType.SEMICOLON:
# self.tokens.add(self.createToken(TOKENS[';']))
elif single in ['"', '\'']:
self.parseString(single)
elif single.isDigit():
@ -247,8 +244,6 @@ proc lex*(self: Lexer): seq[Token] =
while not self.done():
self.start = self.current
self.scanToken()
# if self.tokens[^1].kind != TokenType.SEMICOLON:
# self.tokens.add(self.createToken(TOKENS[';']))
self.tokens.add(Token(kind: TokenType.EOF, lexeme: "EOF", line: self.line))
return self.tokens

View File

@ -14,20 +14,20 @@
# Implementations of builtin functions and modules
import vm
import types/native
import types/baseObject
import types/numbers
import types/methods
import types/japlString
import types/exception
import types/native
import times
import math
import strformat
proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function print
## Prints an object representation
## to stdout. If more than one argument
@ -44,10 +44,10 @@ proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
# Note: we return nil and not asNil() because
# the VM will later use its own cached pointer
# to nil
return (ok: true, result: nil)
return (kind: retNative.Nil, result: nil)
proc natClock(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
proc natClock*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function clock
## Returns the current unix
## time (also known as epoch)
@ -55,10 +55,10 @@ proc natClock(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
# TODO: Move this to a separate module once we have imports
result = (ok: true, result: getTime().toUnixFloat().asFloat())
result = (kind: retNative.Object, result: getTime().toUnixFloat().asFloat())
proc natRound(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
proc natRound*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Rounds a floating point number to a given
## precision (when precision == 0, this function drops the
## decimal part and returns an integer). Note that when
@ -68,53 +68,39 @@ proc natRound(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
var precision = 0
if len(args) notin 1..2:
# Here we need to return immediately to exit the procedure
return (ok: false, result: newTypeError(&"function 'round' takes from 1 to 2 arguments, got {len(args)}"))
return (kind: retNative.Exception, result: newTypeError(&"function 'round' takes from 1 to 2 arguments, got {len(args)}"))
elif len(args) == 2:
if not args[1].isInt():
return (ok: false, result: newTypeError(&"precision must be of type 'int', not '{args[1].typeName()}'"))
return (kind: retNative.Exception, result: newTypeError(&"precision must be of type 'int', not '{args[1].typeName()}'"))
else:
precision = args[1].toInt()
if args[0].kind notin {ObjectType.Integer, ObjectType.Float}:
return (ok: false, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
return (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
if precision < 0:
result = (ok: false, result: newTypeError(&"precision must be positive"))
result = (kind: retNative.Exception, result: newTypeError(&"precision must be positive"))
else:
if args[0].isInt():
result = (ok: true, result: args[0])
result = (kind: retNative.Object, result: args[0])
elif precision == 0:
result = (ok: true, result: int(args[0].toFloat()).asInt())
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
else:
result = (ok: true, result: round(args[0].toFloat(), precision).asFloat())
result = (kind: retNative.Object, result: round(args[0].toFloat(), precision).asFloat())
proc natToInt(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
proc natToInt*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Drops the decimal part of a float and returns an integer.
## If the value is already an integer, the same object is returned
if args[0].isInt():
result = (ok: true, result: args[0])
result = (kind: retNative.Object, result: args[0])
elif args[0].isFloat():
result = (ok: true, result: int(args[0].toFloat()).asInt())
result = (kind: retNative.Object, result: int(args[0].toFloat()).asInt())
else:
result = (ok: false, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
result = (kind: retNative.Exception, result: newTypeError(&"input must be of type 'int' or 'float', not '{args[0].typeName()}'"))
proc natType(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
proc natType*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Returns the type of a given object as a string
result = (ok: true, result: args[0].typeName().asStr())
proc stdlibInit*(vm: VM) =
## 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
vm.defineGlobal("print", newNative("print", natPrint, -1))
vm.defineGlobal("clock", newNative("clock", natClock, 0))
vm.defineGlobal("round", newNative("round", natRound, -1))
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
vm.defineGlobal("type", newNative("type", natType, 1))
result = (kind: retNative.Object, result: args[0].typeName().asStr())

View File

@ -59,5 +59,4 @@ proc newObj*(): ptr Obj =
proc asObj*(self: ptr Obj): ptr Obj =
## Casts a specific JAPL object into a generic
## pointer to Obj
result = cast[ptr Obj](self)

View File

@ -18,6 +18,22 @@ import baseObject
import japlString
type retNative* {.pure.} = enum
## Defines all the possible native
## return types (this is useful
## to keep singletons actually
## singletons and makes it easier
## to bring back exceptions as well)
False,
True,
Inf,
nInf,
Nil,
NotANumber,
Object,
Exception
type
Native* = object of Obj
## A native object
@ -25,10 +41,10 @@ type
arity*: int # The number of required parameters
optionals*: int # The number of optional parameters
defaults*: seq[ptr Obj] # List of default arguments, in order
nimproc*: proc (args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] # The function's body
nimproc*: proc (args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] # The function's body
proc newNative*(name: string, nimproc: proc(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj], arity: int = 0): ptr Native =
proc newNative*(name: string, nimproc: proc(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj], arity: int = 0): ptr Native =
## Allocates a new native object with the given
## bytecode chunk and arity. If the name is an empty string
## (the default), the function will be an

View File

@ -22,6 +22,7 @@ import strformat
import tables
import std/enumerate
## Our modules
import stdlib
import memory
import config
import compiler
@ -59,7 +60,7 @@ type
stackTop*: int
objects*: seq[ptr Obj]
globals*: Table[string, ptr Obj]
cached: array[5, ptr Obj]
cached: array[6, ptr Obj]
file*: string
@ -243,21 +244,27 @@ proc call(self: VM, native: ptr Native, argCount: int): bool =
for i in countup(slot, self.stack.high()):
args.add(self.stack[i])
let nativeResult = native.nimproc(args)
if not nativeResult.ok:
self.error(cast[ptr JaplException](nativeResult.result))
return false
# assumes that all native procs behave well, and if not ok, they
# only return japl exceptions
for i in countup(slot - 1, self.stack.high()):
discard self.pop() # TODO once stack is a custom datatype,
# just reduce its length
if nativeResult.result == nil:
# Since nil is a singleton, natives
# don't reallocate it and we need to
# reuse our cached object instead
self.push(self.cached[2])
else:
self.push(nativeResult.result)
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
return true
@ -322,30 +329,25 @@ proc run(self: VM, repl: bool): InterpretResult =
## them one at a time: this is the runtime's
## main loop
var frame = self.frames[self.frameCount - 1]
var instruction: uint8
var opcode: OpCode
when DEBUG_TRACE_VM:
var iteration: int = 0
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
instruction = frame.readByte()
opcode = OpCode(instruction)
opcode = OpCode(frame.readByte())
when DEBUG_TRACE_VM: # Insight inside the VM
stdout.write("Current VM stack status: [")
iteration += 1
stdout.write(&"Iteration N. {iteration}\nCurrent VM stack status: [")
for i, v in self.stack:
stdout.write(stringify(v))
if i < self.stack.high():
stdout.write(", ")
stdout.write("]\n")
stdout.write("Current global scope status: {")
stdout.write("]\nCurrent global scope status: {")
for i, (k, v) in enumerate(self.globals.pairs()):
stdout.write("'")
stdout.write(k)
stdout.write("'")
stdout.write(": ")
stdout.write(stringify(v))
stdout.write(&"'{k}': {stringify(v)}")
if i < self.globals.len() - 1:
stdout.write(", ")
stdout.write("}\n")
stdout.write("Current frame type: ")
stdout.write("}\nCurrent frame type: ")
if frame.function.name == nil:
stdout.write("main\n")
else:
@ -358,8 +360,7 @@ proc run(self: VM, repl: bool): InterpretResult =
stdout.write(stringify(e))
if i < frame.function.chunk.consts.high():
stdout.write(", ")
stdout.write("]\n")
stdout.write("Current frame stack status: ")
stdout.write("]\nCurrent frame stack status: ")
stdout.write("[")
for i, e in frame.getView():
stdout.write(stringify(e))
@ -704,8 +705,27 @@ proc initCache(self: VM) =
false.asBool().asObj(),
asNil().asObj(),
asInf().asObj(),
nil,
asNan().asObj()
]
# We cache -inf as well
let nInf = asInf()
nInf.isNegative = true
self.cached[4] = nInf.asObj()
proc stdlibInit*(vm: VM) =
## 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
vm.defineGlobal("print", newNative("print", natPrint, -1))
vm.defineGlobal("clock", newNative("clock", natClock, 0))
vm.defineGlobal("round", newNative("round", natRound, -1))
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
vm.defineGlobal("type", newNative("type", natType, 1))
proc initVM*(): VM =
@ -714,6 +734,7 @@ proc initVM*(): VM =
var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]()
result = VM(lastPop: asNil(), objects: @[], globals: globals, source: "", file: "")
result.initCache()
result.stdlibInit()
proc interpret*(self: VM, source: string, repl: bool = false, file: string): InterpretResult =

View File

@ -15,7 +15,7 @@ print(1 or 2 or 3 or 4);//output:1
print(1 and 2 and 3 and 4);//output:4
print(1 and 2 or 3 and 4);//output:2
print(1 and false or 3 and 4);//output:4
print(!0);//output:true
print(!1);//output:false
print(!1 and !2);//output:false
print(!(1 and 0));//output:true
print(not 0);//output:true
print(not 1);//output:false
print(not 1 and not 2);//output:false
print(not (1 and 0));//output:true

View File

@ -1,7 +1,7 @@
var x = 4;
var y = x;
print(x is y);//output:false
print(x is y);//output:true
print(x is x);//output:true
print(x is 4);//output:false