custom string object

This commit is contained in:
prod2 2022-01-28 04:17:11 +01:00
parent 90d04f13f6
commit 8031c7720d
9 changed files with 109 additions and 63 deletions

View File

@ -1,9 +1,9 @@
local function fib(n)
if n < 2 then
return 1
return 1.5
else
return fib(n-1) + fib(n-2)
end
end
print(fib(37))
print(fib(37.2))

View File

@ -1,6 +1,6 @@
var fib = funct(n)
if (n < 2) ^result = 1
if (n < 2) ^result = 1.5
else ^result = fib(n-1) + fib(n-2)
;
print fib(37);
print fib(37.2);

View File

@ -1,7 +1,7 @@
def fib(n):
if n < 2:
return 1
return 1.5
else:
return fib(n-1) + fib(n-2)
print(fib(37))
print(fib(37.2))

View File

@ -648,12 +648,12 @@ proc parseFunct(comp: Compiler) =
dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
# get ndvalue functions:
let ndFunct = newNdFunction(functII)
let ntFunct = newNdFunction(functII)
# end of function declaration:
comp.patchJump(jumpOverBody)
# put the fn object on the stack
comp.writeConstant(ndFunct)
comp.writeConstant(ntFunct)
tkFunct.genRule(parseFunct, nop, pcNone)

View File

@ -77,7 +77,7 @@ proc skipWhitespace(scanner: Scanner) =
discard scanner.advance()
of '/':
if scanner.peekNext() == '/':
while scanner.peek != '\n' and not scanner.isAtEnd():
while not scanner.isAtEnd() and scanner.peek != '\n' :
discard scanner.advance()
else:
return
@ -85,7 +85,7 @@ proc skipWhitespace(scanner: Scanner) =
return
proc scanString(scanner: Scanner): Token =
while scanner.peek() != '\"' and not scanner.isAtEnd():
while not scanner.isAtEnd() and scanner.peek() != '\"' :
if scanner.peek() == '\n':
scanner.line.inc
discard scanner.advance()

19
types/ndstring.nim Normal file
View File

@ -0,0 +1,19 @@
type
NdString* = ptr object
len: uint32
chars: UncheckedArray[char]
proc newString*(str: string): NdString =
let strlen = str.len()
let len = 4 + strlen
result = cast[NdString](alloc(len))
result.len = strlen.uint32
copyMem(result.chars[0].unsafeAddr, str[0].unsafeAddr, strlen)
proc `$`*(ndStr: NdString): string =
result = newString(ndStr.len.int)
copyMem(result[0].unsafeAddr, ndStr.chars[0].unsafeAddr, ndStr.len.int)
proc `&`*(left, right: NdString): NdString =
# TODO optimize this later when strings will be benchmarked
newString($left & $right)

View File

@ -2,13 +2,16 @@ import ../pointerutils
# configure stacks here
const boundsChecks = false
# boundsChecks default: false, true has a large performance impact, and emitting correct code is on the compiler's job
# boundsChecking is only meant for debugging
const growthFactor = 2
# should be a natural number larger than 1
type
Stack*[T] = object
top: ptr T
start: ptr T
cap: int
cap*: int
proc newStack*[T](startingCap: int): Stack[T] =
result.start = cast[ptr T](alloc(startingCap * sizeof(T)))
@ -25,8 +28,8 @@ proc destroyStack*[T](stack: var Stack[T]) =
proc grow[T](stack: var Stack[T], len: int) {.inline.} =
## growth the stack's capacity and increments the top's index by one
stack.start = cast[ptr T](realloc(stack.start, stack.cap * growthFactor))
stack.cap *= growthFactor
stack.start = cast[ptr T](realloc(stack.start, stack.cap * sizeof(T)))
stack.top = stack.start.padd(len * sizeof(T))
proc shrink[T](stack: var Stack[T]) {.inline.} =
@ -57,6 +60,9 @@ proc pop*[T](stack: var Stack[T]): T {.inline.} =
stack.top = stack.top.psub(sizeof(T))
proc peek*[T](stack: Stack[T]): var T {.inline.} =
when boundsChecks:
if stack.top == nil or stack.top.pless(stack.start):
raise newException(Defect, "Stacktop is nil or smaller than start.")
stack.top[]
proc deleteTopN*[T](stack: var Stack[T], n: Natural) =
@ -71,6 +77,12 @@ proc getIndex*[T](stack: Stack[T], index: int): T =
raise newException(Defect, "Attempt to getIndex with an index out of bounds.")
stack.start.padd(index * sizeof(T))[]
proc getIndexNeg*[T](stack: Stack[T], index: int): T =
when boundsChecks:
if index < 0 or index >= stack.len():
raise newException(Defect, "Attempt to getIndexNeg with an index out of bounds.")
stack.top.psub(index * sizeof(T))[]
template `[]`*[T](stack: Stack[T], index: int): T =
stack.getIndex(index)
@ -80,5 +92,12 @@ proc setIndex*[T](stack: var Stack[T], index: int, item: T) =
raise newException(Defect, "Attempt to getIndex with an index out of bounds.")
stack.start.padd(index * sizeof(T))[]= item
proc setIndexNeg*[T](stack: Stack[T], index: int, item: T) =
when boundsChecks:
if index < 0 or index >= stack.len():
raise newException(Defect, "Attempt to setIndexNeg with an index out of bounds.")
stack.top.psub(index * sizeof(T))[] = item
template `[]=`*[T](stack: var Stack[T], index: int, item: T) =
stack.setIndex(index, item)

View File

@ -1,23 +1,23 @@
import strformat
import types/ndstring
type
NdType* = enum
ndNil, ndBool, ndFloat, ndString,
ndFunct,
ntNil, ntBool, ntFloat, ntString,
ntFunct,
type
NdValue* = object
case ndType*: NdType:
of ndNil:
of ntNil:
discard
of ndBool:
of ntBool:
boolValue*: bool
of ndFloat:
of ntFloat:
floatValue*: float64
of ndString:
stringValue*: string
of ndFunct:
placeholder*: string
of ntString:
stringValue*: NdString
of ntFunct:
entryII*: int # entry instruction index
NatReturn* = object
@ -28,19 +28,19 @@ type
proc `$`*(val: NdValue): string =
case val.ndType:
of ndFloat:
of ntFloat:
return $val.floatValue
of ndBool:
of ntBool:
return $val.boolValue
of ndNil:
of ntNil:
return "nil"
of ndString:
return val.stringValue
of ndFunct:
of ntString:
return $val.stringValue
of ntFunct:
return &"Function object: {val.entryII}"
proc isFalsey*(val: NdValue): bool =
val.ndType in {ndNil} or (val.ndType == ndBool and not val.boolValue)
val.ndType in {ntNil} or (val.ndType == ntBool and not val.boolValue)
template isTruthy*(val: NdValue): bool =
not isFalsey(val)
@ -50,33 +50,33 @@ proc equal*(val, right: NdValue): bool =
false
else:
case val.ndType:
of ndFloat:
of ntFloat:
val.floatValue == right.floatValue
of ndBool:
of ntBool:
val.boolValue == right.boolValue
of ndNil:
of ntNil:
true
of ndString:
of ntString:
val.stringValue == right.stringValue
of ndFunct:
of ntFunct:
val.entryII == right.entryII
# NIM VALUE TO KON VALUE WRAPPERS
proc toNdValue*(val: float): NdValue =
NdValue(ndType: ndFloat, floatValue: val)
NdValue(ndType: ntFloat, floatValue: val)
proc toNdValue*(val: bool): NdValue =
NdValue(ndType: ndBool, boolValue: val)
NdValue(ndType: ntBool, boolValue: val)
proc toNdValue*(val: string): NdValue =
NdValue(ndType: ndString, stringValue: val)
NdValue(ndType: ntString, stringValue: val.newString())
proc newNdFunction*(ii: int): NdValue =
NdValue(ndType: ndFunct, entryII: ii)
NdValue(ndType: ntFunct, entryII: ii)
proc toNdValue*: NdValue =
NdValue(ndType: ndNil)
NdValue(ndType: ntNil)
# NatReturn misc
@ -89,51 +89,51 @@ const natOk* = NatReturn(ok: true)
# NOTE: these operations can return ktTypeError with a message if types are invalid
proc negate*(val: var NdValue): NatReturn {.inline.} =
if (val.ndType != ndFloat):
if (val.ndType != ntFloat):
return natError("Operand must be a number.")
else:
val.floatValue = -val.floatValue
return natOk
proc add*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val.floatValue += right.floatValue
elif val.ndType == ndString and right.ndType == ndString:
val.stringValue &= right.stringValue
elif val.ndType == ntString and right.ndType == ntString:
val.stringValue = val.stringValue & right.stringValue
else:
return natError(&"Attempt to add types {val.ndType} and {right.ndType}.")
return natOk
proc subtract*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val.floatValue -= right.floatValue
else:
return natError(&"Attempt to subtract types {val.ndType} and {right.ndType}.")
return natOk
proc multiply*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val.floatValue *= right.floatValue
else:
return natError(&"Attempt to multiply types {val.ndType} and {right.ndType}.")
return natOk
proc divide*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val.floatValue /= right.floatValue
else:
return natError(&"Attempt to divide types {val.ndType} and {right.ndType}.")
return natOk
proc less*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val = toNdValue(val.floatValue < right.floatValue)
else:
return natError(&"Attempt to compare types {val.ndType} and {right.ndType}.")
return natOk
proc greater*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
if val.ndType == ndFloat and right.ndType == ndFloat:
if val.ndType == ntFloat and right.ndType == ntFloat:
val = toNdValue(val.floatValue > right.floatValue)
else:
return natError(&"Attempt to compare types {val.ndType} and {right.ndType}.")

40
vm.nim
View File

@ -7,11 +7,18 @@ import config
import pointerutils
import types/stack
import types/ndstring
when profileInstructions:
import times
import std/monotimes
# not a perfect profiling approach, doing random stacktrace dumps
# x amount of times per second to get the current source line
# would be better
var durations: array[OpCode, Duration]
var runcounts: array[OpCode, float64]
type
Frame = object
stackBottom: int # the absolute index of where 0 inside the frame is
@ -20,21 +27,14 @@ type
InterpretResult* = enum
irOK, irRuntimeError
when profileInstructions:
# not a perfect profiling approach, doing random stacktrace dumps
# x amount of times per second to get the current source line
# would be better
var durations: array[OpCode, Duration]
proc run*(chunk: Chunk): InterpretResult =
var
ip: ptr uint8 = chunk.code[0].unsafeAddr
#stack: seq[NdValue] = newSeqOfCap[NdValue](256)
stack: Stack[NdValue] = newStack[NdValue](256)
hadError: bool
globals: Table[string, NdValue]
frames: seq[Frame] = newSeqOfCap[Frame](4)
frames: Stack[Frame] = newStack[Frame](4)
proc runtimeError(msg: string) =
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
@ -42,6 +42,8 @@ proc run*(chunk: Chunk): InterpretResult =
write stderr, &"[line: {line}] {msg}\n"
hadError = true
frames.add(Frame(stackBottom: 0))
#template peek(stack: seq[NdValue]): NdValue =
# stack[stack.high]
@ -63,6 +65,7 @@ proc run*(chunk: Chunk): InterpretResult =
let index = readDU8()
chunk.constants[index]
#template frameBottom: int = frames.peek().stackBottom
template frameBottom: int = frames[frames.high].stackBottom
while true:
@ -87,6 +90,7 @@ proc run*(chunk: Chunk): InterpretResult =
when profileInstructions:
let startTime = getMonoTime()
runtimes[ins].inc
case ins:
of opPop:
@ -130,7 +134,7 @@ proc run*(chunk: Chunk): InterpretResult =
runtimeError(res.msg)
break
of opReturn:
if frames.len == 0:
if frames.len() == 1:
break
else:
ip = frames.pop().returnIp # remove frame that's over
@ -159,21 +163,21 @@ proc run*(chunk: Chunk): InterpretResult =
of opPrint:
echo $stack.peek()
of opDefineGlobal:
let name = readConstant().stringValue
let name = $readConstant().stringValue
if not globals.hasKey(name):
globals[name] = stack.pop()
else:
runtimeError("Attempt to redefine an existing global variable.")
break
of opGetGlobal:
let name = readConstant().stringValue
let name = $readConstant().stringValue
if globals.hasKey(name):
stack.add(globals[name])
else:
runtimeError(&"Undefined global variable {name}.")
break
of opSetGlobal:
let name = readConstant().stringValue
let name = $readConstant().stringValue
if globals.hasKey(name):
globals[name] = stack.peek()
else:
@ -212,12 +216,12 @@ proc run*(chunk: Chunk): InterpretResult =
# opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3>
let argcount = readUI8()
let funct = stack[stack.high - argcount]
if funct.ndType != ndFunct:
let funct = stack.getIndexNeg(argcount)
if funct.ndType != ntFunct:
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
break
stack[stack.high - argcount] = toNdValue() # replace the function with nil: this is the return value slot
stack.setIndexNeg(argcount, toNdValue()) # replace the function with nil: this is the return value slot
# create new frame
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
@ -234,7 +238,11 @@ proc run*(chunk: Chunk): InterpretResult =
when profileInstructions:
for op in OpCode:
let dur = durations[op].inMilliseconds
echo &"OpCode: {op} total duration {dur} ms"
let times = runcounts[op]
echo &"OpCode: {op} total duration {dur} ms {times} times"
stack.destroyStack()
frames.destroyStack()
if hadError:
irRuntimeError