Attempt to fix function calls by using ref seqs (failed, lol). Now all math operations on nan raise a TypeError

This commit is contained in:
nocturn9x 2020-12-26 17:01:03 +01:00
parent 9e9dc3ac75
commit 1e8b4632e9
18 changed files with 1675 additions and 1563 deletions

View File

@ -102,8 +102,7 @@ def build(path: str, flags: Dict[str, str] = {}, options: Dict[str, bool] = {},
if os.path.isfile(config_path) and not override:
logging.warning(f"A config file exists at '{config_path}', keeping it")
else:
if os.path.isfile(config_path):
logging.warning(f"Overriding config file at '{config_path}'")
logging.warning(f"Overriding config file at '{config_path}'")
logging.debug(f"Generating config file at '{config_path}'")
try:
with open(config_path, "w") as build_config:

View File

@ -23,12 +23,16 @@ import lexer
import meta/opcode
import meta/token
import meta/looptype
import types/jobject
import types/baseObject
import types/function
import types/numbers
import types/japlString
import tables
import config
when isMainModule:
import util/debug
type
Compiler* = ref object
## The state of the compiler
@ -76,7 +80,7 @@ type
proc makeRule(prefix, infix: ParseFn, precedence: Precedence): ParseRule =
## Creates a new rule for parsing
return ParseRule(prefix: prefix, infix: infix, precedence: precedence)
result = ParseRule(prefix: prefix, infix: infix, precedence: precedence)
proc advance(self: var Parser): Token =
@ -152,7 +156,6 @@ proc emitByte(self: Compiler, byt: OpCode|uint8) =
## to the current chunk being compiled
when DEBUG_TRACE_COMPILER:
echo "Compiler.emitByte byt:" & $byt & " (uint8 value of " & $(uint8 byt) & ")"
self.currentChunk.writeChunk(uint8 byt, self.parser.previous.line)
@ -309,7 +312,7 @@ proc strVal(self: Compiler, canAssign: bool) =
var str = self.parser.previous().lexeme
var delimiter = &"{str[0]}" # TODO: Add proper escape sequences support
str = str.unescape(delimiter, delimiter)
self.emitConstant(self.markObject(jobject.asStr(str)))
self.emitConstant(self.markObject(asStr(str)))
proc bracketAssign(self: Compiler, canAssign: bool) =
@ -322,7 +325,8 @@ proc bracket(self: Compiler, canAssign: bool) =
## or someList[5]. Slices can take up to two arguments, a start
## and an end index in the chosen iterable.
## Both arguments are optional, so doing "hi"[::]
## will basically copy the string into a new object.
## will basically copy the string (gets everything from
## start to end of the iterable).
## Indexes start from 0, and while the start index is
## inclusive, the end index is not. If an end index is
## not specified--like in "hello"[0:]--, then the it is
@ -437,13 +441,13 @@ proc synchronize(self: Compiler) =
proc identifierConstant(self: Compiler, tok: Token): uint8 =
## Emits instructions for identifiers
return self.makeConstant(self.markObject(jobject.asStr(tok.lexeme)))
return self.makeConstant(self.markObject(asStr(tok.lexeme)))
proc identifierLongConstant(self: Compiler, tok: Token): array[3, uint8] =
## Same as identifierConstant, but this is used when the constant table is longer
## than 255 elements
return self.makeLongConstant(self.markObject(jobject.asStr(tok.lexeme)))
return self.makeLongConstant(self.markObject(asStr(tok.lexeme)))
proc addLocal(self: Compiler, name: Token) =
@ -536,8 +540,8 @@ proc resolveLocal(self: Compiler, name: Token): int =
proc namedVariable(self: Compiler, tok: Token, canAssign: bool) =
## Handles local and global variables assignment, as well
## as variable resolution.
var arg = self.resolveLocal(tok)
var
var
arg = self.resolveLocal(tok)
get: OpCode
set: OpCode
if arg != -1:
@ -558,9 +562,10 @@ proc namedLongVariable(self: Compiler, tok: Token, canAssign: bool) =
## Handles local and global variables assignment, as well
## as variable resolution. This is only called when the constants
## table's length exceeds 255
var arg = self.resolveLocal(tok)
var casted = cast[array[3, uint8]](arg)
var
var
arg = self.resolveLocal(tok)
casted = cast[array[3, uint8]](arg)
get: OpCode
set: OpCode
if arg != -1:
@ -672,6 +677,7 @@ proc endScope(self: Compiler) =
if start >= self.localCount:
self.locals.delete(self.localCount, start)
proc emitJump(self: Compiler, opcode: OpCode): int =
## Emits a jump instruction with a placeholder offset
## that is later patched, check patchJump for more info
@ -696,12 +702,13 @@ proc patchJump(self: Compiler, offset: int) =
## be jumped over, so the size of the if/else conditions
## or loops is limited (hopefully 65 thousands and change
## instructions are enough for everyone)
var jump = self.currentChunk.code.len - offset - 2
let jump = self.currentChunk.code.len - offset - 2
if jump > (int uint16.high):
self.compileError("too much code to jump over")
else:
self.currentChunk.code[offset] = uint8 (jump shr 8) and 0xff
self.currentChunk.code[offset + 1] = uint8 jump and 0xff
let casted = cast[array[2, uint8]](jump)
self.currentChunk.code[offset] = casted[0]
self.currentChunk.code[offset + 1] = casted[1]
proc ifStatement(self: Compiler) =
@ -1062,7 +1069,7 @@ var rules: array[TokenType, ParseRule] = [
makeRule(literal, nil, Precedence.None), # TRUE
makeRule(nil, nil, Precedence.None), # VAR
makeRule(nil, nil, Precedence.None), # WHILE
makeRule(nil, nil, Precedence.None), # DEL # TODO: Fix del statement to make it GC-aware
makeRule(deleteVariable, nil, Precedence.None), # DEL
makeRule(nil, nil, Precedence.None), # BREAK
makeRule(nil, nil, Precedence.None), # EOF
makeRule(nil, nil, Precedence.None), # TokenType.COLON
@ -1135,9 +1142,9 @@ proc initCompiler*(context: FunctionType, enclosing: Compiler = nil, parser: Par
result.parser.file = file
result.locals.add(Local(depth: 0, name: Token(kind: EOF, lexeme: "")))
inc(result.localCount)
result.function = result.markObject(newFunction())
result.function = result.markObject(newFunction(chunk=newChunk()))
if context != SCRIPT: # If we're compiling a function, we give it its name
result.function.name = jobject.asStr(enclosing.parser.previous().lexeme)
result.function.name = asStr(enclosing.parser.previous().lexeme)
# This way the compiler can be executed on its own
# without the VM

View File

@ -23,9 +23,9 @@
import segfaults
# import config
import config
# when DEBUG_TRACE_ALLOCATION:
# import util/debug # TODO: Recursive dependency
# import util/debug # TODO: Add memory debugging
proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer =

View File

@ -13,10 +13,12 @@
# limitations under the License.
## Implementation of JAPL call frames. A call frame
## is a subset of the VM's stack that represent
## functions' local space
## is a subset of the VM's stack that represents
## function-local space
import ../types/jobject
import ../types/function
import ../types/baseObject
{.experimental: "implicitDeref".}
type
@ -24,16 +26,11 @@ type
function*: ptr Function
ip*: int
slot*: int
endSlot*: int
stack*: seq[ptr Obj]
stack*: ref seq[ptr Obj]
proc getView*(self: CallFrame): seq[ptr Obj] =
result = self.stack[self.slot..self.endSlot]
proc getAbsIndex(self: CallFrame, idx: int, offset: int): int =
return idx + len(self.getView()) - offset
result = self.stack[self.slot..self.stack.high()]
proc len*(self: CallFrame): int =
@ -41,16 +38,16 @@ proc len*(self: CallFrame): int =
proc `[]`*(self: CallFrame, idx: int, offset: int): ptr Obj =
result = self.stack[self.getAbsIndex(idx, offset)]
result = self.stack[idx + self.slot]
proc `[]=`*(self: CallFrame, idx: int, offset: int, val: ptr Obj) =
if idx < self.slot:
raise newException(IndexError, "CallFrame index out of range")
self.stack[self.getAbsIndex(idx, offset)] = val
self.stack[idx + self.slot] = val
proc delete*(self: CallFrame, idx: int, offset: int) =
if idx < self.slot or idx > self.endSlot:
if idx < self.slot:
raise newException(IndexError, "CallFrame index out of range")
self.stack.delete(self.getAbsIndex(idx, offset))
self.stack.delete(idx + self.slot)

View File

@ -12,12 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
## The module dedicated to the Chunk type
## A chunk is a piece of bytecode.
## Implementation for bytecode chunks and the VM's opcodes
## A chunk is a piece of bytecode together with its constants
import ../types/baseObject
import ../types/jobject
type
Chunk* = ref object # TODO: This shouldn't be here, but Function needs it. Consider refactoring
## A piece of bytecode.
## Consts represents the constants table the code is referring to
## Code is the compiled bytecode
## Lines maps bytecode instructions to line numbers (1 to 1 correspondence)
consts*: seq[ptr Obj]
code*: seq[uint8]
lines*: seq[int] # TODO: Run-length encoding
OpCode* {.pure.} = enum
## Enum of possible opcodes
Constant = 0u8,
@ -80,6 +89,11 @@ const byteInstructions* = {OpCode.SetLocal, OpCode.GetLocal, OpCode.DeleteLocal,
const jumpInstructions* = {OpCode.JumpIfFalse, OpCode.Jump, OpCode.Loop}
proc newChunk*(): Chunk =
## Initializes a new, empty chunk
result = Chunk(consts: @[], code: @[], lines: @[])
proc writeChunk*(self: Chunk, newByte: uint8, line: int) =
## Appends newByte at line to a chunk.
self.code.add(newByte)

View File

@ -15,21 +15,22 @@
# WIP - Not working
import ../meta/valueobject
import ../memory
import stringtype
import objecttype
import lenientops
import baseObject
import methods
import japlNil
import japlString
const HASH_MAP_LOAD_MAX = 0.75 # Hash map max load factor
type
Entry = ref object
key*: Value
value*: Value
HashMap = ref object of Obj
key*: ptr Obj
value*: ptr Obj
HashMap = object of Obj
size*: int
capacity*: int
entries*: ptr UncheckedArray[Entry]
@ -46,29 +47,29 @@ proc freeHashMap(self: var HashMap) =
self.entries = nil
proc findEntry(self: HashMap, key: Value): Entry =
var idx = (int key.hash) mod self.capacity
proc findEntry(self: HashMap, key: ptr Obj): Entry =
var idx = (int key.hashValue) mod self.capacity
var entry: Entry
while true:
entry = self.entries[idx]
if valuesEqual(entry.key, key) or entry.key == nil:
if entry.key.eq(key) or entry.key == nil:
result = entry
break
idx = idx + 1 mod self.capacity
proc adjustCapacity(self: HashMap, capacity: int) =
var entries = cast[ptr UncheckedArray[Entry]](allocate(Entry, capacity))
proc adjustCapacity(self: var HashMap, capacity: int) =
var entries = allocate(UncheckedArray[Entry], Entry, capacity)
var i = 0
while i < capacity:
entries[i].key = nil
entries[i].value = Value(kind: NIL)
entries[i].value = cast[ptr Obj](asNil())
i += 1
self.entries = entries
self.capacity = capacity
proc setEntry(self: HashMap, key: Value, value: Value): bool =
proc setEntry(self: var HashMap, key: ptr Obj, value: ptr Obj): bool =
if self.size + 1 > self.capacity * HASH_MAP_LOAD_MAX:
var capacity = growCapacity(self.capacity)
self.adjustCapacity(capacity)
@ -79,3 +80,9 @@ proc setEntry(self: HashMap, key: Value, value: Value): bool =
entry.key = key
entry.value = value
result = isNewKey
when isMainModule:
var dictionary = newHashMap()
discard dictionary.setEntry(asObj[String]("helo".asStr()), asObj[String]("world".asStr()))
echo dictionary.findEntry(asObj[String]("helo".asStr())).value.toStr()

62
src/types/baseObject.nim Normal file
View File

@ -0,0 +1,62 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ../memory
type
NotImplementedError* = object of CatchableError
## Raised when a given operation is unsupported
## on a given type
ObjectType* {.pure.} = enum
## All the possible object types
String, Exception, Function,
Class, Module, BaseObject,
Integer, Float, Bool, NotANumber,
Infinity, Nil
Obj* = object of RootObj
## The base object for all
## JAPL types. Every object
## in JAPL implicitly inherits
## from this base type
kind*: ObjectType
hashValue*: uint64
isHashable*: bool # This is false for unhashable objects
## Object constructors and allocators
proc allocateObject*(size: int, kind: ObjectType): ptr Obj =
## Wrapper around memory.reallocate to create a new generic JAPL object
result = cast[ptr Obj](reallocate(nil, 0, size))
result.kind = kind
template allocateObj*(kind: untyped, objType: ObjectType): untyped =
## Wrapper around allocateObject to cast a generic object
## to a more specific type
cast[ptr kind](allocateObject(sizeof kind, objType))
proc newObj*(): ptr Obj =
## Allocates a generic JAPL object
result = allocateObj(Obj, ObjectType.BaseObject)
proc asObj*(self: ptr Obj): ptr Obj =
## Casts a specific JAPL object into a generic
## pointer to Obj
result = cast[ptr Obj](self)

51
src/types/boolean.nim Normal file
View File

@ -0,0 +1,51 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import baseObject
import numbers
type
Bool* = object of Integer
## A boolean object
boolValue*: bool # If the boolean is true or false
proc asBool*(b: bool): ptr Bool =
## Converts a nim bool into a JAPL bool
result = allocateObj(Bool, ObjectType.Bool)
result.boolValue = b
result.isHashable = true
proc typeName*(self: ptr Bool): string =
result = "boolean"
proc stringify*(self: ptr Bool): string =
result = $self.boolValue
proc isFalsey*(self: ptr Bool): bool =
result = not self.boolValue
proc eq*(self, other: ptr Bool): bool =
result = self.boolValue == other.boolValue
proc toBool*(obj: ptr Obj): bool =
## Converts a JAPL bool to a nim bool
result = cast[ptr Bool](obj).boolValue

60
src/types/exception.nim Normal file
View File

@ -0,0 +1,60 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import baseObject
import japlString
type
JAPLException* = object of Obj # TODO: Create exceptions subclasses
## The base exception object
errName*: ptr String # TODO: Ditch error name in favor of inheritance-based types
message*: ptr String
proc newIndexError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "IndexError".asStr()
result.message = message.asStr()
proc newReferenceError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "ReferenceError".asStr()
result.message = message.asStr()
proc newInterruptedError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "InterruptedError".asStr()
result.message = message.asStr()
proc newRecursionError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "RecursionError".asStr()
result.message = message.asStr()
proc newTypeError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "TypeError".asStr()
result.message = message.asStr()
proc stringify*(self: ptr JAPLException): string =
result = self.errName.toStr() & ": " & self.message.toStr()

66
src/types/function.nim Normal file
View File

@ -0,0 +1,66 @@
import baseObject
import ../meta/opcode
import japlString
type
FunctionType* {.pure.} = enum
## All code in JAPL is compiled
## as if it was inside some sort
## of function. To differentiate
## between actual functions and
## the top-level code, this tiny
## enum is used to tell the two
## contexts apart when compiling
Func, Script
Function* = object of Obj
## A function object
name*: ptr String
arity*: int # The number of required parameters
optionals*: int # The number of optional parameters
defaults*: seq[ptr Obj] # List of default arguments, in order
chunk*: Chunk # The function's body
proc newFunction*(name: string = "", chunk: Chunk, arity: int = 0): ptr Function =
## Allocates a new function object with the given
## bytecode chunk and arity. If the name is an empty string
## (the default), the function will be an
## anonymous code object
# TODO: Add lambdas
# TODO: Add support for optional parameters
result = allocateObj(Function, ObjectType.Function)
if name.len > 1:
result.name = name.asStr()
else:
result.name = nil
result.arity = arity
result.chunk = chunk
result.isHashable = false
proc typeName*(self: ptr Function): string =
result = "function"
proc stringify*(self: ptr Function): string =
if self.name != nil:
result = "<function '" & self.name.toStr() & "'>"
else:
result = "<code object>"
proc isFalsey*(self: ptr Function): bool =
result = false
proc hash*(self: ptr Function): uint64 =
# TODO: Hashable?
raise newException(NotImplementedError, "unhashable type 'function'")
proc eq*(self, other: ptr Function): bool =
result = self.name.stringify() == other.name.stringify() # Since in JAPL functions cannot
# be overridden, if two function names are equal they are also the same
# function object (TODO: Verify this)

48
src/types/japlNil.nim Normal file
View File

@ -0,0 +1,48 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import boolean
import baseObject
type
Nil* = object of Bool
## A nil object
proc asNil*(): ptr Nil =
## Creates a nil object
result = allocateObj(Nil, ObjectType.Nil)
proc typeName*(self: ptr Nil): string =
result = "nil"
proc stringify*(self: ptr Nil): string =
result = "nil"
proc isFalsey*(self: ptr Nil): bool =
result = true
proc hash*(self: ptr Nil): uint64 =
# TODO: Arbitrary hash seems a bad idea
result = 2u
proc eq*(self, other: ptr Nil): bool =
result = true

98
src/types/japlString.nim Normal file
View File

@ -0,0 +1,98 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Implementation for string objects
import baseObject
import numbers
import ../memory
import strutils
type
String* = object of Obj
## A string object
str*: ptr UncheckedArray[char] # TODO -> Unicode support
len*: int
proc toStr*(obj: ptr Obj): string =
## Converts a JAPL string into a nim string
var strObj = cast[ptr String](obj)
for i in 0..strObj.str.len - 1:
result.add(strObj.str[i])
proc hash*(self: ptr String): uint64 =
result = 2166136261u
var i = 0
while i < self.len:
result = result xor uint64 self.str[i]
result *= 16777619
i += 1
return result
proc asStr*(s: string): ptr String =
## Converts a nim string into a
## JAPL string
result = allocateObj(String, ObjectType.String)
result.str = allocate(UncheckedArray[char], char, len(s))
for i in 0..len(s) - 1:
result.str[i] = s[i]
result.len = len(s)
result.hashValue = result.hash()
result.isHashable = true
proc isFalsey*(self: ptr String): bool =
result = self.len == 0
proc stringify*(self: ptr String): string =
result = self.toStr()
proc typeName*(self: ptr String): string =
return "string"
proc eq*(self, other: ptr String): bool =
if self.len != other.len:
return false
elif self.hash != other.hash:
return false
for i in 0..self.len - 1:
if self.str[i] != other.str[i]:
return false
result = true
proc sum*(self: ptr String, other: ptr Obj): ptr String =
if other.kind == ObjectType.String:
var other = cast[ptr String](other)
var selfStr = self.toStr()
var otherStr = other.toStr()
result = (selfStr & otherStr).asStr()
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr String, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = self.toStr().repeat(cast[ptr Integer](other).toInt()).asStr()
else:
raise newException(NotImplementedError, "")

File diff suppressed because it is too large Load Diff

458
src/types/methods.nim Normal file
View File

@ -0,0 +1,458 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Implementations for dispatcher methods of JAPL objects.
## This modules serves to avoid recursive dependencies
import baseObject
import japlString
import function
import boolean
import japlNil
import numbers
proc typeName*(self: ptr Obj): string =
## Returns the name of the object's type
case self.kind:
of ObjectType.BaseObject:
result = "object"
of ObjectType.String:
result = cast[ptr String](self).typeName()
of ObjectType.Integer:
result = cast[ptr Integer](self).typeName()
of ObjectType.Float:
result = cast[ptr Float](self).typeName()
of ObjectType.Bool:
result = cast[ptr Bool](self).typeName()
of ObjectType.Function:
result = cast[ptr Function](self).typeName()
of ObjectType.Infinity:
result = cast[ptr Infinity](self).typeName()
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).typeName()
of ObjectType.Nil:
result = cast[ptr Nil](self).typeName()
else:
discard # TODO
proc stringify*(self: ptr Obj): string =
## Returns a string representation of the
## given object
case self.kind:
of ObjectType.BaseObject:
result = "<object>"
of ObjectType.String:
result = cast[ptr String](self).stringify()
of ObjectType.Integer:
result = cast[ptr Integer](self).stringify()
of ObjectType.Float:
result = cast[ptr Float](self).stringify()
of ObjectType.Bool:
result = cast[ptr Bool](self).stringify()
of ObjectType.Function:
result = cast[ptr Function](self).stringify()
of ObjectType.Infinity:
result = cast[ptr Infinity](self).stringify()
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).stringify()
of ObjectType.Nil:
result = cast[ptr Nil](self).stringify()
else:
discard # TODO
proc hash*(self: ptr Obj): uint64 =
## Returns the hash of the object using
## the FNV-1a algorithm (or a predefined value).
## Raises an error if the object is not hashable
if not self.isHashable:
raise newException(NotImplementedError, "")
case self.kind:
of ObjectType.BaseObject:
result = 2166136261u # Constant hash
of ObjectType.String:
result = cast[ptr String](self).hash()
of ObjectType.Integer:
result = uint64 cast[ptr Integer](self).hash()
of ObjectType.Float:
result = cast[ptr Float](self).hash()
of ObjectType.Bool:
let b = cast[ptr Bool](self)
if b.boolValue:
result = uint64 1
else:
result = uint64 0
of ObjectType.Function:
result = cast[ptr Function](self).hash()
of ObjectType.Infinity:
result = cast[ptr Infinity](self).hash()
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).hash()
of ObjectType.Nil:
result = cast[ptr Nil](self).hash()
else:
discard # TODO
proc isFalsey*(self: ptr Obj): bool =
## Returns true if the object is
## falsey, false otherwise
case self.kind:
of ObjectType.BaseObject:
result = false
of ObjectType.String:
result = cast[ptr String](self).isFalsey()
of ObjectType.Integer:
result = cast[ptr Integer](self).isFalsey()
of ObjectType.Float:
result = cast[ptr Float](self).isFalsey()
of ObjectType.Bool:
result = cast[ptr Bool](self).isFalsey()
of ObjectType.Function:
result = cast[ptr Function](self).isFalsey()
of ObjectType.Infinity:
result = cast[ptr Infinity](self).isFalsey()
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).isFalsey()
of ObjectType.Nil:
result = cast[ptr Nil](self).isFalsey()
else:
discard # TODO
proc eq*(self, other: ptr Obj): bool =
## Compares two objects for equality,
## returns true if self equals other
## and false otherwise
if self.kind != other.kind: # If the types are different it's not
# even worth going any further (and we couldn't anyway)
return false
case self.kind:
of ObjectType.BaseObject:
result = other.kind == ObjectType.BaseObject
of ObjectType.String:
var self = cast[ptr String](self)
var other = cast[ptr String](other)
result = self.eq(other)
of ObjectType.Integer:
var self = cast[ptr Integer](self)
var other = cast[ptr Integer](other)
result = self.eq(other)
of ObjectType.Float:
var self = cast[ptr Float](self)
var other = cast[ptr Float](other)
result = self.eq(other)
of ObjectType.Bool:
var self = cast[ptr Bool](self)
var other = cast[ptr Bool](other)
result = self.eq(other)
of ObjectType.Function:
var self = cast[ptr Function](self)
var other = cast[ptr Function](other)
result = self.eq(other)
of ObjectType.Infinity:
var self = cast[ptr Infinity](self)
var other = cast[ptr Infinity](other)
result = self.eq(other)
of ObjectType.NotANumber:
var self = cast[ptr NotANumber](self)
var other = cast[ptr NotANumber](other)
result = self.eq(other)
of ObjectType.Nil:
var self = cast[ptr Nil](self)
var other = cast[ptr Nil](other)
result = self.eq(other)
else:
discard # TODO
proc negate*(self: ptr Obj): ptr Obj =
## Returns the result of -self or
## raises an error if the operation
## is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).negate()
of ObjectType.Float:
result = cast[ptr Float](self).negate()
of ObjectType.Infinity:
result = cast[ptr Infinity](self).negate()
else:
raise newException(NotImplementedError, "")
proc sum*(self, other: ptr Obj): ptr Obj =
## Returns the result of self + other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.String: # Here we don't cast other (yet)
# because binary operators can mix types together,
# like in "hello" * 5, or 3.5 * 8. Casting that
# later allows for finer error reporting and keeps
# these methods as generic as possible
result = cast[ptr String](self).sum(other)
of ObjectType.Integer:
result = cast[ptr Integer](self).sum(other)
of ObjectType.Float:
result = cast[ptr Float](self).sum(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).sum(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).sum(other)
else:
raise newException(NotImplementedError, "")
proc sub*(self, other: ptr Obj): ptr Obj =
## Returns the result of self - other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).sub(other)
of ObjectType.Float:
result = cast[ptr Float](self).sub(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).sub(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).sub(other)
else:
raise newException(NotImplementedError, "")
proc mul*(self, other: ptr Obj): ptr Obj =
## Returns the result of self * other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).mul(other)
of ObjectType.Float:
result = cast[ptr Float](self).mul(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).mul(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).mul(other)
of ObjectType.String:
result = cast[ptr String](self).mul(other)
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self, other: ptr Obj): ptr Obj =
## Returns the result of self / other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).trueDiv(other)
of ObjectType.Float:
result = cast[ptr Float](self).trueDiv(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).trueDiv(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).trueDiv(other)
else:
raise newException(NotImplementedError, "")
proc pow*(self, other: ptr Obj): ptr Obj =
## Returns the result of self ** other (exponentiation)
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).pow(other)
of ObjectType.Float:
result = cast[ptr Float](self).pow(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).pow(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).pow(other)
else:
raise newException(NotImplementedError, "")
proc divMod*(self, other: ptr Obj): ptr Obj =
## Returns the result of self % other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).divMod(other)
of ObjectType.Float:
result = cast[ptr Float](self).divMod(other)
of ObjectType.NotANumber:
result = cast[ptr NotANumber](self).divMod(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).divMod(other)
else:
raise newException(NotImplementedError, "")
proc binaryAnd*(self, other: ptr Obj): ptr Obj =
## Returns the result of self & other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryAnd(cast[ptr Integer](other))
else:
raise newException(NotImplementedError, "")
proc binaryOr*(self, other: ptr Obj): ptr Obj =
## Returns the result of self | other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryOr(cast[ptr Integer](other))
else:
raise newException(NotImplementedError, "")
proc binaryXor*(self, other: ptr Obj): ptr Obj =
## Returns the result of self ^ other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryXor(cast[ptr Integer](other))
else:
raise newException(NotImplementedError, "")
proc binaryShr*(self, other: ptr Obj): ptr Obj =
## Returns the result of self >> other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryShr(cast[ptr Integer](other))
else:
raise newException(NotImplementedError, "")
proc binaryShl*(self, other: ptr Obj): ptr Obj =
## Returns the result of self << other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryShl(cast[ptr Integer](other))
else:
raise newException(NotImplementedError, "")
proc binaryNot*(self: ptr Obj): ptr Obj =
## Returns the result of self ~other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).binaryNot()
else:
raise newException(NotImplementedError, "")
proc lt*(self: ptr Obj, other: ptr Obj): bool =
## Returns the result of self < other or
## raises an error if the operation
## is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).lt(other)
of ObjectType.Float:
result = cast[ptr Float](self).lt(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).lt(other)
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Obj, other: ptr Obj): bool =
## Returns the result of self > other or
## raises an error if the operation
## is unsupported
case self.kind:
of ObjectType.Integer:
result = cast[ptr Integer](self).gt(other)
of ObjectType.Float:
result = cast[ptr Float](self).gt(other)
of ObjectType.Infinity:
result = cast[ptr Infinity](self).gt(other)
else:
raise newException(NotImplementedError, "")
## Utilities to inspect JAPL objects
proc objType*(obj: ptr Obj): ObjectType =
## Returns the type of the object
result = obj.kind
proc isCallable*(obj: ptr Obj): bool =
## Returns true if the given object
## is callable, false otherwise
result = obj.kind in {ObjectType.Function, ObjectType.Class}
proc isNil*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL nil object
result = obj.kind == ObjectType.Nil
proc isBool*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL bool
result = obj.kind == ObjectType.Bool
proc isInt*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL integer
result = obj.kind == ObjectType.Integer
proc isFloat*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL float
result = obj.kind == ObjectType.Float
proc isInf*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL inf object
result = obj.kind == ObjectType.Infinity
proc isNan*(obj: ptr Obj): bool =
## Returns true if the given obj
## is a JAPL nan object
result = obj.kind == ObjectType.NotANumber
proc isNum*(obj: ptr Obj): bool =
## Returns true if the given obj is
## either a JAPL number, infinity or nan.
## Note to JavaScript developers: No, in JAPL
## nan is not a number. Here we consider it like
## a number because internally it's easier to
## represent it like that for methods that perform
## binary operations on numbers, since 2 * nan is
## valid JAPL code and will yield nan
result = isInt(obj) or isFloat(obj) or isInf(obj) or isNan(obj)
proc isStr*(obj: ptr Obj): bool =
## Returns true if the given object is a JAPL string
result = obj.kind == ObjectType.String

656
src/types/numbers.nim Normal file
View File

@ -0,0 +1,656 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import baseObject
import bitops
import math
# Custom operators for exponentiation
proc `**`(a, b: int): int = pow(a.float, b.float).int
proc `**`(a, b: float): float = pow(a, b)
type
Integer* = object of Obj
## An integer object
intValue*: int # TODO: Bignum arithmetic
Float* = object of Integer
## A float object
floatValue*: float
Infinity* = object of Float # Inf is considered a float
## An inf object
isNegative*: bool # This differentiates inf and -inf
NotANumber* = object of Float # NaN is a subclass of float (as per IEEE 754 spec)
## A nan object
proc toInt*(obj: ptr Obj): int =
## Converts a JAPL int to a nim int
result = cast[ptr Integer](obj).intValue
proc toFloat*(obj: ptr Obj): float =
## Converts a JAPL float to a nim float
result = cast[ptr Float](obj).floatValue
proc asInt*(n: int): ptr Integer =
## Converts a nim int into a JAPL int
result = allocateObj(Integer, ObjectType.Integer)
result.intValue = n
result.isHashable = true
proc asFloat*(n: float): ptr Float =
## Converts a nim float into a JAPL float
result = allocateObj(Float, ObjectType.Float)
result.floatValue = n
result.isHashable = true
proc asNan*(): ptr NotANumber =
## Creates a nan object
result = allocateObj(NotANumber, ObjectType.NotANumber)
proc asInf*(): ptr Infinity =
## Creates an inf object
result = allocateObj(Infinity, ObjectType.Infinity)
proc typeName*(self: ptr NotANumber): string =
result = "nan"
proc typeName*(self: ptr Infinity): string =
result = "infinity"
proc typeName*(self: ptr Float): string =
result = "float"
proc stringify*(self: ptr NotANumber): string =
result = "nan"
proc hash*(self: ptr NotANumber): uint64 =
# TODO: Hashable?
raise newException(NotImplementedError, "unhashable type 'nan'")
proc eq*(self, other: ptr NotANumber): bool =
result = false # As per IEEE 754 spec, nan != nan
proc sum*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc stringify*(self: ptr Infinity): string =
if self.isNegative:
result = "-inf"
else:
result = "inf"
proc isFalsey*(self: ptr Infinity): bool =
result = false
proc hash*(self: ptr Infinity): uint64 =
# TODO: Arbitrary hash seems a bad idea
if self.isNegative:
result = 1u
else:
result = 0u
proc negate*(self: ptr Infinity): ptr Infinity =
result = self
if self.isNegative:
result.isNegative = false
else:
result.isNegative = true
proc eq*(self, other: ptr Infinity): bool =
result = self.isNegative == other.isNegative
proc lt*(self: ptr Infinity, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
let other = cast[ptr Integer](other)
if self.isNegative and other.intValue > 0:
result = true
else:
result = false
of ObjectType.Float:
let other = cast[ptr Float](other)
if self.isNegative and other.floatValue > 0.0:
result = true
else:
result = false
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative and not self.isNegative:
result = false
else:
result = false
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Infinity, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
let other = cast[ptr Integer](other)
if self.isNegative and other.intValue > 0:
result = false
else:
result = true
of ObjectType.Float:
let other = cast[ptr Float](other)
if self.isNegative and other.floatValue > 0.0:
result = false
else:
result = true
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative and not self.isNegative:
result = true
else:
result = false
else:
raise newException(NotImplementedError, "")
proc sum*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc stringify*(self: ptr Float): string =
result = $self.floatValue
proc isFalsey*(self: ptr Float): bool =
result = self.floatValue == 0.0
proc hash*(self: ptr Float): uint64 =
result = 2166136261u xor uint64 self.floatValue # TODO: Improve this
result *= 16777619
proc eq*(self, other: ptr Float): bool =
result = self.floatValue == other.floatValue
proc negate*(self: ptr Float): ptr Float =
result = (-self.toFloat()).asFloat()
proc typeName*(self: ptr Integer): string =
result = "integer"
proc stringify*(self: ptr Integer): string =
result = $self.intValue
proc isFalsey*(self: ptr Integer): bool =
result = self.intValue == 0
proc eq*(self, other: ptr Integer): bool =
result = self.intValue == other.intValue
proc negate*(self: ptr Integer): ptr Integer =
result = (-self.toInt()).asInt()
proc hash*(self: ptr Integer): uint64 =
result = uint64 self.intValue
proc lt*(self: ptr Integer, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
result = self.intValue < cast[ptr Integer](other).intValue
of ObjectType.Float:
result = (float self.intValue) < cast[ptr Float](other).floatValue
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = false
else:
result = true
else:
raise newException(NotImplementedError, "")
proc lt*(self: ptr Float, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
result = self.floatValue < (float cast[ptr Integer](other).intValue)
of ObjectType.Float:
result = self.floatValue < cast[ptr Float](other).floatValue
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = false
else:
result = true
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Integer, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
result = self.intValue > cast[ptr Integer](other).intValue
of ObjectType.Float:
result = (float self.intValue) > cast[ptr Float](other).floatValue
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = true
else:
result = false
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Float, other: ptr Obj): bool =
case other.kind:
of ObjectType.Integer:
result = self.floatValue > (float cast[ptr Integer](other).intValue)
of ObjectType.Float:
result = self.floatValue > cast[ptr Float](other).floatValue
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = true
else:
result = false
else:
raise newException(NotImplementedError, "")
proc sum*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
case other.kind:
of ObjectType.Integer:
result = (self.toInt() + cast[ptr Integer](other).toInt()).asInt()
of ObjectType.Float:
let res = ((float self.toInt()) + cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc sum*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() + float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() + cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
case other.kind:
of ObjectType.Integer:
result = (self.toInt() - cast[ptr Integer](other).toInt()).asInt()
of ObjectType.Float:
let res = ((float self.toInt()) - cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc sub*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() - float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() - cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc mul*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
case other.kind:
of ObjectType.Integer:
result = (self.toInt() * cast[ptr Integer](other).toInt()).asInt()
of ObjectType.Float:
let res = ((float self.toInt()) * cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() * float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() * cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Integer, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = ((float self.toInt()) / (float cast[ptr Integer](other).toInt())).asFloat() # so that 4 / 2 == 2.0
of ObjectType.Float:
let res = ((float self.toInt()) / cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() / float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() / cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc pow*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
case other.kind:
of ObjectType.Integer:
result = (self.toInt() ** cast[ptr Integer](other).toInt()).asInt()
of ObjectType.Float:
let res = ((float self.toInt()) ** cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() ** float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() ** cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
case other.kind:
of ObjectType.Integer:
result = (self.toInt() mod cast[ptr Integer](other).toInt()).asInt()
of ObjectType.Float:
let res = ((float self.toInt()) mod cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Float, other: ptr Obj): ptr Obj =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() mod float cast[ptr Integer](other).toInt()).asFloat()
of ObjectType.Float:
let res = (self.toFloat() mod cast[ptr Float](other).toFloat())
if res == system.Inf:
result = asInf()
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
else:
result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
else:
raise newException(NotImplementedError, "")
proc binaryAnd*(self, other: ptr Integer): ptr Integer =
result = bitand(self.toInt(), other.toInt()).asInt()
proc binaryOr*(self, other: ptr Integer): ptr Integer =
result = bitor(self.toInt(), other.toInt()).asInt()
proc binaryNot*(self: ptr Integer): ptr Integer =
result = bitnot(self.toInt()).asInt()
proc binaryXor*(self, other: ptr Integer): ptr Integer =
result = bitxor(self.toInt(), other.toInt()).asInt()
proc binaryShr*(self, other: ptr Integer): ptr Integer =
result = (self.toInt() shr other.toInt()).asInt()
proc binaryShl*(self, other: ptr Integer): ptr Integer =
result = (self.toInt() shr other.toInt()).asInt()

View File

@ -16,7 +16,8 @@
## screen.
import ../meta/opcode
import ../types/jobject
import ../types/baseObject
import ../types/methods
import strformat
@ -52,8 +53,9 @@ proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
proc jumpInstruction(name: string, chunk: Chunk, offset: int): int =
var jump = uint16 (chunk.code[offset + 1] shr 8)
jump = jump or chunk.code[offset + 2]
var jumpArray: array[2, uint8] = [chunk.code[offset + 1], chunk.code[offset + 2]]
var jump: int
copyMem(jump.addr, unsafeAddr(jumpArray), sizeof(uint16))
echo &"\tInstruction at IP: {name}\n\tJump offset: {jump}\n"
return offset + 3

View File

@ -14,6 +14,8 @@
## A stack-based bytecode virtual machine implementation.
## This is the entire runtime environment for JAPL
{.experimental: "implicitDeref".}
import algorithm
import strformat
@ -22,7 +24,14 @@ import compiler
import tables
import meta/opcode
import meta/frame
import types/jobject
import types/baseObject
import types/japlString
import types/japlNil
import types/exception
import types/numbers
import types/boolean
import types/methods
import types/function
import memory
import tables
when DEBUG_TRACE_VM:
@ -44,7 +53,7 @@ type
frameCount*: int
source*: string
frames*: seq[CallFrame]
stack*: seq[ptr Obj]
stack*: ref seq[ptr Obj]
stackTop*: int
objects*: seq[ptr Obj]
globals*: Table[string, ptr Obj]
@ -61,7 +70,7 @@ func handleInterrupt() {.noconv.} =
proc resetStack*(self: var VM) =
## Resets the VM stack to a blank state
self.stack = @[]
self.stack = new(seq[ptr Obj])
self.frames = @[]
self.frameCount = 0
self.stackTop = 0
@ -116,6 +125,8 @@ proc pop*(self: var VM): ptr Obj =
proc push*(self: var VM, obj: ptr Obj) =
## Pushes an object onto the stack
self.stack.add(obj)
if obj notin self.objects and obj notin self.cached:
self.objects.add(obj)
self.stackTop += 1
@ -158,13 +169,13 @@ proc slice(self: var VM): bool =
self.error(newIndexError("string index out of bounds"))
return false
else:
self.push(self.addObject(jobject.asStr(&"{str[index]}")))
self.push(asStr(&"{str[index]}"))
return true
else:
self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'"))
return false
# TODO: Move this to jobject.nim
# TODO: Move this to types/
proc sliceRange(self: var VM): bool =
## Handles slices when there's both a start
## and an end index (even implicit ones)
@ -189,14 +200,14 @@ proc sliceRange(self: var VM): bool =
if startIndex < 0:
sliceStart = (len(str) + sliceEnd.toInt()).asInt()
elif startIndex - 1 > len(str) - 1:
self.push(self.addObject(jobject.asStr("")))
self.push(asStr(""))
return true
if endIndex - 1 > len(str) - 1:
sliceEnd = len(str).asInt()
if startIndex > endIndex:
self.push(self.addObject(jobject.asStr("")))
self.push(asStr(""))
return true
self.push(self.addObject(jobject.asStr(str[sliceStart.toInt()..<sliceEnd.toInt()])))
self.push(asStr(str[sliceStart.toInt()..<sliceEnd.toInt()]))
return true
else:
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))
@ -213,7 +224,7 @@ proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
if self.frameCount == FRAMES_MAX:
self.error(newRecursionError("max recursion depth exceeded"))
return false
var frame = CallFrame(function: function, ip: 0, slot: argCount, endSlot: self.stackTop - 1, stack: self.stack) # TODO:
var frame = CallFrame(function: function, ip: 0, slot: argCount, stack: self.stack) # TODO:
# Check why this raises NilAccessError when high recursion limit is hit
self.frames.add(frame)
self.frameCount += 1
@ -233,7 +244,7 @@ proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool =
return false
proc readByte(self: CallFrame): byte =
proc readByte(self: CallFrame): uint8 =
## Reads a single byte from the given
## frame's chunk of bytecode
inc(self.ip)
@ -244,23 +255,20 @@ proc readBytes(self: CallFrame): int =
## Reads and decodes 3 bytes from the
## given frame's chunk into an integer
var arr = [self.readByte(), self.readByte(), self.readByte()]
var index: int
copyMem(index.addr, unsafeAddr(arr), sizeof(arr))
result = index
copyMem(result.addr, unsafeAddr(arr), sizeof(arr))
proc readShort(self: CallFrame): uint16 =
## Reads a 16 bit number from the
## given frame's chunk
inc(self.ip)
inc(self.ip)
cast[uint16]((self.function.chunk.code[self.ip - 2] shl 8) or self.function.chunk.code[self.ip - 1])
let arr = [self.readByte(), self.readByte()]
copyMem(result.addr, unsafeAddr(arr), sizeof(uint16))
proc readConstant(self: CallFrame): ptr Obj =
## Reads a constant from the given
## frame's constant table
result = self.function.chunk.consts[int(self.readByte())]
result = self.function.chunk.consts[uint8 self.readByte()]
proc readLongConstant(self: CallFrame): ptr Obj =
@ -322,106 +330,111 @@ proc run(self: var VM, repl: bool): InterpretResult =
of OpCode.ConstantLong:
self.push(frame.readLongConstant())
of OpCode.Negate:
let operand = self.pop()
try:
self.push(self.pop().negate())
self.push(operand.negate())
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported unary operator '-' for object of type '{operand.typeName()}'"))
return RuntimeError
of OpCode.Shl: # Bitwise left-shift
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.binaryShl(right)))
self.push(left.binaryShl(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '<<' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Shr: # Bitwise right-shift
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.binaryShr(right)))
self.push(left.binaryShr(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '>>' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Xor: # Bitwise xor
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.binaryXor(right)))
self.push(left.binaryXor(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '^' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Bor: # Bitwise or
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.binaryOr(right)))
self.push(left.binaryOr(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '&' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Bnot: # Bitwise not
var operand = self.pop()
try:
self.push(self.addObject(self.pop().binaryNot()))
self.push(operand.binaryNot())
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported unary operator '~' for object of type '{operand.typeName()}'"))
return RuntimeError
of OpCode.Band: # Bitwise and
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.binaryAnd(right)))
self.push(left.binaryAnd(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '&' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Add:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.sum(right)))
self.push(left.sum(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '+' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Subtract:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.sub(right)))
self.push(left.sub(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '-' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Divide:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.trueDiv(right)))
self.push(left.trueDiv(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '/' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Multiply:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.mul(right)))
self.push(left.mul(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '*' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Mod:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.divMod(right)))
self.push(left.divMod(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '%' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Pow:
var right = self.pop()
var left = self.pop()
try:
self.push(self.addObject(left.pow(right)))
self.push(left.pow(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '**' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.True:
## TODO: Make sure that even operations that can yield
## preallocated types, but do not have access to the VM,
## yield these cached types
self.push(cast[ptr Bool](self.getBoolean(true)))
of OpCode.False:
self.push(cast[ptr Bool](self.getBoolean(false)))
@ -445,7 +458,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
try:
self.push(self.getBoolean(left.lt(right)))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '<' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Greater:
var right = self.pop()
@ -453,7 +466,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
try:
self.push(self.getBoolean(left.gt(right)))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
self.error(newTypeError(&"unsupported binary operator '>' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.GetItem:
# TODO: More generic method
@ -500,7 +513,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
self.globals[constant] = self.peek(0)
discard self.pop()
of OpCode.DeleteGlobal:
# This opcode, as well as DeleteLocal, is currently unused due to potential issues with the GC
# TODO: Inspect potential issues with the GC
if frame.function.chunk.consts.len > 255:
var constant = frame.readLongConstant().toStr()
if constant notin self.globals:
@ -527,7 +540,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
frame[int frame.readByte(), stackOffset] = self.peek(0)
discard self.pop()
of OpCode.DeleteLocal:
# Unused due to GC potential issues
# TODO: Inspect potential issues with the GC
if frame.len > 255:
var slot = frame.readBytes()
frame.delete(slot, stackOffset)
@ -553,11 +566,10 @@ proc run(self: var VM, repl: bool): InterpretResult =
discard
of OpCode.Return:
var retResult = self.pop()
if repl:
if not self.lastPop.isNil() and self.frameCount == 1:
# This avoids unwanted output with recursive calls
echo stringify(self.lastPop)
self.lastPop = asNil()
if repl and not self.lastPop.isNil() and self.frameCount == 1:
# This avoids unwanted output with recursive calls
echo stringify(self.lastPop)
self.lastPop = cast[ptr Nil](self.cached[2])
self.frameCount -= 1
discard self.frames.pop()
if self.frameCount == 0:
@ -568,27 +580,33 @@ proc run(self: var VM, repl: bool): InterpretResult =
frame = self.frames[self.frameCount - 1]
proc freeObject(obj: ptr Obj) =
proc freeObject(self: VM, obj: ptr Obj) =
## Frees the associated memory
## of an object
case obj.kind:
of ObjectType.String:
var str = cast[ptr String](obj)
when DEBUG_TRACE_ALLOCATION:
echo &"DEBUG: Freeing string object with value '{stringify(str)}' of length {str.len}"
echo &"DEBUG: Freeing string object of length {str.len}"
discard freeArray(char, str.str, str.len)
discard free(ObjectType.String, obj)
of ObjectType.Exception, ObjectType.Class,
ObjectType.Module, ObjectType.BaseObject, ObjectType.Integer,
ObjectType.Float, ObjectType.Bool, ObjectType.NotANumber,
ObjectType.Infinity, ObjectType.Nil:
echo &"DEBUG: Freeing {obj.typeName()} object with value '{stringify(obj)}'"
when DEBUG_TRACE_ALLOCATION:
if obj notin self.cached:
echo &"DEBUG: Freeing {obj.typeName()} object with value '{stringify(obj)}'"
else:
echo &"DEBUG: Freeing cached {obj.typeName()} object with value '{stringify(obj)}'"
discard free(obj.kind, obj)
of ObjectType.Function:
var fun = cast[ptr Function](obj)
when DEBUG_TRACE_ALLOCATION:
echo &"DEBUG: Freeing function object with value '{stringify(fun)}'"
if fun.name == nil:
echo &"DEBUG: Freeing global code object"
else:
echo &"DEBUG: Freeing function object with name '{stringify(fun)}'"
fun.chunk.freeChunk()
discard free(ObjectType.Function, fun)
@ -596,26 +614,32 @@ proc freeObject(obj: ptr Obj) =
proc freeObjects(self: var VM) =
## Frees all the allocated objects
## from the VM
var objCount = len(self.objects) + len(self.cached)
var runtimeObjCount = len(self.objects)
var cacheCount = len(self.cached)
var runtimeFreed = 0
var cachedFreed = 0
for obj in reversed(self.objects):
freeObject(obj)
self.freeObject(obj)
discard self.objects.pop()
runtimeFreed += 1
for cached_obj in self.cached:
freeObject(cached_obj)
self.freeObject(cached_obj)
cachedFreed += 1
when DEBUG_TRACE_ALLOCATION:
echo &"DEBUG: Freed {objCount} objects"
echo &"DEBUG: Freed {runtimeFreed + cachedFreed} objects out of {runtimeObjCount + cacheCount} ({cachedFreed}/{cacheCount} cached objects, {runtimeFreed}/{runtimeObjCount} runtime objects)"
proc freeVM*(self: var VM) =
## Tears down the VM
when DEBUG_TRACE_ALLOCATION:
echo "\nDEBUG: Freeing all allocated memory before exiting"
unsetControlCHook()
try:
self.freeObjects()
except NilAccessError:
stderr.write("A fatal error occurred -> could not free memory, segmentation fault\n")
quit(71)
when DEBUG_TRACE_ALLOCATION:
if self.objects.len > 0:
echo &"DEBUG: Warning, {self.objects.len} objects were not freed"
proc initCache(self: var VM) =
@ -623,11 +647,11 @@ proc initCache(self: var VM) =
## such as nil, true, false and nan
self.cached =
[
cast[ptr Obj](true.asBool()),
cast[ptr Obj](false.asBool()),
cast[ptr Obj](asNil()),
cast[ptr Obj](asInf()),
cast[ptr Obj](asNan())
true.asBool().asObj(),
false.asBool().asObj(),
asNil().asObj(),
asInf().asObj(),
asNan().asObj()
]
@ -646,7 +670,7 @@ proc interpret*(self: var VM, source: string, repl: bool = false, file: string):
var compiled = compiler.compile(source)
self.source = source
self.file = file
self.objects = compiler.objects # TODO:
self.objects = self.objects & compiler.objects # TODO:
# revisit the best way to transfer marked objects from the compiler
# to the vm
if compiled == nil: