mirror of https://github.com/japl-lang/japl.git
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:
parent
92e91f2c17
commit
ff0ec33991
|
@ -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
|
||||
|
||||
|
|
|
@ -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] =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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"
|
||||
|
|
111
nim/vm.nim
111
nim/vm.nim
|
@ -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))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue