mirror of https://github.com/japl-lang/japl.git
commit
1e2227ea38
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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))
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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()
|
30
src/vm.nim
30
src/vm.nim
|
@ -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)}'"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import ../../src/multibyte
|
||||
|
||||
|
||||
for i in countup(0, int(uint16.high())):
|
||||
assert fromDouble(toDouble(i)) == uint16(i)
|
||||
|
Loading…
Reference in New Issue