Merge pull request #27 from Productive2/master

Natives
This commit is contained in:
Mattia 2021-01-05 00:41:11 +01:00 committed by GitHub
commit 1e2227ea38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 14 deletions

View File

@ -31,6 +31,7 @@ import types/methods
import tables
import config
import memory
import multibyte
when isMainModule:
import util/debug
@ -716,7 +717,7 @@ proc patchJump(self: Compiler, offset: int) =
if jump > (int uint16.high):
self.compileError("too much code to jump over")
else:
let casted = cast[array[2, uint8]](jump)
let casted = toDouble(jump)
self.currentChunk.code[offset] = casted[0]
self.currentChunk.code[offset + 1] = casted[1]
@ -751,8 +752,9 @@ proc emitLoop(self: Compiler, start: int) =
if offset > (int uint16.high):
self.compileError("loop body is too large")
else:
self.emitByte(uint8 offset and 0xff)
self.emitByte(uint8 (offset shr 8) and 0xff)
let offsetBytes = toDouble(offset)
self.emitByte(offsetBytes[0])
self.emitByte(offsetBytes[1])
proc endLooping(self: Compiler) =
@ -1048,7 +1050,7 @@ proc freeObject(self: Compiler, obj: ptr Obj) =
of ObjectType.Exception, ObjectType.Class,
ObjectType.Module, ObjectType.BaseObject, ObjectType.Integer,
ObjectType.Float, ObjectType.Bool, ObjectType.NotANumber,
ObjectType.Infinity, ObjectType.Nil:
ObjectType.Infinity, ObjectType.Nil, ObjectType.Native:
when DEBUG_TRACE_ALLOCATION:
echo &"DEBUG - Compiler: Freeing {obj.typeName()} object with value '{stringify(obj)}'"
discard free(obj.kind, obj)

View File

@ -20,10 +20,12 @@ import parseopt
import os
import config
import vm
import stdlib
proc repl() =
var bytecodeVM = initVM()
stdlibInit(bytecodeVM)
echo JAPL_VERSION_STRING
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
when DEBUG_TRACE_VM:

7
src/multibyte.nim Normal file
View File

@ -0,0 +1,7 @@
proc toDouble*(input: int | uint | uint16): array[2, uint8] =
cast[array[2, uint8]](uint16(input))
proc fromDouble*(input: array[2, uint8]): uint16 =
copyMem(result.addr, unsafeAddr(input), sizeof(uint16))

15
src/stdlib.nim Normal file
View File

@ -0,0 +1,15 @@
# this implements stdlib functions for JAPL
import vm
import types/native
import types/baseObject
import types/japlNil
import types/methods
proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
echo args[0].stringify()
return (ok: true, result: asNil())
template stdlibInit*(vm: VM) =
vm.defineGlobal("print", newNative("print", natPrint, 1))

View File

@ -24,6 +24,7 @@ type
## All the possible object types
String, Exception, Function,
Class, Module, BaseObject,
Native,
Integer, Float, Bool, NotANumber,
Infinity, Nil
Obj* = object of RootObj

View File

@ -22,6 +22,7 @@ import function
import boolean
import japlNil
import numbers
import native
proc typeName*(self: ptr Obj): string =
@ -45,8 +46,10 @@ proc typeName*(self: ptr Obj): string =
result = cast[ptr NotANumber](self).typeName()
of ObjectType.Nil:
result = cast[ptr Nil](self).typeName()
of ObjectType.Native:
result = cast[ptr Native](self).typeName()
else:
discard # TODO
discard
proc stringify*(self: ptr Obj): string =
@ -71,8 +74,10 @@ proc stringify*(self: ptr Obj): string =
result = cast[ptr NotANumber](self).stringify()
of ObjectType.Nil:
result = cast[ptr Nil](self).stringify()
of ObjectType.Native:
result = cast[ptr Native](self).stringify()
else:
discard # TODO
discard
proc hash*(self: ptr Obj): uint64 =
@ -104,8 +109,10 @@ proc hash*(self: ptr Obj): uint64 =
result = cast[ptr NotANumber](self).hash()
of ObjectType.Nil:
result = cast[ptr Nil](self).hash()
of ObjectType.Native:
result = cast[ptr Native](self).hash()
else:
discard # TODO
discard
proc isFalsey*(self: ptr Obj): bool =
@ -130,8 +137,10 @@ proc isFalsey*(self: ptr Obj): bool =
result = cast[ptr NotANumber](self).isFalsey()
of ObjectType.Nil:
result = cast[ptr Nil](self).isFalsey()
of ObjectType.Native:
result = cast[ptr Native](self).isFalsey()
else:
discard # TODO
discard
proc eq*(self, other: ptr Obj): bool =
@ -176,8 +185,12 @@ proc eq*(self, other: ptr Obj): bool =
var self = cast[ptr Nil](self)
var other = cast[ptr Nil](other)
result = self.eq(other)
of ObjectType.Native:
var self = cast[ptr Native](self)
var other = cast[ptr Native](other)
result = self.eq(other)
else:
discard # TODO
discard
proc negate*(self: ptr Obj): ptr Obj =
@ -402,7 +415,7 @@ proc objType*(obj: ptr Obj): ObjectType =
proc isCallable*(obj: ptr Obj): bool =
## Returns true if the given object
## is callable, false otherwise
result = obj.kind in {ObjectType.Function, ObjectType.Class}
result = obj.kind in {ObjectType.Function, ObjectType.Class, ObjectType.Native}
proc isNil*(obj: ptr Obj): bool =

52
src/types/native.nim Normal file
View File

@ -0,0 +1,52 @@
import baseObject
import ../meta/opcode
import japlString
type
Native* = object of Obj
## A native object
name*: ptr String
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
proc newNative*(name: string, nimproc: proc(args: seq[ptr Obj]): tuple[ok: bool, 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
## anonymous code object
result = allocateObj(Native, ObjectType.Native)
if name.len > 1:
result.name = name.asStr()
else:
result.name = nil
result.arity = arity
result.nimproc = nimproc
result.isHashable = false
proc typeName*(self: ptr Native): string =
result = "function"
proc stringify*(self: ptr Native): string =
if self.name != nil:
result = "<function '" & self.name.toStr() & "'>"
else:
result = "<code object>"
proc isFalsey*(self: ptr Native): bool =
result = false
proc hash*(self: ptr Native): uint64 =
# TODO: Hashable?
raise newException(NotImplementedError, "unhashable type 'native'")
proc eq*(self, other: ptr Native): bool =
result = self.name.stringify() == other.name.stringify()

View File

@ -32,6 +32,7 @@ import types/numbers
import types/boolean
import types/methods
import types/function
import types/native
import memory
import tables
when DEBUG_TRACE_VM:
@ -214,10 +215,9 @@ proc sliceRange(self: var VM): bool =
return false
proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
proc call(self: var VM, function: ptr Function, argCount: int): bool =
## Sets up the call frame and performs error checking
## when calling callables
var argCount = int argCount
if argCount != function.arity:
self.error(newTypeError(&"function '{stringify(function.name)}' takes {function.arity} argument(s), got {argCount}"))
return false
@ -231,19 +231,41 @@ proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
self.frameCount += 1
return true
proc call(self: var VM, native: ptr Native, argCount: int): bool =
if argCount != native.arity:
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)
if not nativeResult.ok:
self.error(cast[ptr JaplException](nativeResult.result))
# 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
self.push(nativeResult.result)
return true
proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool =
## Wrapper around call() to do type checking
if callee.isCallable():
case callee.kind:
of ObjectType.Function:
return self.call(cast[ptr Function](callee), argCount)
return self.call(cast[ptr Function](callee), int(argCount))
of ObjectType.Native:
return self.call(cast[ptr Native](callee), int(argCount))
else: # TODO: Classes
discard # Unreachable
else:
self.error(newTypeError(&"object of type '{callee.typeName()}' is not callable"))
return false
proc defineGlobal*(self: VM, name: string, value: ptr Obj) =
self.globals[name] = value
proc readByte(self: CallFrame): uint8 =
## Reads a single byte from the given
@ -597,7 +619,7 @@ proc freeObject(self: VM, obj: ptr Obj) =
of ObjectType.Exception, ObjectType.Class,
ObjectType.Module, ObjectType.BaseObject, ObjectType.Integer,
ObjectType.Float, ObjectType.Bool, ObjectType.NotANumber,
ObjectType.Infinity, ObjectType.Nil:
ObjectType.Infinity, ObjectType.Nil, ObjectType.Native:
when DEBUG_TRACE_ALLOCATION:
if obj notin self.cached:
echo &"DEBUG- VM: Freeing {obj.typeName()} object with value '{stringify(obj)}'"

6
tests/nim/multibyte.nim Normal file
View File

@ -0,0 +1,6 @@
import ../../src/multibyte
for i in countup(0, int(uint16.high())):
assert fromDouble(toDouble(i)) == uint16(i)