Removed short version of constants for benchmarking purposes and refactored the type system's behavior to return more consistent values

This commit is contained in:
nocturn9x 2021-01-16 11:47:01 +01:00
parent 882efd28da
commit f8ac8d08cb
13 changed files with 651 additions and 513 deletions

View File

@ -214,27 +214,18 @@ proc emitBytes(self: Compiler, bytarr: array[3, uint8]) =
self.emitByte(bytarr[2])
proc makeConstant(self: Compiler, obj: ptr Obj): uint8 =
## Adds a constant (literal) to the current chunk's
## constants table
result = uint8 self.currentChunk.addConstant(obj)
proc makeLongConstant(self: Compiler, val: ptr Obj): array[3, uint8] =
proc makeConstant(self: Compiler, val: ptr Obj): array[3, uint8] =
## Does the same as makeConstant(), but encodes the index in the
## chunk's constant table as an array (which is later reconstructed
## into an integer at runtime) to store more than 256 constants in the table
result = self.currentChunk.writeConstant(val)
result = self.currentChunk.addConstant(val)
proc emitConstant(self: Compiler, obj: ptr Obj) =
## Emits a Constant or ConstantLong instruction along
## Emits a Constant instruction along
## with its operand
if self.currentChunk().consts.len > 255:
self.emitByte(OpCode.ConstantLong)
self.emitBytes(self.makeLongConstant(obj))
else:
self.emitBytes(OpCode.Constant, self.makeConstant(obj))
self.emitByte(OpCode.Constant)
self.emitBytes(self.makeConstant(obj))
proc initParser*(tokens: seq[Token], file: string): Parser
@ -252,7 +243,7 @@ proc parsePrecedence(self: Compiler, precedence: Precedence) =
else:
discard self.parser.advance()
var prefixRule = getRule(self.parser.previous.kind).prefix
if prefixRule == nil: # If there is no prefix rule than an expression is expected
if prefixRule == nil: # If there is no prefix rule then an expression is expected
self.parser.parseError(self.parser.previous, "Expecting expression")
return
var canAssign = precedence <= Precedence.Assign # This is used to detect invalid assignment targets
@ -463,17 +454,11 @@ proc grouping(self: Compiler, canAssign: bool) =
self.parser.consume(TokenType.RP, "Expecting ')' after parentheszed expression")
proc identifierConstant(self: Compiler, tok: Token): uint8 =
proc identifierConstant(self: Compiler, tok: Token): array[3, uint8] =
## Emits instructions for identifiers
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(asStr(tok.lexeme)))
proc addLocal(self: Compiler, name: Token) =
## Stores a local variable. Local name resolution
## happens at compile time rather than runtime,
@ -499,24 +484,13 @@ proc declareVariable(self: Compiler) =
self.addLocal(name)
proc parseVariable(self: Compiler, message: string): uint8 =
proc parseVariable(self: Compiler, message: string): array[3, uint8] =
## Parses variables and declares them
self.parser.consume(TokenType.ID, message)
self.declareVariable()
if self.scopeDepth > 0:
return uint8 0
return self.identifierConstant(self.parser.previous())
proc parseLongVariable(self: Compiler, message: string): array[3, uint8] =
## Parses variables and declares them. This is used in place
## of parseVariable when there's more than 255 constants
## in the chunk table
self.parser.consume(TokenType.ID, message)
self.declareVariable()
if self.scopeDepth > 0:
return [uint8 0, uint8 0, uint8 0]
return self.identifierLongConstant(self.parser.previous())
return self.identifierConstant(self.parser.previous())
proc markInitialized(self: Compiler) =
@ -527,17 +501,6 @@ proc markInitialized(self: Compiler) =
self.locals[self.localCount - 1].depth = self.scopeDepth
proc defineVariable(self: Compiler, idx: uint8) =
## Defines a variable, emitting appropriate
## instructions if we're in the local scope
## or marking the last local as initialized
## otherwise
if self.scopeDepth > 0:
self.markInitialized()
return
self.emitBytes(OpCode.DefineGlobal, idx)
proc defineVariable(self: Compiler, idx: array[3, uint8]) =
## Same as defineVariable, but this is used when
## there's more than 255 locals in the chunk's table
@ -563,30 +526,7 @@ 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)
get: OpCode
set: OpCode
if arg != -1:
get = OpCode.GetLocal
set = OpCode.SetLocal
else:
get = OpCode.GetGlobal
set = OpCode.SetGlobal
arg = int self.identifierConstant(tok)
if self.parser.match(TokenType.EQ) and canAssign:
self.expression()
self.emitBytes(set, uint8 arg)
else:
self.emitBytes(get, uint8 arg)
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
## as variable resolution
var
arg = self.resolveLocal(tok)
casted = cast[array[3, uint8]](arg)
@ -598,7 +538,7 @@ proc namedLongVariable(self: Compiler, tok: Token, canAssign: bool) =
else:
get = OpCode.GetGlobal
set = OpCode.SetGlobal
casted = self.identifierLongConstant(tok)
casted = self.identifierConstant(tok)
if self.parser.match(TokenType.EQ) and canAssign:
self.expression()
self.emitByte(set)
@ -608,37 +548,21 @@ proc namedLongVariable(self: Compiler, tok: Token, canAssign: bool) =
self.emitBytes(casted)
proc variable(self: Compiler, canAssign: bool) =
## Emits the code to declare a variable,
## both locally and globally
if self.locals.len < 255:
self.namedVariable(self.parser.previous(), canAssign)
else:
self.namedLongVariable(self.parser.previous(), canAssign)
self.namedVariable(self.parser.previous(), canAssign)
proc varDeclaration(self: Compiler) =
## Parses a variable declaration, taking into account
## the possibility that the chunk table could already
## be bigger than 255 elements
var shortName: uint8
var longName: array[3, uint8]
var useShort: bool = true
if self.currentChunk.consts.len < 255:
shortName = self.parseVariable("Expecting variable name")
else:
useShort = false
longName = self.parseLongVariable("Expecting variable name")
## Parses a variable declaration
var name: array[3, uint8] = self.parseVariable("Expecting variable name")
if self.parser.match(TokenType.EQ):
self.expression()
else:
self.emitByte(OpCode.Nil)
self.parser.consume(TokenType.SEMICOLON, "Missing semicolon after var declaration")
if useShort:
self.defineVariable(shortName)
else:
self.defineVariable(longName)
self.defineVariable(name)
proc expressionStatement(self: Compiler) =
@ -661,13 +585,9 @@ proc delStatement(self: Compiler) =
else:
code = OpCode.DeleteLocal
self.localCount = self.localCount - 1
if self.currentChunk.consts.len < 255:
var name = self.identifierConstant(self.parser.previous())
self.emitBytes(code, name)
else:
var name = self.identifierLongConstant(self.parser.previous())
self.emitBytes(code, name[0])
self.emitBytes(name[1], name[2])
var name = self.identifierConstant(self.parser.previous())
self.emitBytes(code, name[0])
self.emitBytes(name[1], name[2])
self.parser.consume(TokenType.SEMICOLON, "Missing semicolon after del statement")
@ -696,7 +616,6 @@ proc endScope(self: Compiler) =
while self.localCount > 0 and self.locals[self.localCount - 1].depth > self.scopeDepth:
self.emitByte(OpCode.Pop)
self.localCount = self.localCount - 1
if start >= self.localCount:
self.locals.delete(self.localCount, start)
@ -915,7 +834,7 @@ proc endCompiler(self: Compiler): ptr Function =
## if no return statement is supplied
self.emitByte(OpCode.Nil)
self.emitByte(OpCode.Return)
return self.function # TODO: self.markObject?
return self.function
proc parseFunction(self: Compiler, funType: FunctionType) =
@ -963,12 +882,8 @@ proc parseFunction(self: Compiler, funType: FunctionType) =
self.parseBlock()
var fun = self.endCompiler()
self = self.enclosing
if self.currentChunk.consts.len < 255:
self.emitBytes(OpCode.Constant, self.makeConstant(fun))
else:
self.emitByte(OpCode.ConstantLong)
self.emitBytes(self.makeLongConstant(fun))
self.emitByte(OpCode.Constant)
self.emitBytes(self.makeConstant(fun))
proc parseLambda(self: Compiler, canAssign: bool) =

View File

@ -22,6 +22,7 @@ import config
import vm
proc repl() =
var bytecodeVM = initVM()
echo JAPL_VERSION_STRING
@ -31,11 +32,11 @@ proc repl() =
echo "==== Runtime Constants ====\n"
echo &"- FRAMES_MAX -> {FRAMES_MAX}"
echo "==== Debugger started ====\n"
var source: string = ""
var source = ""
while true:
try:
stdout.write("=> ")
source = readLine(stdin)
source = stdin.readLine()
except IOError:
echo ""
bytecodeVM.freeVM()

View File

@ -162,7 +162,7 @@ proc parseIdentifier(self: Lexer) =
## Parses identifiers, note that
## multi-character tokens such as
## UTF runes are not supported
while self.peek().isAlphaNumeric():
while self.peek().isAlphaNumeric() or self.peek() in {'_', }:
discard self.step()
var text: string = self.source[self.start..<self.current]
if text in RESERVED:
@ -247,3 +247,19 @@ proc lex*(self: Lexer): seq[Token] =
self.tokens.add(Token(kind: TokenType.EOF, lexeme: "EOF", line: self.line))
return self.tokens
when isMainModule:
echo("JAPL Lexer REPL")
while true:
try:
stdout.write(">> ")
var lexer = initLexer(readLine(stdin), "stdin")
stdout.write("Lexer output: [")
var lexed = lexer.lex()
for i, el in lexed:
stdout.write($el[])
if i < lexed.high():
stdout.write(", ")
stdout.write("]\n")
except IOError:
break

View File

@ -19,7 +19,7 @@ import ../types/baseObject
type
Chunk* = ref object # TODO: This shouldn't be here, but Function needs it. Consider refactoring
Chunk* = ref object
## A piece of bytecode.
## Consts represents the constants table the code is referring to
## Code is the compiled bytecode
@ -30,7 +30,6 @@ type
OpCode* {.pure.} = enum
## Enum of possible opcodes
Constant = 0u8,
ConstantLong,
Return,
Negate,
Add,
@ -84,10 +83,8 @@ const simpleInstructions* = {OpCode.Return, OpCode.Add, OpCode.Multiply,
OpCode.Slice, OpCode.Pop, OpCode.Negate,
OpCode.Is, OpCode.As}
const constantInstructions* = {OpCode.Constant, OpCode.DefineGlobal,
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}
const constantLongInstructions* = {OpCode.ConstantLong}
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}
const byteInstructions* = {OpCode.SetLocal, OpCode.GetLocal, OpCode.DeleteLocal,
OpCode.Call}
const jumpInstructions* = {OpCode.JumpIfFalse, OpCode.Jump, OpCode.Loop}
@ -117,15 +114,10 @@ proc freeChunk*(self: Chunk) =
self.lines = @[]
proc addConstant*(self: Chunk, constant: ptr Obj): int =
## Adds a constant to a chunk. Returns its index.
proc addConstant*(self: Chunk, constant: ptr Obj): array[3, uint8] =
## Writes a constant to a chunk. Returns its index casted to an array
self.consts.add(constant)
return self.consts.high() # The index of the constant
proc writeConstant*(self: Chunk, constant: ptr Obj): array[3, uint8] =
## Writes a constant to a chunk. Returns its index casted to an array.
## TODO newdoc
let index = self.addConstant(constant)
let index = self.consts.high()
result = cast[array[3, uint8]](index)

View File

@ -17,6 +17,7 @@
import types/baseObject
import types/numbers
import types/methods
import types/typeutils
import types/japlString
import types/exception
import types/native

View File

@ -12,49 +12,52 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Implementation of JAPL exceptions (WIP)
import baseObject
import japlString
import strformat
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
# TODO -> Use ptr String again once
# the recursive dependency is fixed
errName*: string # TODO: Ditch error name in favor of inheritance-based builtin types
message*: string
proc newIndexError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "IndexError".asStr()
result.message = message.asStr()
result.errName = "IndexError"
result.message = message
proc newReferenceError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "ReferenceError".asStr()
result.message = message.asStr()
result.errName = "ReferenceError"
result.message = message
proc newInterruptedError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "InterruptedError".asStr()
result.message = message.asStr()
result.errName = "InterruptedError"
result.message = message
proc newRecursionError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "RecursionError".asStr()
result.message = message.asStr()
result.errName = "RecursionError"
result.message = message
proc newTypeError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = "TypeError".asStr()
result.message = message.asStr()
result.errName = "TypeError"
result.message = message
proc stringify*(self: ptr JAPLException): string =
result = self.errName.toStr() & ": " & self.message.toStr()
result = &"{self.errName}: {self.message}"

View File

@ -18,7 +18,11 @@
import baseObject
import numbers
import ../memory
import typeutils
import exception
import strutils
import strformat
type
@ -36,13 +40,12 @@ proc toStr*(obj: ptr Obj): string =
proc hash*(self: ptr String): uint64 =
## Implements the FNV-1a hashing algorithm
## for strings
result = 2166136261u
var i = 0
while i < self.len:
result = result xor uint64 self.str[i]
for i in countup(0, self.len):
result = result xor uint64(self.str[i])
result *= 16777619
i += 1
return result
proc asStr*(s: string): ptr String =
@ -53,7 +56,10 @@ proc asStr*(s: string): ptr String =
for i in 0..len(s) - 1:
result.str[i] = s[i]
result.len = len(s)
result.hashValue = result.hash()
if result.len > 0:
result.hashValue = result.hash()
else:
result.hashValue = 0u
result.isHashable = true
@ -82,19 +88,69 @@ proc eq*(self, other: ptr String): bool =
result = true
proc sum*(self: ptr String, other: ptr Obj): ptr String =
if other.kind == ObjectType.String:
proc sum*(self: ptr String, other: ptr Obj): returnType =
result.kind = returnTypes.Object
if other.isStr():
var other = cast[ptr String](other)
var selfStr = self.toStr()
var otherStr = other.toStr()
result = (selfStr & otherStr).asStr()
result.result = (selfStr & otherStr).asStr()
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr String, other: ptr Obj): ptr Obj =
proc mul*(self: ptr String, other: ptr Obj): returnType =
result.kind = returnTypes.Object
case other.kind:
of ObjectType.Integer:
result = self.toStr().repeat(cast[ptr Integer](other).toInt()).asStr()
result.result = self.toStr().repeat(cast[ptr Integer](other).toInt()).asStr()
else:
raise newException(NotImplementedError, "")
proc getItem*(self: ptr String, other: ptr Obj): returnType =
result.kind = returnTypes.Object
## Handles getItem expressions
var str = self.toStr()
if not other.isInt():
result.kind = returnTypes.Exception
result.result = newTypeError("string indeces must be integers")
else:
var index: int = other.toInt()
if index < 0:
index = len(str) + other.toInt()
if index < 0: # If even now it is less than 0 then it is out of bounds
result.kind = returnTypes.Exception
result.result = newIndexError("string index out of bounds")
elif index - 1 > len(str) - 1:
result.kind = returnTypes.Exception
result.result = newIndexError("string index out of bounds")
else:
result.result = asStr(&"{str[index]}")
proc Slice*(self: ptr String, a: ptr Obj, b: ptr Obj): returnType =
## Handles slice expressions
var startIndex = b.toInt()
var endIndex = a.toInt()
result.kind = returnTypes.Object
if a.isNil():
endIndex = self.len
if b.isNil():
startIndex = 0
if not b.isInt() or not a.isInt():
result.kind = returnTypes.Exception
result.result = newTypeError("string indeces must be integers")
elif startIndex < 0:
startIndex = (self.len + startIndex)
if startIndex < 0:
startIndex = (self.len + endIndex)
elif startIndex > self.str.high():
result.result = asStr("")
return result
if endIndex > self.str.high():
endIndex = self.len
if startIndex > endIndex:
result.result = asStr("")
return result
result.result = self.toStr()[startIndex..<endIndex].asStr()

View File

@ -23,6 +23,7 @@ import boolean
import japlNil
import numbers
import native
import typeutils
import parseutils
@ -195,7 +196,7 @@ proc eq*(self, other: ptr Obj): bool =
discard
proc negate*(self: ptr Obj): ptr Obj =
proc negate*(self: ptr Obj): returnType =
## Returns the result of -self or
## raises an error if the operation
## is unsupported
@ -210,7 +211,7 @@ proc negate*(self: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc sum*(self, other: ptr Obj): ptr Obj =
proc sum*(self, other: ptr Obj): returnType =
## Returns the result of self + other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -232,7 +233,7 @@ proc sum*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc sub*(self, other: ptr Obj): ptr Obj =
proc sub*(self, other: ptr Obj): returnType =
## Returns the result of self - other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -248,7 +249,7 @@ proc sub*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc mul*(self, other: ptr Obj): ptr Obj =
proc mul*(self, other: ptr Obj): returnType =
## Returns the result of self * other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -266,7 +267,7 @@ proc mul*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc trueDiv*(self, other: ptr Obj): ptr Obj =
proc trueDiv*(self, other: ptr Obj): returnType =
## Returns the result of self / other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -282,7 +283,7 @@ proc trueDiv*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc pow*(self, other: ptr Obj): ptr Obj =
proc pow*(self, other: ptr Obj): returnType =
## Returns the result of self ** other (exponentiation)
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -298,7 +299,7 @@ proc pow*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc divMod*(self, other: ptr Obj): ptr Obj =
proc divMod*(self, other: ptr Obj): returnType =
## Returns the result of self % other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -314,7 +315,7 @@ proc divMod*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc binaryAnd*(self, other: ptr Obj): ptr Obj =
proc binaryAnd*(self, other: ptr Obj): returnType =
## Returns the result of self & other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -325,7 +326,7 @@ proc binaryAnd*(self, other: ptr Obj): ptr Obj =
proc binaryOr*(self, other: ptr Obj): ptr Obj =
proc binaryOr*(self, other: ptr Obj): returnType =
## Returns the result of self | other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -335,7 +336,7 @@ proc binaryOr*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc binaryXor*(self, other: ptr Obj): ptr Obj =
proc binaryXor*(self, other: ptr Obj): returnType =
## Returns the result of self ^ other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -345,7 +346,7 @@ proc binaryXor*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc binaryShr*(self, other: ptr Obj): ptr Obj =
proc binaryShr*(self, other: ptr Obj): returnType =
## Returns the result of self >> other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -355,7 +356,7 @@ proc binaryShr*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc binaryShl*(self, other: ptr Obj): ptr Obj =
proc binaryShl*(self, other: ptr Obj): returnType =
## Returns the result of self << other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -365,7 +366,7 @@ proc binaryShl*(self, other: ptr Obj): ptr Obj =
raise newException(NotImplementedError, "")
proc binaryNot*(self: ptr Obj): ptr Obj =
proc binaryNot*(self: ptr Obj): returnType =
## Returns the result of self ~other
## or raises NotImplementedError if the operation is unsupported
case self.kind:
@ -405,26 +406,27 @@ proc gt*(self: ptr Obj, other: ptr Obj): bool =
raise newException(NotImplementedError, "")
proc objAs*(self: ptr Obj, other: ObjectType): ptr Obj =
proc objAs*(self: ptr Obj, other: ObjectType): returnType =
## Returns the result of self as other
## (casts self to other's type)
# TODO -> This is in beta!
# TODO -> This is in beta, move to specific types module!
result.kind = returnTypes.Object
case other:
of ObjectType.String:
result = stringify(self).asStr()
result.result = stringify(self).asStr()
of ObjectType.Integer:
case self.kind:
of ObjectType.String:
var intVal: int
discard parseInt(cast[ptr String](self).toStr(), intVal)
result = intVal.asInt()
result.result = intVal.asInt()
of ObjectType.Float:
var floatVal: float64
discard (parseFloat(cast[ptr Float](self).stringify(), floatVal))
result = int(floatVal).asInt()
result.result = int(floatVal).asInt()
of ObjectType.Integer:
result = self
result.result = self
else:
raise newException(NotImplementedError, "")
of ObjectType.Float:
@ -432,13 +434,13 @@ proc objAs*(self: ptr Obj, other: ObjectType): ptr Obj =
of ObjectType.String:
var floatVal: float64
discard parseFloat(cast[ptr String](self).toStr(), floatVal)
result = floatVal.asFloat()
result.result = floatVal.asFloat()
of ObjectType.Integer:
var intVal: int
discard parseInt(cast[ptr Integer](self).stringify(), intVal)
result = float(intVal).asFloat()
result.result = float(intVal).asFloat()
of ObjectType.Float:
result = self
result.result = self
else:
raise newException(NotImplementedError, "")
@ -446,68 +448,20 @@ proc objAs*(self: ptr Obj, other: ObjectType): ptr Obj =
raise newException(NotImplementedError, "")
## Utilities to inspect JAPL objects
proc getItem*(self: ptr Obj, other: ptr Obj): returnType =
## Implements get expressions with square brackets
result.kind = returnTypes.Object
case self.kind:
of ObjectType.String:
result = cast[ptr String](self).getItem(other)
else:
raise newException(NotImplementedError, "")
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, ObjectType.Native}
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
proc Slice*(self: ptr Obj, a: ptr Obj, b: ptr Obj): returnType =
## Implements get expressions with square brackets
case self.kind:
of ObjectType.String:
result = cast[ptr String](self).Slice(a, b)
else:
raise newException(NotImplementedError, "")

View File

@ -12,11 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Implementation of numerical types
import baseObject
import typeutils
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)
@ -95,7 +98,7 @@ 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 =
proc sum*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
@ -118,12 +121,12 @@ proc hash*(self: ptr Infinity): uint64 =
result = 0u
proc negate*(self: ptr Infinity): ptr Infinity =
result = self
proc negate*(self: ptr Infinity): returnType =
result.result = nil
if self.isNegative:
result.isNegative = false
result.kind = returnTypes.Inf
else:
result.isNegative = true
result.kind = returnTypes.nInf
proc eq*(self, other: ptr Infinity): bool =
@ -178,26 +181,30 @@ proc gt*(self: ptr Infinity, other: ptr Obj): bool =
raise newException(NotImplementedError, "")
proc sum*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc sum*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc sub*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
elif not self.isNegative and not other.isNegative:
result.kind = returnTypes.NotANumber
of ObjectType.Integer, ObjectType.Float:
discard
else:
@ -221,8 +228,9 @@ proc eq*(self, other: ptr Float): bool =
result = self.floatValue == other.floatValue
proc negate*(self: ptr Float): ptr Float =
result = (-self.toFloat()).asFloat()
proc negate*(self: ptr Float): returnType =
result.kind = returnTypes.Object
result.result = (-self.toFloat()).asFloat()
@ -242,8 +250,9 @@ proc eq*(self, other: ptr Integer): bool =
result = self.intValue == other.intValue
proc negate*(self: ptr Integer): ptr Integer =
result = (-self.toInt()).asInt()
proc negate*(self: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = (-self.toInt()).asInt()
proc hash*(self: ptr Integer): uint64 =
@ -314,343 +323,391 @@ proc gt*(self: ptr Float, other: ptr Obj): bool =
raise newException(NotImplementedError, "")
proc sum*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
proc sum*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toInt() + cast[ptr Integer](other).toInt()).asInt()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.Inf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc sum*(self: ptr Float, other: ptr Obj): ptr Obj =
proc sum*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() + float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.Inf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
proc sub*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toInt() - cast[ptr Integer](other).toInt()).asInt()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc sub*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
proc sub*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc sub*(self: ptr Float, other: ptr Obj): ptr Obj =
proc sub*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() - float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc mul*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
proc mul*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc mul*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
proc mul*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toInt() * cast[ptr Integer](other).toInt()).asInt()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc mul*(self: ptr Float, other: ptr Obj): ptr Obj =
proc mul*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() * float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc trueDiv*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
proc trueDiv*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Integer, other: ptr Obj): ptr Obj =
proc trueDiv*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = ((float self.toInt()) / (float cast[ptr Integer](other).toInt())).asFloat() # so that 4 / 2 == 2.0
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc trueDiv*(self: ptr Float, other: ptr Obj): ptr Obj =
proc trueDiv*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() / float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc pow*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
proc pow*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc pow*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
proc pow*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toInt() ** cast[ptr Integer](other).toInt()).asInt()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc pow*(self: ptr Float, other: ptr Obj): ptr Obj =
proc pow*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() ** float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Infinity, other: ptr Obj): ptr Infinity =
result = asInf()
proc divMod*(self: ptr Infinity, other: ptr Obj): returnType =
result.result = nil
result.kind = returnTypes.Inf
case other.kind:
of ObjectType.Infinity:
var other = cast[ptr Infinity](other)
if self.isNegative or other.isNegative:
result.isNegative = true
result.kind = returnTypes.nInf
of ObjectType.Integer, ObjectType.Float:
discard
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr NotANumber, other: ptr Obj): ptr NotANumber =
proc divMod*(self: ptr NotANumber, other: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Integer, other: ptr Obj): ptr Obj = # This can yield a float!
proc divMod*(self: ptr Integer, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toInt() mod cast[ptr Integer](other).toInt()).asInt()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc divMod*(self: ptr Float, other: ptr Obj): ptr Obj =
proc divMod*(self: ptr Float, other: ptr Obj): returnType =
case other.kind:
of ObjectType.Integer:
result = (self.toFloat() mod float cast[ptr Integer](other).toInt()).asFloat()
result.kind = returnTypes.Object
result.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()
result.kind = returnTypes.Inf
result.result = nil
elif res == -system.Inf:
let negInf = asInf()
negInf.isNegative = true
result = negInf
result.kind = returnTypes.nInf
result.result = nil
else:
result = res.asFloat()
result.kind = returnTypes.Object
result.result = res.asFloat()
of ObjectType.Infinity:
result = cast[ptr Infinity](other)
result.kind = returnTypes.nInf
result.result = nil
else:
raise newException(NotImplementedError, "")
proc binaryAnd*(self, other: ptr Integer): ptr Integer =
result = bitand(self.toInt(), other.toInt()).asInt()
proc binaryAnd*(self, other: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = bitand(self.toInt(), other.toInt()).asInt()
proc binaryOr*(self, other: ptr Integer): ptr Integer =
result = bitor(self.toInt(), other.toInt()).asInt()
proc binaryOr*(self, other: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = bitor(self.toInt(), other.toInt()).asInt()
proc binaryNot*(self: ptr Integer): ptr Integer =
result = bitnot(self.toInt()).asInt()
proc binaryNot*(self: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = bitnot(self.toInt()).asInt()
proc binaryXor*(self, other: ptr Integer): ptr Integer =
result = bitxor(self.toInt(), other.toInt()).asInt()
proc binaryXor*(self, other: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = bitxor(self.toInt(), other.toInt()).asInt()
proc binaryShr*(self, other: ptr Integer): ptr Integer =
result = (self.toInt() shr other.toInt()).asInt()
proc binaryShr*(self, other: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = (self.toInt() shr other.toInt()).asInt()
proc binaryShl*(self, other: ptr Integer): ptr Integer =
result = (self.toInt() shr other.toInt()).asInt()
proc binaryShl*(self, other: ptr Integer): returnType =
result.kind = returnTypes.Object
result.result = (self.toInt() shr other.toInt()).asInt()

101
src/types/typeutils.nim Normal file
View File

@ -0,0 +1,101 @@
# 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.
## Utilities to inspect JAPL objects
import baseObject
type
returnTypes* {.pure.} = enum
## Pretty much like retNative, we use
## this handy enum to differentiate
## what is what, perform better error
## handling and (most importantly)
## maintain our invariant that singletons
## always refer to the same object
False,
True,
Inf,
nInf,
Nil,
NotANumber,
Object,
Exception
returnType* = tuple[kind: returnTypes, result: ptr Obj]
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, ObjectType.Native}
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

View File

@ -34,7 +34,7 @@ proc byteInstruction(name: string, chunk: Chunk, offset: int): int =
return offset + 2
proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =
proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
# Rebuild the index
var constantArray: array[3, uint8] = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]]
var constant: int
@ -45,14 +45,6 @@ proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =
return offset + 4
proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
var constant = chunk.code[offset + 1]
echo &"\tInstruction at IP: {name}, points to index {constant}"
let obj = chunk.consts[constant]
echo &"\tOperand: {stringify(obj)}\n\tValue kind: {obj.kind}\n"
return offset + 2
proc jumpInstruction(name: string, chunk: Chunk, offset: int): int =
var jumpArray: array[2, uint8] = [chunk.code[offset + 1], chunk.code[offset + 2]]
var jump: int
@ -70,8 +62,6 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
result = simpleInstruction($opcode, offset)
of constantInstructions:
result = constantInstruction($opcode, chunk, offset)
of constantLongInstructions:
result = constantLongInstruction($opcode, chunk, offset)
of byteInstructions:
result = byteInstruction($opcode, chunk, offset)
of jumpInstructions:

View File

@ -35,6 +35,7 @@ import types/exception
import types/numbers
import types/boolean
import types/methods
import types/typeutils
import types/function
import types/native
# We always import it to
@ -102,7 +103,7 @@ proc error*(self: VM, error: ptr JAPLException) =
# Exceptions are objects too and they need to
# be freed like any other entity in JAPL
self.objects.add(error)
self.objects.add(error) # TODO -> Move this somewhere else to mark exceptions even before they are raised
var previous = "" # All this stuff seems overkill, but it makes the traceback look nicer
var repCount = 0 # and if we are here we are far beyond a point where performance matters anyway
var mainReached = false
@ -145,81 +146,34 @@ proc push*(self: VM, obj: ptr Obj) =
self.stackTop += 1
proc push*(self: VM, ret: returnType) =
## Pushes a return value from a builtin
## method onto the stack and handles errors
case ret.kind:
of returnTypes.Object:
self.push(ret.result)
of returnTypes.Exception:
self.error(cast[ptr JAPLException](ret.result))
of returnTypes.True:
self.push(self.cached[0])
of returnTypes.False:
self.push(self.cached[1])
of returnTypes.Nil:
self.push(self.cached[2])
of returnTypes.Inf:
self.push(self.cached[3])
of returnTypes.nInf:
self.push(self.cached[4])
of returnTypes.NotANumber:
self.push(self.cached[5])
proc peek*(self: VM, distance: int): ptr Obj =
## Peeks an object (at a given distance from the
## current index) from the stack
return self.stack[self.stackTop - distance - 1]
# TODO: Move this to types/
proc slice(self: VM): bool =
## Handles single-operator slice expressions
## (consider moving this to an appropriate
## slice method)
var idx = self.pop()
var peeked = self.pop()
case peeked.kind:
of ObjectType.String:
var str = peeked.toStr()
if not idx.isInt():
self.error(newTypeError("string indeces must be integers"))
return false
else:
var index: int = idx.toInt()
if index < 0:
index = len(str) + idx.toInt()
if index < 0: # If even now it is less than 0 than it is out of bounds
self.error(newIndexError("string index out of bounds"))
return false
elif index - 1 > len(str) - 1:
self.error(newIndexError("string index out of bounds"))
return false
else:
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 types/
proc sliceRange(self: VM): bool =
## Handles slices when there's both a start
## and an end index (even implicit ones)
var sliceEnd = self.pop()
var sliceStart = self.pop()
var popped = self.pop()
case popped.kind:
of ObjectType.String:
var str = popped.toStr()
if sliceEnd.isNil():
sliceEnd = len(str).asInt()
if sliceStart.isNil():
sliceStart = asInt(0)
elif not sliceStart.isInt() or not sliceEnd.isInt():
self.error(newTypeError("string indexes must be integers"))
return false
else:
var startIndex = sliceStart.toInt()
var endIndex = sliceEnd.toInt()
if startIndex < 0:
sliceStart = (len(str) + sliceStart.toInt()).asInt()
if startIndex < 0:
sliceStart = (len(str) + sliceEnd.toInt()).asInt()
elif startIndex - 1 > len(str) - 1:
self.push(asStr(""))
return true
if endIndex - 1 > len(str) - 1:
sliceEnd = len(str).asInt()
if startIndex > endIndex:
self.push(asStr(""))
return true
self.push(asStr(str[sliceStart.toInt()..<sliceEnd.toInt()]))
return true
else:
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))
return false
proc call(self: VM, function: ptr Function, argCount: int): bool =
## Sets up the call frame and performs error checking
## when calling callables
@ -315,18 +269,13 @@ proc readShort(self: CallFrame): uint16 =
proc readConstant(self: CallFrame): ptr Obj =
## Reads a constant from the given
## frame's constant table
result = self.function.chunk.consts[uint8 self.readByte()]
proc readLongConstant(self: CallFrame): ptr Obj =
## Reads a long constant from the
## given frame's constant table
var arr = [self.readByte(), self.readByte(), self.readByte()]
var idx: int
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
copyMem(idx.addr, arr.addr, sizeof(arr))
result = self.function.chunk.consts[idx]
proc showRuntime*(self: VM, frame: CallFrame, iteration: uint64) =
## Shows debug information about the current
## state of the virtual machine
@ -368,24 +317,18 @@ proc run(self: VM, repl: bool): InterpretResult =
## them one at a time: this is the runtime's
## main loop
var frame = self.frames[self.frameCount - 1]
var opcode: OpCode
when DEBUG_TRACE_VM:
var iteration: uint64 = 0
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
opcode = OpCode(frame.readByte())
when DEBUG_TRACE_VM: # Insight inside the VM
iteration += 1
self.showRuntime(frame, iteration)
case opcode: # Main OpCodes dispatcher
case OpCode(frame.readByte()): # Main OpCodes dispatcher
of OpCode.Constant:
# Loads a constant from the chunk's constant
# table
self.push(frame.readConstant())
of OpCode.ConstantLong:
# Loads a constant from the chunk's constant
# table when its size exceeds 256
self.push(frame.readLongConstant())
of OpCode.Negate:
# Performs unary negation
let operand = self.pop()
@ -560,29 +503,31 @@ proc run(self: VM, repl: bool): InterpretResult =
of OpCode.GetItem:
# Implements expressions such as a[b]
# TODO: More generic method
if not self.slice():
var right = self.pop()
var left = self.pop()
try:
self.push(left.getItem(right))
except NotImplementedError:
self.error(newTypeError(&"object of type '{left.typeName()}' does not support getItem expressions"))
return RuntimeError
of OpCode.Slice:
# Implements expressions such as a[b:c]
# TODO: More generic method
if not self.sliceRange():
var right = self.pop()
var left = self.pop()
var operand = self.pop()
try:
self.push(operand.Slice(right, left))
except NotImplementedError:
self.error(newTypeError(&"object of type '{operand.typeName()}' does not support slicing"))
return RuntimeError
of OpCode.DefineGlobal:
# Defines a global variable
var name: string
if frame.function.chunk.consts.len > 255:
name = frame.readLongConstant().toStr()
else:
name = frame.readConstant().toStr()
var name = frame.readConstant().toStr()
self.globals[name] = self.peek(0)
discard self.pop()
of OpCode.GetGlobal:
# Retrieves a global variable
var constant: string
if frame.function.chunk.consts.len > 255:
constant = frame.readLongConstant().toStr()
else:
constant = frame.readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
@ -590,11 +535,7 @@ proc run(self: VM, repl: bool): InterpretResult =
self.push(self.globals[constant])
of OpCode.SetGlobal:
# Changes the value of an already defined global variable
var constant: string
if frame.function.chunk.consts.len > 255:
constant = frame.readLongConstant().toStr()
else:
constant = frame.readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
return RuntimeError
@ -603,11 +544,7 @@ proc run(self: VM, repl: bool): InterpretResult =
of OpCode.DeleteGlobal:
# Deletes a global variable
# TODO: Inspect potential issues with the GC
var constant: string
if frame.function.chunk.consts.len > 255:
constant = frame.readLongConstant().toStr()
else:
constant = frame.readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
@ -615,29 +552,14 @@ proc run(self: VM, repl: bool): InterpretResult =
self.globals.del(constant)
of OpCode.GetLocal:
# Retrieves a local variable
var slot: int
if frame.len > 255:
slot = frame.readBytes()
else:
slot = int frame.readByte()
self.push(frame[slot])
self.push(frame[frame.readBytes()])
of OpCode.SetLocal:
# Changes the value of an already defined local variable
var slot: int
if frame.len > 255:
slot = frame.readBytes()
else:
slot = int frame.readByte()
frame[slot] = self.peek(0)
frame[frame.readBytes()] = self.peek(0)
of OpCode.DeleteLocal:
# Deletes a global variable
# TODO: Inspect potential issues with the GC
var slot: int
if frame.len > 255:
slot = frame.readBytes()
else:
slot = int frame.readByte()
frame.delete(slot)
frame.delete(frame.readBytes())
of OpCode.Pop:
# Pops an item off the stack
self.lastPop = self.pop()

View File

@ -0,0 +1,130 @@
// Test for constants
var v_1 = 1;
var v_2 = 1;
var v_3 = 1;
var v_4 = 1;
var v_5 = 1;
var v_6 = 1;
var v_7 = 1;
var v_8 = 1;
var v_9 = 1;
var v_10 = 1;
var v_11 = 1;
var v_12 = 1;
var v_13 = 1;
var v_14 = 1;
var v_15 = 1;
var v_16 = 1;
var v_17 = 1;
var v_18 = 1;
var v_19 = 1;
var v_20 = 1;
var v_21 = 1;
var v_22 = 1;
var v_23 = 1;
var v_24 = 1;
var v_25 = 1;
var v_26 = 1;
var v_27 = 1;
var v_28 = 1;
var v_29 = 1;
var v_30 = 1;
var v_31 = 1;
var v_32 = 1;
var v_33 = 1;
var v_34 = 1;
var v_35 = 1;
var v_36 = 1;
var v_37 = 1;
var v_38 = 1;
var v_39 = 1;
var v_40 = 1;
var v_41 = 1;
var v_42 = 1;
var v_43 = 1;
var v_44 = 1;
var v_45 = 1;
var v_46 = 1;
var v_47 = 1;
var v_48 = 1;
var v_49 = 1;
var v_50 = 1;
var v_51 = 1;
var v_52 = 1;
var v_53 = 1;
var v_54 = 1;
var v_55 = 1;
var v_56 = 1;
var v_57 = 1;
var v_58 = 1;
var v_59 = 1;
var v_60 = 1;
var v_61 = 1;
var v_62 = 1;
var v_63 = 1;
var v_64 = 1;
var v_65 = 1;
var v_66 = 1;
var v_67 = 1;
var v_68 = 1;
var v_69 = 1;
var v_70 = 1;
var v_71 = 1;
var v_72 = 1;
var v_73 = 1;
var v_74 = 1;
var v_75 = 1;
var v_76 = 1;
var v_77 = 1;
var v_78 = 1;
var v_79 = 1;
var v_80 = 1;
var v_81 = 1;
var v_82 = 1;
var v_83 = 1;
var v_84 = 1;
var v_85 = 1;
var v_86 = 1;
var v_87 = 1;
var v_88 = 1;
var v_89 = 1;
var v_90 = 1;
var v_91 = 1;
var v_92 = 1;
var v_93 = 1;
var v_94 = 1;
var v_95 = 1;
var v_96 = 1;
var v_97 = 1;
var v_98 = 1;
var v_99 = 1;
var v_100 = 1;
var v_101 = 1;
var v_102 = 1;
var v_103 = 1;
var v_104 = 1;
var v_105 = 1;
var v_106 = 1;
var v_107 = 1;
var v_108 = 1;
var v_109 = 1;
var v_110 = 1;
var v_111 = 1;
var v_112 = 1;
var v_113 = 1;
var v_114 = 1;
var v_115 = 1;
var v_116 = 1;
var v_117 = 1;
var v_118 = 1;
var v_119 = 1;
var v_120 = 1;
var v_121 = 1;
var v_122 = 1;
var v_123 = 1;
var v_124 = 1;
var v_125 = 1;
var v_126 = 1;
var v_127 = 1;
var v_128 = 1;