mirror of https://github.com/japl-lang/japl.git
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:
parent
633c0f67f6
commit
4a1fc8df6d
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ type String* = object of Obj
|
|||
|
||||
|
||||
proc stringify*(s: String): string =
|
||||
result = "\"" & $s.str & "\""
|
||||
$s.str
|
||||
|
||||
|
||||
proc isFalsey*(s: String): bool =
|
||||
|
|
78
nim/vm.nim
78
nim/vm.nim
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue