Strings are now implemented in terms of an array of char and are no longer garbage collected by nim automatically

This commit is contained in:
nocturn9x 2020-08-23 23:48:38 +02:00
parent 92e91f2c17
commit ff0ec33991
8 changed files with 124 additions and 98 deletions

View File

@ -7,7 +7,6 @@ import meta/tokenobject
import meta/valueobject
import meta/tokentype
import meta/looptype
import types/objecttype
import types/stringtype
import types/functiontype

View File

@ -14,7 +14,6 @@ import meta/tokentype
import meta/tokenobject
import meta/valueobject
import types/stringtype
import types/objecttype
const TOKENS = to_table({
@ -114,15 +113,19 @@ proc parseString(self: var Lexer, delimiter: char) =
proc parseNumber(self: var Lexer) =
while isDigit(self.peek()):
discard self.step()
if self.peek() == '.':
discard self.step()
while self.peek().isDigit():
try:
if self.peek() == '.':
discard self.step()
var value = Value(kind: ValueTypes.DOUBLE, floatValue: parseFloat(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.NUMBER, value))
else:
var value = Value(kind: ValueTypes.INTEGER, intValue: parseInt(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.NUMBER, value))
while self.peek().isDigit():
discard self.step()
var value = Value(kind: ValueTypes.DOUBLE, floatValue: parseFloat(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.NUMBER, value))
else:
var value = Value(kind: ValueTypes.INTEGER, intValue: parseInt(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.NUMBER, value))
except ValueError:
echo "OverflowError: integer is too big"
self.errored = true
proc parseIdentifier(self: var Lexer) =
@ -187,7 +190,7 @@ proc scanToken(self: var Lexer) =
self.tokens.add(self.createToken(TOKENS[single], Value(kind: ValueTypes.OBJECT, obj: newString(&"{single}"))))
else:
self.errored = true
echo &"SyntaxError: Unexpected character '{single}' at {self.line}"
echo &"SyntaxError: Unexpected character '{single}' at line {self.line}"
proc lex*(self: var Lexer): seq[Token] =

View File

@ -11,7 +11,7 @@ import types/objecttype
proc reallocate*(pointer: pointer, oldSize: int, newSize: int): pointer =
if newSize == 0:
if newSize == 0 and pointer != nil:
dealloc(pointer)
return nil
var res = realloc(pointer, newSize)

View File

@ -8,6 +8,7 @@
import ../types/objecttype
import ../types/stringtype
import strformat
import strutils
type
@ -61,6 +62,9 @@ func isObj*(value: Value): bool =
result = value.kind == OBJECT
func isStr*(value: Value): bool =
result = isObj(value) and value.obj.kind == ObjectTypes.STRING
func toBool*(value: Value): bool =
result = value.boolValue
@ -73,6 +77,18 @@ func toFloat*(value: Value): float =
result = value.floatValue
func typeName*(value: Value): string =
case value.kind:
of BOOL, NIL, DOUBLE, INTEGER:
result = ($value.kind).toLowerAscii()
of OBJECT:
case value.obj.kind:
of ObjectTypes.STRING:
result = cast[String](value.obj).typeName()
else:
result = value.obj.typeName()
func toStr*(value: Value): string =
var strObj = cast[String](value.obj)
var c = ""
@ -122,17 +138,24 @@ func isFalsey*(value: Value): bool =
result = true
func valuesEqual*(a: Value, b: Value): bool =
proc valuesEqual*(a: Value, b: Value): bool =
if a.kind != b.kind:
result = false
case a.kind:
of BOOL:
result = a.toBool() == b.toBool()
of NIL:
result = true
of INTEGER:
result = a.toInt() == b.toInt()
of DOUBLE:
result = a.toFloat() == b.toFloat()
of OBJECT:
result = valuesEqual(a.obj, b.obj)
else:
case a.kind:
of BOOL:
result = a.toBool() == b.toBool()
of NIL:
result = true
of INTEGER:
result = a.toInt() == b.toInt()
of DOUBLE:
result = a.toFloat() == b.toFloat()
of OBJECT:
case a.obj.kind:
of ObjectTypes.STRING:
var a = cast[String](a.obj)
var b = cast[String](b.obj)
result = valuesEqual(a, b)
else:
result = valuesEqual(a.obj, b.obj)

View File

@ -1,5 +1,6 @@
import objecttype
import stringtype
import strformat
type JAPLException* = ref object of Obj
@ -7,19 +8,22 @@ type JAPLException* = ref object of Obj
message*: String
func newTypeError*(message: string): JAPLException =
proc stringify*(self: JAPLException): string =
return &"{self.errName.str}: {self.message.str}"
proc newTypeError*(message: string): JAPLException =
result = JAPLException(kind: ObjectTypes.EXCEPTION, errName: newString("TypeError"), message: newString(message))
func newIndexError*(message: string): JAPLException =
proc newIndexError*(message: string): JAPLException =
result = JAPLException(kind: ObjectTypes.EXCEPTION, errName: newString("IndexError"), message: newString(message))
func newReferenceError*(message: string): JAPLException =
proc newReferenceError*(message: string): JAPLException =
result = JAPLException(kind: ObjectTypes.EXCEPTION, errName: newString("ReferenceError"), message: newString(message))
func newInterruptedError*(message: string): JAPLException =
proc newInterruptedError*(message: string): JAPLException =
result = JAPLException(kind: ObjectTypes.EXCEPTION, errName: newString("InterruptedError"), message: newString(message))

View File

@ -3,8 +3,6 @@
# be needed in the future when more and more default methods are added
# to objects, without having to add a (possibly redundant) implementation
# into each specific file in the types directory
import strutils
import strformat
type
@ -24,7 +22,7 @@ method stringify*(obj: Obj): string {.base.} =
method typeName*(obj: Obj): string {.base.} =
result = &"<class '{($obj.kind).toLowerAscii()}'>"
result = "object"
method isFalsey*(obj: Obj): bool {.base.} =

View File

@ -13,22 +13,23 @@ type String* = ref object of Obj
method stringify*(s: String): string =
result = ""
result = $s.str
method isFalsey*(s: String): bool =
result = s.len == 0
method valuesEqual*(a: String, b: String): bool =
if a.kind != b.kind:
result = false
else:
result = a.str == b.str
if a.len != b.len:
return false
for i in 0..a.len - 1:
if a.str[i] != b.str[i]:
return false
return true
proc newString*(str: string): ptr String =
result = cast[ptr String](allocateObject(sizeof(String), ObjectTypes.STRING))
proc newString*(str: string): String =
result = String()
var arrStr = cast[ptr UncheckedArray[char]](reallocate(nil, 0, sizeof(char) * len(str)))
var length = len(str)
for i in 0..len(str) - 1:
@ -37,6 +38,5 @@ proc newString*(str: string): ptr String =
result.len = length
let s = newString("o")
echo s.len
echo $s.str
proc typeName*(s: String): string =
return "string"

View File

@ -67,25 +67,24 @@ proc slice(self: VM): bool =
case peeked.kind:
of OBJECT:
case peeked.obj.kind:
of STRING:
var str = peeked.obj.str
if idx.kind != INTEGER:
of ObjectTypes.STRING:
var str = peeked.toStr()
if not idx.isInt():
self.error(newTypeError("string indeces must be integers"))
return false
elif idx.intValue - 1 > len(str) - 1:
elif idx.toInt() < 0:
idx.intValue = (len(str) - 1) - (-idx.toInt())
if idx.toInt() - 1 > len(str) - 1:
self.error(newIndexError("string index out of bounds"))
return false
elif idx.intValue < 0:
self.error(newIndexError("string index out of bounds"))
return false
self.push(Value(kind: OBJECT, obj: Obj(kind: STRING, str: &"{str[idx.intValue]}")))
self.push(Value(kind: OBJECT, obj: newString(&"{str[idx.toInt()]}")))
return true
else:
self.error(newTypeError(&"Unsupported slicing for object of type '{toLowerAscii($(peeked.kind))}'"))
self.error(newTypeError(&"Unsupported slicing for object of type '{peeked.typeName()}'"))
return false
else:
self.error(newTypeError(&"Unsupported slicing for object of type '{toLowerAscii($(peeked.kind))}'"))
self.error(newTypeError(&"Unsupported slicing for object of type '{peeked.typeName()}'"))
return false
@ -96,29 +95,29 @@ proc sliceRange(self: VM): bool =
case popped.kind:
of OBJECT:
case popped.obj.kind:
of STRING:
var str = popped.obj.str
if sliceEnd.kind == NIL:
sliceEnd = Value(kind: INTEGER, intValue: len(str) - 1)
if sliceStart.kind == NIL:
of ObjectTypes.STRING:
var str = popped.toStr()
if sliceEnd.isNil():
sliceEnd = Value(kind: INTEGER, intValue: len(str))
if sliceStart.isNil():
sliceStart = Value(kind: INTEGER, intValue: 0)
if sliceStart.kind != INTEGER or sliceEnd.kind != INTEGER:
if not sliceStart.isInt() or not sliceEnd.isInt():
self.error(newTypeError("string indeces must be integers"))
return false
elif sliceStart.intValue - 1 > len(str) - 1 or sliceEnd.intValue - 1 > len(str) - 1:
elif sliceStart.toInt() - 1 > len(str) - 1 or sliceEnd.toInt() - 1 > len(str) - 1:
self.error(newIndexError("string index out of bounds"))
return false
elif sliceStart.intValue < 0 or sliceEnd.intValue < 0:
elif sliceStart.toInt() < 0 or sliceEnd.toInt() < 0:
self.error(newIndexError("string index out of bounds"))
return false
self.push(Value(kind: OBJECT, obj: Obj(kind: STRING, str: str[sliceStart.intValue..sliceEnd.intValue])))
self.push(Value(kind: OBJECT, obj: newString(str[sliceStart.toInt()..<sliceEnd.toInt()])))
return true
else:
self.error(newTypeError(&"Unsupported slicing for object of type '{toLowerAscii($(popped.kind))}'"))
self.error(newTypeError(&"Unsupported slicing for object of type '{popped.typeName()}'"))
return false
else:
self.error(newTypeError(&"Unsupported slicing for object of type '{toLowerAscii($(popped.kind))}'"))
self.error(newTypeError(&"Unsupported slicing for object of type '{popped.typeName()}'"))
return false
@ -173,7 +172,7 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
else:
self.push(Value(kind: DOUBLE, floatValue: float tmp))
else:
self.error(newTypeError(&"Unsupported binary operand for objects of type '{toLowerAscii($(leftVal.kind))}' and '{toLowerAscii($(rightVal.kind))}'"))
self.error(newTypeError(&"Unsupported binary operator for objects of type '{leftVal.typeName()}' and '{rightVal.typeName()}'"))
return RUNTIME_ERROR
var instruction: uint8
var opcode: OpCode
@ -205,21 +204,21 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
var cur = self.pop()
case cur.kind:
of DOUBLE:
cur.floatValue = -cur.floatValue
cur.floatValue = -cur.toFloat()
self.push(cur)
of INTEGER:
cur.intValue = -cur.intValue
cur.intValue = -cur.toInt()
self.push(cur)
else:
echo &"Unsupported unary operator '-' for object of type '{toLowerAscii($cur.kind)}'"
self.error(newTypeError(&"Unsupported unary operator '-' for object of type '{cur.typeName()}'"))
of OP_ADD:
if self.peek(0).kind == OBJECT and self.peek(1).kind == OBJECT:
if self.peek(0).obj.kind == STRING and self.peek(1).obj.kind == STRING:
var r = self.peek(0).obj.str
var l = self.peek(1).obj.str
self.push(Value(kind: OBJECT, obj: Obj(kind: STRING, str: l & r)))
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)))
else:
self.error(newTypeError(&"Unsupported binary operand for objects of type '{toLowerAscii($(self.peek(0).kind))}' and '{toLowerAscii($(self.peek(1).kind))}'"))
self.error(newTypeError(&"Unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
else:
BinOp(`+`, isNum)
@ -228,21 +227,21 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
of OP_DIVIDE:
BinOp(`/`, isNum)
of OP_MULTIPLY:
if self.peek(0).kind == INTEGER and self.peek(1).kind == OBJECT:
if self.peek(1).obj.kind == STRING:
var r = self.peek(0).intValue
var l = self.peek(1).obj.str
self.push(Value(kind: OBJECT, obj: Obj(kind: STRING, str: l.repeat(r))))
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))))
else:
self.error(newTypeError(&"Unsupported binary operand for objects of type '{toLowerAscii($(self.peek(0).kind))}' and '{toLowerAscii($(self.peek(1).kind))}'"))
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).kind == OBJECT and self.peek(1).kind == INTEGER:
if self.peek(0).obj.kind == STRING:
var r = self.peek(0).obj.str
var l = self.peek(1).intValue
self.push(Value(kind: OBJECT, obj: Obj(kind: STRING, str: r.repeat(l))))
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))))
else:
self.error(newTypeError(&"Unsupported binary operand for objects of type '{toLowerAscii($(self.peek(0).kind))}' and '{toLowerAscii($(self.peek(1).kind))}'"))
self.error(newTypeError(&"Unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
else:
BinOp(`*`, isNum)
@ -261,10 +260,10 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
of OP_EQUAL:
var a = self.pop()
var b = self.pop()
if a.kind == DOUBLE and b.kind == INTEGER:
b = Value(kind: DOUBLE, floatValue: float b.intValue)
elif b.kind == DOUBLE and a.kind == INTEGER:
a = Value(kind: DOUBLE, floatValue: float a.intValue)
if a.isFloat() and b.isInt():
b = Value(kind: DOUBLE, floatValue: float b.toInt())
elif b.isFloat() and a.isInt():
a = Value(kind: DOUBLE, floatValue: float a.toInt())
self.push(Value(kind: BOOL, boolValue: valuesEqual(a, b)))
of OP_LESS:
BinOp(`<`, isNum)
@ -278,22 +277,22 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
return RUNTIME_ERROR
of OP_DEFINE_GLOBAL:
if self.chunk.consts.values.len > 255:
var constant = readLongConstant().obj.str
var constant = readLongConstant().toStr()
self.globals[constant] = self.peek(0)
else:
var constant = readConstant().obj.str
var constant = readConstant().toStr()
self.globals[constant] = self.peek(0)
discard self.pop() # This will help when we have a custom GC
of OP_GET_GLOBAL:
if self.chunk.consts.values.len > 255:
var constant = readLongConstant().obj.str
var constant = readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RUNTIME_ERROR
else:
self.push(self.globals[constant])
else:
var constant = readConstant().obj.str
var constant = readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RUNTIME_ERROR
@ -301,14 +300,14 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
self.push(self.globals[constant])
of OP_SET_GLOBAL:
if self.chunk.consts.values.len > 255:
var constant = readLongConstant().obj.str
var constant = readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
return RUNTIME_ERROR
else:
self.globals[constant] = self.peek(0)
else:
var constant = readConstant().obj.str
var constant = readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
return RUNTIME_ERROR
@ -316,14 +315,14 @@ proc run(self: VM, debug, repl: bool): InterpretResult =
self.globals[constant] = self.peek(0)
of OP_DELETE_GLOBAL:
if self.chunk.consts.values.len > 255:
var constant = readLongConstant().obj.str
var constant = readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RUNTIME_ERROR
else:
self.globals.del(constant)
else:
var constant = readConstant().obj.str
var constant = readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RUNTIME_ERROR
@ -399,6 +398,6 @@ proc resetStack*(self: VM) =
proc initVM*(): VM =
result = VM(chunk: initChunk(), ip: 0, stack: @[], stackTop: 0, objects: initSinglyLinkedList[Obj](), globals: initTable[string, Value](), lastPop: Value(kind: NIL), exitLoop: false)
result = VM(chunk: initChunk(), ip: 0, stack: @[], stackTop: 0, objects: initSinglyLinkedList[Obj](), globals: initTable[string, Value](), lastPop: Value(kind: NIL))