Fixed bugs with some corner cases in binary operations with strings and completed the shift towards manually allocated objects. Started to work on callframes and functions

This commit is contained in:
nocturn9x 2020-08-27 18:15:45 +02:00
parent 633c0f67f6
commit 4a1fc8df6d
10 changed files with 139 additions and 66 deletions

View File

@ -1,17 +1,26 @@
# Common module for the entire JAPL ecosystem. This file
# servers the only purpose of avoiding recursive dependencies
# Common functionality and objects shared across the entire JAPL ecosystem.
# This module exists to avoid recursive dependencies
import tables
import types/functiontype
import meta/valueobject
import meta/tokenobject
import meta/chunk
import meta/looptype
import types/objecttype
import meta/chunk
import types/functiontype
type
callFrame* = object
function*: ptr Function
ip*: int
slots*: ptr Value
VM* = object
chunk*: Chunk
frames*: seq[callFrame]
ip*: int
stack*: seq[Value]
stackTop*: int
@ -19,20 +28,13 @@ type
globals*: Table[string, Value]
lastPop*: Value
Lexer* = ref object
source*: string
tokens*: seq[Token]
line*: int
start*: int
current*: int
errored*: bool
Local* = ref object
name*: Token
depth*: int
Compiler* = object
function*: ptr Function
context*: FunctionType
locals*: seq[Local]
localCount*: int
scopeDepth*: int
@ -49,9 +51,3 @@ type
proc initParser*(tokens: seq[Token]): Parser =
result = Parser(current: 0, tokens: tokens, hadError: false, panicMode: false)
proc initCompiler*(vm: VM): Compiler =
result = Compiler(parser: initParser(@[]), function: newFunction(), locals: @[], scopeDepth: 0, localCount: 0, loop: Loop(alive: false, loopEnd: -1), vm: vm)

View File

@ -115,8 +115,9 @@ proc statement(self: var Compiler)
proc declaration(self: var Compiler)
proc endCompiler(self: var Compiler) =
proc endCompiler(self: var Compiler): ptr Function =
self.emitByte(OP_RETURN)
return self.function
proc parsePrecedence(self: var Compiler, precedence: Precedence) =
@ -193,11 +194,17 @@ proc unary(self: var Compiler, canAssign: bool) =
return
template markObject*(self, obj: untyped): untyped =
obj.next = self.vm.objects
self.vm.objects = obj
obj
proc strVal(self: var Compiler, canAssign: bool) =
var str = self.parser.previous().lexeme
var delimiter = &"{str[0]}"
str = str.unescape(delimiter, delimiter)
self.emitConstant(Value(kind: OBJECT, obj: newString(str)))
self.emitConstant(Value(kind: OBJECT, obj: self.markObject(newString(str))))
proc bracketAssign(self: var Compiler, canAssign: bool) =
@ -271,11 +278,11 @@ proc synchronize(self: var Compiler) =
proc identifierConstant(self: var Compiler, tok: Token): uint8 =
return self.makeConstant(Value(kind: OBJECT, obj: newString(tok.lexeme)))
return self.makeConstant(Value(kind: OBJECT, obj: self.markObject(newString(tok.lexeme))))
proc identifierLongConstant(self: var Compiler, tok: Token): array[3, uint8] =
return self.makeLongConstant(Value(kind: OBJECT, obj: newString(tok.lexeme)))
return self.makeLongConstant(Value(kind: OBJECT, obj: self.markObject(newString(tok.lexeme))))
proc addLocal(self: var Compiler, name: Token) =
@ -694,12 +701,22 @@ proc getRule(kind: TokenType): ParseRule =
result = rules[kind]
proc compile*(self: var Compiler, source: string): bool =
proc compile*(self: var Compiler, source: string): ptr Function =
var scanner = initLexer(source)
var tokens = scanner.lex()
if len(tokens) > 1 and not scanner.errored:
self.parser = initParser(tokens)
while not self.parser.match(EOF):
self.declaration()
self.endCompiler()
return not self.parser.hadError
var function = self.endCompiler()
if not self.parser.hadError:
return function
else:
return nil
else:
return nil
proc initCompiler*(vm: var VM, context: FunctionType): Compiler =
result = Compiler(parser: initParser(@[]), function: nil, locals: @[], scopeDepth: 0, localCount: 0, loop: Loop(alive: false, loopEnd: -1), vm: vm, context: context)
result.locals.add(Local(depth: 0, name: Token(kind: EOF, lexeme: "")))
result.function = result.markObject(newFunction())

View File

@ -10,7 +10,6 @@ import system
import strutils
import strformat
import tables
import common
import meta/tokentype
import meta/tokenobject
import meta/valueobject
@ -39,9 +38,17 @@ const RESERVED = to_table({
"this": TokenType.THIS, "super": TokenType.SUPER,
"del": TokenType.DEL, "break": TokenType.BREAK,
"continue": TokenType.CONTINUE})
type
Lexer* = ref object
source*: string
tokens*: seq[Token]
line*: int
start*: int
current*: int
errored*: bool
proc initLexer*(source: string): Lexer =
func initLexer*(source: string): Lexer =
result = Lexer(source: source, tokens: @[], line: 1, start: 0, current: 0, errored: false)

View File

@ -16,9 +16,11 @@ proc repl(debug: bool = false) =
source = readLine(stdin)
except IOError:
echo ""
bytecodeVM.freeVM()
break
except KeyboardInterrupt:
echo ""
bytecodeVM.freeVM()
break
if source == "":
continue
@ -27,6 +29,7 @@ proc repl(debug: bool = false) =
if debug:
echo &"Result: {result}"
proc main(file: string = "", debug: bool = false) =
if file == "":
repl(debug)
@ -48,6 +51,7 @@ proc main(file: string = "", debug: bool = false) =
var result = bytecodeVM.interpret(source, debug)
if debug:
echo &"Result: {result}"
bytecodeVM.freeVM()
when isMainModule:

View File

@ -7,8 +7,9 @@
# and some parts of the Virtual Machine) will use nim's GC
import types/objecttype
import segfaults
import types/objecttype
import common
proc reallocate*(pointer: pointer, oldSize: int, newSize: int): pointer =
@ -26,10 +27,14 @@ template resizeArray*(kind: untyped, pointer: pointer, oldCount, newCount: int):
cast[ptr kind](reallocate(pointer, sizeof(kind) * oldCount, sizeof(kind) * newCount))
template freeArray*(kind: untyped, pointer: ptr, oldCount: int): untyped =
template freeArray*(kind: untyped, pointer: pointer, oldCount: int): untyped =
reallocate(pointer, sizeof(kind) * oldCount, 0)
template free*(kind: untyped, pointer: pointer): untyped =
reallocate(pointer, sizeof(kind), 0)
template growCapacity*(capacity: int): untyped =
if capacity < 8:
8
@ -37,14 +42,15 @@ template growCapacity*(capacity: int): untyped =
capacity * 2
template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped =
cast[ptr castTo](reallocate(nil, 0, sizeof(sizeTo) * count))
proc allocateObject*(size: int, kind: ObjectTypes): ptr Obj =
result = cast[ptr Obj](reallocate(nil, 0, size))
result.kind = kind
template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped =
cast[ptr castTo](reallocate(nil, 0, sizeof(sizeTo) * count))
template allocateObj*(kind: untyped, objType: ObjectTypes): untyped =
cast[ptr kind](allocateObject(sizeof kind, objType))

View File

@ -1,6 +1,7 @@
import valueobject
type
OpCode* = enum
OP_CONSTANT = 0u8,
@ -68,3 +69,4 @@ proc addConstant*(chunk: var Chunk, constant: Value): int =
proc writeConstant*(chunk: var Chunk, constant: Value): array[3, uint8] =
let index = chunk.addConstant(constant)
result = cast[array[3, uint8]](index)

View File

@ -1,10 +1,10 @@
# This module represents the generic interface that JAPL uses internally
# to represent types. Small-sized entities such as numbers and booleans are
# treated differently with respect to bigger and more complex ones such as
# strings and functions. That is because, at least when we have our own GC,
# those more complex entities will be allocated on the heap, while the simpler
# ones will live on the stack
# strings and functions. That is because those more comolex entities are
# allocated on the heap, while the simpler ones live on the stack
# import ../types/functiontype
import ../types/objecttype
import ../types/stringtype
import strformat
@ -14,7 +14,7 @@ import strutils
type
ValueTypes* = enum # All possible value types (this is the VM's notion of 'type', not the end user's)
INTEGER, DOUBLE, BOOL, NIL, OBJECT
Value* = ref object
Value* = object
case kind*: ValueTypes
of INTEGER:
intValue*: int
@ -26,6 +26,7 @@ type
discard
of OBJECT:
obj*: ptr Obj
ValueArray* = ref object
values*: seq[Value]
@ -65,6 +66,7 @@ func isObj*(value: Value): bool =
func isStr*(value: Value): bool =
result = isObj(value) and value.obj.kind == ObjectTypes.STRING
func toBool*(value: Value): bool =
result = value.boolValue
@ -109,11 +111,12 @@ func asBool*(b: bool): Value =
result = Value(kind: BOOL, boolValue: b)
proc asStr*(s: string): Value =
result = Value(kind: OBJECT, obj: newString(s))
proc stringify*(value: Value): string =
func stringify*(value: Value): string =
case value.kind:
of INTEGER:
result = $value.toInt()

View File

@ -5,23 +5,25 @@
# code objects that can be compiled inside the JAPL runtime, pretty much
# like in Python
import objecttype
import stringtype
import ../meta/chunk
import strformat
import ../memory
import ../meta/chunk
type Function* = object of Obj
name*: ptr String
arity*: int
chunk*: Chunk
type
Function* = object of Obj
name*: ptr String
arity*: int
chunk*: Chunk
FunctionType* = enum
FUNC, SCRIPT
proc newFunction*(name: string = "", chunk: Chunk = initChunk(), arity: int = 0): ptr Function =
result = allocateObj(Function, ObjectTypes.FUNCTION)
result.name = nil
result.name = newString(name)
result.arity = arity
result.chunk = chunk

View File

@ -13,7 +13,7 @@ type String* = object of Obj
proc stringify*(s: String): string =
result = "\"" & $s.str & "\""
$s.str
proc isFalsey*(s: String): bool =

View File

@ -3,6 +3,7 @@ import strformat
import math
import lenientops
import common
import compiler
import tables
import util/debug
import meta/chunk
@ -10,7 +11,8 @@ import meta/valueobject
import types/exceptions
import types/objecttype
import types/stringtype
import compiler
import types/functiontype
import memory
proc `**`(a, b: int): int = pow(a.float, b.float).int
@ -19,13 +21,13 @@ proc `**`(a, b: int): int = pow(a.float, b.float).int
proc `**`(a, b: float): float = pow(a, b)
type KeyboardInterrupt* = object of CatchableError
type
KeyboardInterrupt* = object of CatchableError
type InterpretResult = enum
OK,
COMPILE_ERROR,
RUNTIME_ERROR
InterpretResult = enum
OK,
COMPILE_ERROR,
RUNTIME_ERROR
func handleInterrupt() {.noconv.} =
@ -51,6 +53,12 @@ proc peek*(self: var VM, distance: int): Value =
return self.stack[len(self.stack) - distance - 1]
template markObject*(self, obj: untyped): untyped =
obj.next = self.objects
self.objects = obj
obj
proc slice(self: var VM): bool =
var idx = self.pop()
var peeked = self.pop()
@ -70,7 +78,7 @@ proc slice(self: var VM): bool =
if idx.toInt() - 1 > len(str) - 1:
self.error(newIndexError("string index out of bounds"))
return false
self.push(Value(kind: OBJECT, obj: newString(&"{str[idx.toInt()]}")))
self.push(Value(kind: OBJECT, obj: self.markObject(newString(&"{str[idx.toInt()]}"))))
return true
else:
@ -113,7 +121,7 @@ proc sliceRange(self: var VM): bool =
elif sliceStart.toInt() > sliceEnd.toInt():
self.error(newIndexError("the start index can't be bigger than the end index"))
return false
self.push(Value(kind: OBJECT, obj: newString(str[sliceStart.toInt()..<sliceEnd.toInt()])))
self.push(Value(kind: OBJECT, obj: self.markObject(newString(str[sliceStart.toInt()..<sliceEnd.toInt()]))))
return true
else:
@ -217,9 +225,9 @@ proc run(self: var VM, debug, repl: bool): InterpretResult =
of OP_ADD:
if self.peek(0).isObj() and self.peek(1).isObj():
if self.peek(0).isStr() and self.peek(1).isStr():
var r = self.peek(0).toStr()
var l = self.peek(1).toStr()
self.push(Value(kind: OBJECT, obj: newString(l & r)))
var r = self.pop().toStr()
var l = self.pop().toStr()
self.push(Value(kind: OBJECT, obj: self.markObject(newString(l & r))))
else:
self.error(newTypeError(&"Unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
@ -232,17 +240,17 @@ proc run(self: var VM, debug, repl: bool): InterpretResult =
of OP_MULTIPLY:
if self.peek(0).isInt() and self.peek(1).isObj():
if self.peek(1).isStr():
var r = self.peek(0).toInt()
var l = self.peek(1).toStr()
self.push(Value(kind: OBJECT, obj: newString(l.repeat(r))))
var r = self.pop().toInt()
var l = self.pop().toStr()
self.push(Value(kind: OBJECT, obj: self.markObject(newString(l.repeat(r)))))
else:
self.error(newTypeError(&"Unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
elif self.peek(0).isObj() and self.peek(1).isInt():
if self.peek(0).isStr():
var r = self.peek(0).toStr()
var l = self.peek(1).toInt()
self.push(Value(kind: OBJECT, obj: newString(r.repeat(l))))
var r = self.pop().toStr()
var l = self.pop().toInt()
self.push(Value(kind: OBJECT, obj: self.markObject(newString(r.repeat(l)))))
else:
self.error(newTypeError(&"Unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
@ -374,14 +382,42 @@ proc run(self: var VM, debug, repl: bool): InterpretResult =
return OK
proc freeObject(obj: ptr Obj) =
case obj.kind:
of ObjectTypes.STRING:
var str = cast[ptr String](obj)
discard freeArray(char, str.str, str.len)
discard free(ObjectTypes.STRING, obj)
of ObjectTypes.FUNCTION:
var fun = cast[ptr Function](obj)
fun.chunk.freeChunk()
discard free(ObjectTypes.FUNCTION, fun)
else:
discard
proc freeObjects(self: var VM) =
var obj = self.objects
var next: ptr Obj
while obj != nil:
next = obj[].next
freeObject(obj)
obj = next
proc freeVM*(self: var VM) =
unsetControlCHook()
try:
self.freeObjects()
except NilAccessError:
echo "MemoryError: could not manage memory, exiting"
quit(71)
proc interpret*(self: var VM, source: string, debug: bool = false, repl: bool = false): InterpretResult =
var compiler = initCompiler(self)
var compiler = initCompiler(self, SCRIPT)
setControlCHook(handleInterrupt)
if not compiler.compile(source) or compiler.parser.hadError:
if compiler.compile(source) == nil:
return COMPILE_ERROR
self.chunk = compiler.function.chunk
self.ip = 0
@ -400,4 +436,4 @@ proc resetStack*(self: var VM) =
proc initVM*(): VM =
result = VM(chunk: initChunk(), ip: 0, stack: @[], stackTop: 0, objects: nil, globals: initTable[string, Value](), lastPop: Value(kind: NIL))
result = VM(chunk: nil, ip: 0, stack: @[], stackTop: 0, objects: nil, globals: initTable[string, Value](), lastPop: Value(kind: NIL))