WIP closures
This commit is contained in:
parent
0c4a6553ff
commit
3e48b1cc52
|
@ -6,6 +6,7 @@ import types/value
|
|||
type
|
||||
OpCode* = enum
|
||||
opReturn, opCall, opCheckArity, opFunctionDef, # functions
|
||||
opClosure, # closures
|
||||
opPop, opPopSA, opPopA # pop
|
||||
opNegate, opNot # unary
|
||||
opAdd, opSubtract, opMultiply, opDivide, # math
|
||||
|
@ -111,7 +112,7 @@ const argInstructions = {
|
|||
opPopA,
|
||||
opGetLocal, opSetLocal,
|
||||
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop,
|
||||
opFunctionDef,
|
||||
opFunctionDef, opClosure,
|
||||
opCreateList, opCreateTable,
|
||||
}
|
||||
|
||||
|
|
|
@ -9,19 +9,21 @@ import bitops # needed for value
|
|||
import config
|
||||
|
||||
type
|
||||
Local = object
|
||||
Local = ref object
|
||||
name: string # name of this local
|
||||
index: int # what is its index in the stack (0 is the stack bottom - nil in the main function)
|
||||
depth: int # depth of this local
|
||||
# if depth is -1, the variable cannot be referenced yet
|
||||
# its depth will be set once its first ever value is determined
|
||||
scope: Scope # the innermost scope of this local
|
||||
|
||||
Scope = ref object
|
||||
labels: seq[string]
|
||||
#depth: int
|
||||
goalStackIndex: int # the stack count it started with plus 1
|
||||
jumps: seq[int] # jumps to be patched that jump to the end
|
||||
function: bool # if true, it is a function
|
||||
parentFunction: Scope # if not a function, which scope is the innermost function it's in (if it's a function this points to itself)
|
||||
# if parentFunction is nil, the scope is not inside a function
|
||||
|
||||
Compiler = ref object
|
||||
#
|
||||
|
@ -67,6 +69,11 @@ proc newScope(comp: Compiler, function: bool): Scope =
|
|||
#result.depth = comp.scopes.len + 1
|
||||
result.function = function
|
||||
result.goalStackIndex = comp.stackIndex + 1
|
||||
if function:
|
||||
result.parentFunction = result
|
||||
elif comp.scopes[comp.scopes.high()].parentFunction != nil:
|
||||
result.parentFunction = comp.scopes[comp.scopes.high()].parentFunction
|
||||
|
||||
comp.scopes.add(result)
|
||||
|
||||
# HELPERS FOR THE COMPILER TYPE
|
||||
|
@ -168,7 +175,7 @@ proc addLocal(comp: Compiler, name: string, delta: int) =
|
|||
|
||||
# if delta is 0 or negative, it means that it is already on the stack when addLocal is called
|
||||
# if delta is positive, the first ever value of the local is to the right
|
||||
comp.locals.add(Local(name: name, depth: if delta > 0: -1 else: comp.scopes.high, index: comp.stackIndex + delta))
|
||||
comp.locals.add(Local(name: name, depth: if delta > 0: -1 else: comp.scopes.high, index: comp.stackIndex + delta, scope: comp.scopes[comp.scopes.high()]))
|
||||
|
||||
proc markInitialized(comp: Compiler) =
|
||||
comp.locals[comp.locals.high].depth = comp.scopes.high
|
||||
|
@ -295,6 +302,8 @@ proc jumpToEnd(comp: Compiler, scope: Scope) =
|
|||
proc endScope(comp: Compiler) =
|
||||
# remove locals
|
||||
let popped = comp.scopes.pop()
|
||||
if popped.parentFunction == popped:
|
||||
popped.parentFunction = nil # cleanup cycles
|
||||
let function = popped.function
|
||||
when debugCompiler:
|
||||
debugEcho &"End scope called for depth {comp.scopes.len} function? {function}"
|
||||
|
@ -383,15 +392,30 @@ proc expString(comp: Compiler) =
|
|||
|
||||
tkString.genRule(expString, nop, pcNone)
|
||||
|
||||
proc resolveLocal(comp: Compiler, name: string): int =
|
||||
## returns the stack index of the local of the name
|
||||
proc addUpvalue(comp: Compiler, index: int): int =
|
||||
## This proc takes an index to a local in the locals table
|
||||
## and creates an upvalue in every function up until the one
|
||||
## including this local so that all of the function scopes in between
|
||||
## have the right upvalue in them (compile time)
|
||||
discard
|
||||
|
||||
proc resolveLocal(comp: Compiler, name: string): Tuple[int, bool] =
|
||||
## the bool arg specifies whether it found an upvalue
|
||||
## if it's a local: returns the stack index of the local of the name
|
||||
## if it's an upvalue: returns the upvalue index
|
||||
## if the number is -1, then the name cannot be resolved at compile time
|
||||
var i = comp.locals.high
|
||||
let cfunc = comp.scopes[comp.scopes.high()].parentFunction
|
||||
while i >= 0:
|
||||
let local = comp.locals[i]
|
||||
if local.name == name:
|
||||
if local.depth == -1:
|
||||
comp.error("Can't read local variable in its own initializer.")
|
||||
return local.index
|
||||
let upvalue = local.scope.parentFunction != cfunc
|
||||
if not upvalue:
|
||||
return (local.index, false)
|
||||
else:
|
||||
return (comp.addUpvalue(i), true)
|
||||
i.dec
|
||||
return -1
|
||||
|
||||
|
@ -403,12 +427,16 @@ proc variable(comp: Compiler) =
|
|||
let name = comp.previous.text
|
||||
|
||||
# try resolving local, set arg to the index on the stack
|
||||
var arg = comp.resolveLocal(name)
|
||||
var (arg, upval) = comp.resolveLocal(name)
|
||||
|
||||
if arg != -1:
|
||||
# local
|
||||
getOp = opGetLocal
|
||||
setOp = opSetLocal
|
||||
if upval:
|
||||
getOp = opGetUpvalue
|
||||
setOp = opSetUpvalue
|
||||
else:
|
||||
# local
|
||||
getOp = opGetLocal
|
||||
setOp = opSetLocal
|
||||
else:
|
||||
# global
|
||||
arg = comp.chunk.addConstant(name.fromNimString())
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
type
|
||||
UpvalueObj[T] = object
|
||||
location: ptr T
|
||||
|
||||
Upvalue[T]* = ptr UpvalueObj[T]
|
||||
|
||||
ClosureObj[T] = object
|
||||
start: ptr uint8
|
||||
upvalueCount: int
|
||||
upvalues: UncheckedArray[Upvalue[T]]
|
||||
|
||||
Closure[T]* = ptr ClosureObj[T]
|
||||
|
||||
proc newClosure[T]*(start: ptr uint8, upvalueCount: int): Closure[T] =
|
||||
result = cast[Closure[T]](alloc0(8 * upvalueCount + sizeof(ClosureObj[T])))
|
||||
result.start = start
|
||||
result.upvalueCount = upvalueCount
|
||||
|
||||
proc getIp*(clos: Closure): ptr uint8 {.inline.} =
|
||||
clos.start
|
|
@ -12,11 +12,12 @@ proc free*(ndStr: var NdString) =
|
|||
# hashes
|
||||
|
||||
proc fnv1a*(ndStr: NdString): int {.inline.} =
|
||||
var hash = 2166136261'u32
|
||||
for i in countup(0, ndStr.len.int - 1):
|
||||
hash = hash xor (ndStr.chars[i]).uint32
|
||||
hash *= 16777619
|
||||
return hash.int
|
||||
return ndStr.hash.int
|
||||
#var hash = 2166136261'u32
|
||||
#for i in countup(0, ndStr.len.int - 1):
|
||||
# hash = hash xor (ndStr.chars[i]).uint32
|
||||
# hash *= 16777619
|
||||
#return hash.int
|
||||
|
||||
proc fnv1a*(str: string): int {.inline.} = # SHOULD RETURN THE SAME HASH AS AN NDSTRING WITH THE SAME CONTENTS
|
||||
var hash = 2166136261'u32
|
||||
|
|
|
@ -4,6 +4,7 @@ import bitops
|
|||
import ndstring
|
||||
import ndlist
|
||||
import hashtable
|
||||
import closure
|
||||
|
||||
type
|
||||
NdValue* = uint
|
||||
|
@ -23,7 +24,7 @@ type
|
|||
# 00 -> nil or bool (singletons)
|
||||
# 01 -> string
|
||||
# 10 -> funct
|
||||
# 11 -> closure (to be implemented later)
|
||||
# 11 -> closure
|
||||
# if bit 63 is 1:
|
||||
# 00 -> list
|
||||
# 01 -> table
|
||||
|
@ -42,6 +43,8 @@ const tagString* = 0x7ffd000000000000'u
|
|||
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
||||
const tagFunct* = 0x7ffe000000000000'u
|
||||
|
||||
const tagClosure* = 0x7fff000000000000'u
|
||||
|
||||
const tagList* = 0xfffc000000000000'u
|
||||
const tagTable* = 0xfffd000000000000'u
|
||||
|
||||
|
@ -64,6 +67,9 @@ template isString*(val: NdValue): bool =
|
|||
template isFunct*(val: NdValue): bool =
|
||||
val.bitand(mask48) == tagFunct
|
||||
|
||||
template isClosure*(val: NdValue): bool =
|
||||
val.bitand(mask48) == tagClosure
|
||||
|
||||
template isList*(val: NdValue): bool =
|
||||
val.bitand(mask48) == tagList
|
||||
|
||||
|
@ -83,6 +89,9 @@ template asString*(val: NdValue): NdString =
|
|||
template asFunct*(val: NdValue): ptr uint8 =
|
||||
cast[ptr uint8](val.bitand(mask48.bitnot()))
|
||||
|
||||
template asClosure*(val: NdValue): Closure[NdValue] =
|
||||
cast[Closure[NdValue]](val.bitand(mask48.bitnot()))
|
||||
|
||||
template asList*(val: NdValue): List[NdValue] =
|
||||
cast[List[NdValue]](val.bitand(mask48.bitnot()))
|
||||
|
||||
|
@ -107,6 +116,9 @@ template fromNimString*(sval: string): NdValue =
|
|||
template fromFunct*(val: ptr uint8): NdValue =
|
||||
cast[uint](val).bitor(tagFunct)
|
||||
|
||||
template fromClosure*(val: Closure[NdValue]): NdValue =
|
||||
cast[uint](val).bitor(tagClosure)
|
||||
|
||||
template fromList*(val: List[NdValue]): NdValue =
|
||||
cast[uint](val).bitor(tagList)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import bitops # needed for value's templates
|
|||
import types/value
|
||||
import types/hashtable
|
||||
import types/ndlist
|
||||
import types/closure
|
||||
|
||||
|
||||
when profileInstructions:
|
||||
|
@ -64,6 +65,17 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
|
||||
template frameBottom: int = frames.peek().stackBottom
|
||||
|
||||
template call(funct: NdValue, argcount: int, error: untyped) =
|
||||
if funct.isFunct():
|
||||
# create new frame
|
||||
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
||||
ip = funct.asFunct() # jump to the entry point
|
||||
elif funct.isClosure():
|
||||
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
||||
ip = funct.asClosure().getIp()
|
||||
else:
|
||||
error
|
||||
|
||||
while true:
|
||||
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
||||
|
||||
|
@ -204,6 +216,11 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
let faddr: ptr uint8 = ip
|
||||
ip = ip.padd(offset)
|
||||
stack.push(faddr.fromFunct())
|
||||
of opClosure:
|
||||
let offset = readDU8()
|
||||
let faddr: ptr uint8 = ip
|
||||
ip = ip.padd(offset)
|
||||
stack.push(newClosure(faddr).fromClosure())
|
||||
of opCheckArity:
|
||||
let arity = readUI8()
|
||||
let argcount = stack.high() - frameBottom
|
||||
|
@ -218,16 +235,12 @@ proc run*(chunk: Chunk): InterpretResult =
|
|||
# ... <ret val> <arg1> <arg2> <arg3>
|
||||
let argcount = readUI8()
|
||||
let funct = stack.getIndexNeg(argcount)
|
||||
if not funct.isFunct():
|
||||
|
||||
stack.setIndexNeg(argcount, fromNil()) # replace the function with nil: this is the return value slot
|
||||
funct.call(argcount):
|
||||
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
|
||||
break
|
||||
|
||||
stack.setIndexNeg(argcount, fromNil()) # replace the function with nil: this is the return value slot
|
||||
|
||||
# create new frame
|
||||
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
||||
|
||||
ip = funct.asFunct() # jump to the entry point
|
||||
of opCreateList:
|
||||
let listLen = readDU8()
|
||||
if listLen == 0:
|
||||
|
|
|
@ -29,4 +29,38 @@ var f2 = funct() {
|
|||
};
|
||||
|
||||
//expect:5.0
|
||||
f2()();
|
||||
f2()();
|
||||
|
||||
// oop: constructors, getters, setters
|
||||
|
||||
var newAnimal = funct(species, color) {
|
||||
var species = species; // copy it so that it's mutable, if args ever get immutable
|
||||
var animal = @{
|
||||
"getSpecies" = funct() :result = species,
|
||||
"setSpecies" = funct(newSpecies) species = newSpecies,
|
||||
"getColor" = funct() :result = color, // this captures an argument directly
|
||||
};
|
||||
:result = animal;
|
||||
};
|
||||
|
||||
var horse1 = newAnimal("horse", "brown");
|
||||
var horse2 = newAnimal("horse", "white");
|
||||
|
||||
var turtle = newAnimal("turtle", "brown");
|
||||
|
||||
//expect:horse
|
||||
print horse1["getSpecies"]();
|
||||
horse1["setSpecies"]("zebra");
|
||||
//expect:zebra
|
||||
print horse1["getSpecies"]();
|
||||
|
||||
//expect:brown
|
||||
print horse1["getColor"]();
|
||||
//expect:horse
|
||||
print horse2["getSpecies"]();
|
||||
//expect:white
|
||||
print horse2["getColor"]();
|
||||
//expect:turtle
|
||||
print turtle["getSpecies"]();
|
||||
|
||||
// TODO: tests for enclosing upvalues multiple levels up
|
Loading…
Reference in New Issue