mirror of https://github.com/japl-lang/japl.git
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:
parent
9e9dc3ac75
commit
1e8b4632e9
3
build.py
3
build.py
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
|
@ -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
|
||||
|
|
@ -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
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
|
162
src/vm.nim
162
src/vm.nim
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue