Huge refactoring for the type system and object structure, VM needs opcode refactoring

This commit is contained in:
nocturn9x 2020-10-25 12:45:03 +01:00
parent 823f4c622b
commit 673b88f9de
26 changed files with 400 additions and 631 deletions

View File

@ -1,126 +0,0 @@
# 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 meta/tokenobject
import types/japlvalue
import types/function
import tables
const FRAMES_MAX* = 400 # TODO: Inspect why the VM crashes if this exceeds 400
const JAPL_VERSION* = "0.2.0"
const JAPL_RELEASE* = "alpha"
const DEBUG_TRACE_VM* = true # Traces VM execution
const DEBUG_TRACE_GC* = true # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = true # Traces memory allocation/deallocation (WIP)
const DEBUG_TRACE_COMPILER* = true # Traces the compiler (TODO)
type
CallFrame* = ref object # FIXME: Call frames are broken (end indexes are likely wrong)
function*: ptr Function
ip*: int
slot*: int
endSlot*: int
stack*: seq[Value]
VM* = ref object # The VM object
lastPop*: Value
frameCount*: int
source*: string
frames*: seq[CallFrame]
stack*: seq[Value]
stackTop*: int
objects*: seq[ptr Obj]
globals*: Table[string, Value]
file*: string
Local* = ref object # A local variable
name*: Token
depth*: int
Parser* = ref object # A Parser object
current*: int
tokens*: seq[Token]
hadError*: bool
panicMode*: bool
file*: string
proc getView*(self: CallFrame): seq[Value] =
result = self.stack[self.slot..self.endSlot - 1]
proc getAbsIndex(self: CallFrame, idx: int): int =
return idx + len(self.getView()) - 1 # TODO: Inspect this code (locals, functions)
proc len*(self: CallFrame): int =
result = len(self.getView())
proc `[]`*(self: CallFrame, idx: int): Value =
result = self.stack[self.getAbsIndex(idx)]
proc `[]=`*(self: CallFrame, idx: int, val: Value) =
if idx < self.slot:
raise newException(IndexError, "CallFrame index out of range")
self.stack[self.getAbsIndex(idx)] = val
proc delete*(self: CallFrame, idx: int) =
if idx < self.slot or idx > self.endSlot:
raise newException(IndexError, "CallFrame index out of range")
self.stack.delete(idx)
## TODO: Move this stuff back to their respective module
proc initParser*(tokens: seq[Token], file: string): Parser =
result = Parser(current: 0, tokens: tokens, hadError: false, panicMode: false, file: file)
proc hashFloat(f: float): uint32 =
# TODO: Any improvement?
result = 2166136261u32
result = result xor uint32 f
result *= 16777619
# TODO: Move this into an hash() method for objects
proc hash*(value: Value): uint32 =
case value.kind:
of ValueType.Integer:
result = uint32 value.toInt()
of ValueType.Bool:
if value.boolValue:
result = uint32 1
else:
result = uint32 0
of ValueType.Double:
result = hashFloat(value.toFloat())
of ValueType.Object:
case value.obj.kind:
of ObjectType.String:
result = hash(cast[ptr String](value.obj))
else:
result = hash(value.obj)
else: # More coming soon
result = uint32 0

View File

@ -19,13 +19,11 @@ import strutils
import algorithm import algorithm
import strformat import strformat
import lexer import lexer
import common
import meta/opcode import meta/opcode
import meta/tokenobject import meta/token
import meta/tokentype
import meta/looptype import meta/looptype
import types/japlvalue import types/jobject
import types/stringtype import types/jstring
import types/function import types/function
import tables import tables
when isMainModule: when isMainModule:
@ -44,6 +42,18 @@ type
loop*: Loop loop*: Loop
objects*: seq[ptr Obj] objects*: seq[ptr Obj]
file*: string file*: string
Local* = ref object # A local variable
name*: Token
depth*: int
Parser* = ref object # A Parser object
current*: int
tokens*: seq[Token]
hadError*: bool
panicMode*: bool
file*: string
Precedence {.pure.} = enum Precedence {.pure.} = enum
None, None,
Assignment, Assignment,
@ -160,29 +170,30 @@ proc emitBytes(self: ref Compiler, bytarr: array[3, uint8]) =
self.emitByte(bytarr[2]) self.emitByte(bytarr[2])
proc makeConstant(self: ref Compiler, val: Value): uint8 = proc makeConstant(self: ref Compiler, obj: ptr Obj): uint8 =
## Adds a constant (literal) to the current chunk's ## Adds a constant (literal) to the current chunk's
## constants table ## constants table
result = uint8 self.currentChunk.addConstant(val) result = uint8 self.currentChunk.addConstant(obj)
proc makeLongConstant(self: ref Compiler, val: Value): array[3, uint8] = proc makeLongConstant(self: ref Compiler, val: ptr Obj): array[3, uint8] =
## Does the same as makeConstant(), but encodes the index in the ## Does the same as makeConstant(), but encodes the index in the
## chunk's constant table as an array (which is later reconstructed ## 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 ## into an integer at runtime) to store more than 256 constants in the table
result = self.currentChunk.writeConstant(val) result = self.currentChunk.writeConstant(val)
proc emitConstant(self: ref Compiler, value: Value) = proc emitConstant(self: ref Compiler, obj: ptr Obj) =
## Emits a Constant or ConstantLong instruction along ## Emits a Constant or ConstantLong instruction along
## with its operand ## with its operand
if self.currentChunk().consts.len > 255: if self.currentChunk().consts.len > 255:
self.emitByte(OpCode.ConstantLong) self.emitByte(OpCode.ConstantLong)
self.emitBytes(self.makeLongConstant(value)) self.emitBytes(self.makeLongConstant(obj))
else: else:
self.emitBytes(OpCode.Constant, self.makeConstant(value)) self.emitBytes(OpCode.Constant, self.makeConstant(obj))
proc initParser*(tokens: seq[Token], file: string): Parser
proc getRule(kind: TokenType): ParseRule # Forward declarations for later use proc getRule(kind: TokenType): ParseRule # Forward declarations for later use
proc statement(self: ref Compiler) proc statement(self: ref Compiler)
proc declaration(self: ref Compiler) proc declaration(self: ref Compiler)
@ -236,39 +247,39 @@ proc binary(self: ref Compiler, canAssign: bool) =
var rule = getRule(operator) var rule = getRule(operator)
self.parsePrecedence(Precedence((int rule.precedence) + 1)) self.parsePrecedence(Precedence((int rule.precedence) + 1))
case operator: case operator:
of PLUS: of TokenType.PLUS:
self.emitByte(OpCode.Add) self.emitByte(OpCode.Add)
of MINUS: of TokenType.MINUS:
self.emitByte(OpCode.Subtract) self.emitByte(OpCode.Subtract)
of SLASH: of TokenType.SLASH:
self.emitByte(OpCode.Divide) self.emitByte(OpCode.Divide)
of STAR: of TokenType.STAR:
self.emitByte(OpCode.Multiply) self.emitByte(OpCode.Multiply)
of MOD: of TokenType.MOD:
self.emitByte(OpCode.Mod) self.emitByte(OpCode.Mod)
of POW: of TokenType.POW:
self.emitByte(OpCode.Pow) self.emitByte(OpCode.Pow)
of NE: of TokenType.NE:
self.emitBytes(OpCode.Equal, OpCode.Not) self.emitBytes(OpCode.Equal, OpCode.Not)
of DEQ: of TokenType.DEQ:
self.emitByte(OpCode.Equal) self.emitByte(OpCode.Equal)
of GT: of TokenType.GT:
self.emitByte(OpCode.Greater) self.emitByte(OpCode.Greater)
of GE: of TokenType.GE:
self.emitBytes(OpCode.Less, OpCode.Not) self.emitBytes(OpCode.Less, OpCode.Not)
of LT: of TokenType.LT:
self.emitByte(OpCode.Less) self.emitByte(OpCode.Less)
of LE: of TokenType.LE:
self.emitBytes(OpCode.Greater, OpCode.Not) self.emitBytes(OpCode.Greater, OpCode.Not)
of CARET: of TokenType.CARET:
self.emitByte(OpCode.Xor) self.emitByte(OpCode.Xor)
of SHL: of TokenType.SHL:
self.emitByte(OpCode.Shl) self.emitByte(OpCode.Shl)
of SHR: of TokenType.SHR:
self.emitByte(OpCode.Shr) self.emitByte(OpCode.Shr)
of BOR: of TokenType.BOR:
self.emitByte(OpCode.Bor) self.emitByte(OpCode.Bor)
of BAND: of TokenType.BAND:
self.emitByte(OpCode.Band) self.emitByte(OpCode.Band)
else: else:
discard # Unreachable discard # Unreachable
@ -294,11 +305,12 @@ proc unary(self: ref Compiler, canAssign: bool) =
return return
template markObject*(self: ref Compiler, obj: untyped): untyped = template markObject*(self: ref Compiler, obj: ptr Obj): untyped =
## Marks compile-time objects (since those take up memory as well) ## Marks compile-time objects (since those take up memory as well)
## for the VM to reclaim space later on ## for the VM to reclaim space later on
self.objects.add(obj) let temp = obj
obj self.objects.add(temp)
temp
proc strVal(self: ref Compiler, canAssign: bool) = proc strVal(self: ref Compiler, canAssign: bool) =
@ -306,7 +318,7 @@ proc strVal(self: ref Compiler, canAssign: bool) =
var str = self.parser.previous().lexeme var str = self.parser.previous().lexeme
var delimiter = &"{str[0]}" # TODO: Add proper escape sequences support var delimiter = &"{str[0]}" # TODO: Add proper escape sequences support
str = str.unescape(delimiter, delimiter) str = str.unescape(delimiter, delimiter)
self.emitConstant(Value(kind: OBJECT, obj: self.markObject(newString(str)))) self.emitConstant(self.markObject(jstring.newString(str)))
proc bracketAssign(self: ref Compiler, canAssign: bool) = proc bracketAssign(self: ref Compiler, canAssign: bool) =
@ -328,26 +340,26 @@ proc bracket(self: ref Compiler, canAssign: bool) =
## Like in Python, using an end index that's out of bounds ## Like in Python, using an end index that's out of bounds
## will not raise an error. Doing "hello"[0:999] will just ## will not raise an error. Doing "hello"[0:999] will just
## return the whole string instead ## return the whole string instead
if self.parser.peek.kind == COLON: if self.parser.peek.kind == TokenType.COLON:
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
discard self.parser.advance() discard self.parser.advance()
if self.parser.peek().kind == RS: if self.parser.peek().kind == TokenType.RS:
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
else: else:
self.parsePrecedence(Precedence.Term) self.parsePrecedence(Precedence.Term)
self.emitByte(OpCode.SliceRange) self.emitByte(OpCode.SliceRange)
else: else:
self.parsePrecedence(Precedence.Term) self.parsePrecedence(Precedence.Term)
if self.parser.peek().kind == RS: if self.parser.peek().kind == TokenType.RS:
self.emitByte(OpCode.Slice) self.emitByte(OpCode.Slice)
elif self.parser.peek().kind == COLON: elif self.parser.peek().kind == TokenType.COLON:
discard self.parser.advance() discard self.parser.advance()
if self.parser.peek().kind == RS: if self.parser.peek().kind == TokenType.RS:
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
else: else:
self.parsePrecedence(Precedence.Term) self.parsePrecedence(Precedence.Term)
self.emitByte(OpCode.SliceRange) self.emitByte(OpCode.SliceRange)
if self.parser.peek().kind == EQ: if self.parser.peek().kind == TokenType.EQ:
discard self.parser.advance() discard self.parser.advance()
self.parsePrecedence(Precedence.Term) self.parsePrecedence(Precedence.Term)
self.parser.consume(TokenType.RS, "Expecting ']' after slice expression") self.parser.consume(TokenType.RS, "Expecting ']' after slice expression")
@ -356,9 +368,9 @@ proc bracket(self: ref Compiler, canAssign: bool) =
proc literal(self: ref Compiler, canAssign: bool) = proc literal(self: ref Compiler, canAssign: bool) =
## Parses literal values such as true, nan and inf ## Parses literal values such as true, nan and inf
case self.parser.previous().kind: case self.parser.previous().kind:
of TRUE: of TokenType.TRUE:
self.emitByte(OpCode.True) self.emitByte(OpCode.True)
of FALSE: of TokenType.FALSE:
self.emitByte(OpCode.False) self.emitByte(OpCode.False)
of TokenType.NIL: of TokenType.NIL:
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
@ -372,21 +384,29 @@ proc literal(self: ref Compiler, canAssign: bool) =
proc number(self: ref Compiler, canAssign: bool) = proc number(self: ref Compiler, canAssign: bool) =
## Parses numerical constants ## Parses numerical constants
var value = self.parser.previous().literal var value = self.parser.previous().lexeme
self.emitConstant(value) var obj: ptr Obj
try:
if "." in value:
obj = parseFloat(value).asFloat()
else:
obj = parseInt(value).asInt()
except OverflowError:
self.compileError("number literal is too big")
self.emitConstant(obj)
proc grouping(self: ref Compiler, canAssign: bool) = proc grouping(self: ref Compiler, canAssign: bool) =
## Parses parenthesized expressions. The only interesting ## Parses parenthesized expressions. The only interesting
## semantic about parentheses is that they allow lower-precedence ## semantic about parentheses is that they allow lower-precedence
## expressions where a higher precedence one is expected ## expressions where a higher precedence one is expected
if self.parser.match(EOF): if self.parser.match(TokenType.EOF):
self.parser.parseError(self.parser.previous, "Expecting ')'") self.parser.parseError(self.parser.previous, "Expecting ')'")
elif self.parser.match(RP): elif self.parser.match(RP):
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
else: else:
self.expression() self.expression()
self.parser.consume(RP, "Expecting ')' after parentheszed expression") self.parser.consume(TokenType.RP, "Expecting ')' after parentheszed expression")
proc synchronize(self: ref Compiler) = proc synchronize(self: ref Compiler) =
@ -408,11 +428,13 @@ proc synchronize(self: ref Compiler) =
## parsing from there. Note that hadError is never reset, but ## parsing from there. Note that hadError is never reset, but
## panidMode is ## panidMode is
self.parser.panicMode = false self.parser.panicMode = false
while self.parser.peek().kind != EOF: # Infinite loops are bad, so we must take EOF into account while self.parser.peek().kind != TokenType.EOF: # Infinite loops are bad, so we must take EOF into account
if self.parser.previous().kind == SEMICOLON: if self.parser.previous().kind == TokenType.SEMICOLON:
return return
case self.parser.peek().kind: case self.parser.peek().kind:
of TokenType.CLASS, FUN, VAR, TokenType.FOR, IF, TokenType.WHILE, RETURN: # We found a statement boundary, so the parser bails out of TokenType.CLASS, TokenType.FUN, TokenType.VAR,
TokenType.FOR, TokenType.IF, TokenType.WHILE,
TokenType.RETURN: # We found a statement boundary, so the parser bails out
return return
else: else:
discard discard
@ -421,13 +443,13 @@ proc synchronize(self: ref Compiler) =
proc identifierConstant(self: ref Compiler, tok: Token): uint8 = proc identifierConstant(self: ref Compiler, tok: Token): uint8 =
## Emits instructions for identifiers ## Emits instructions for identifiers
return self.makeConstant(Value(kind: OBJECT, obj: self.markObject(newString(tok.lexeme)))) return self.makeConstant(self.markObject(jstring.newString(tok.lexeme)))
proc identifierLongConstant(self: ref Compiler, tok: Token): array[3, uint8] = proc identifierLongConstant(self: ref Compiler, tok: Token): array[3, uint8] =
## Same as identifierConstant, but this is used when the constant table is longer ## Same as identifierConstant, but this is used when the constant table is longer
## than 255 elements ## than 255 elements
return self.makeLongConstant(Value(kind: OBJECT, obj: self.markObject(newString(tok.lexeme)))) return self.makeLongConstant(self.markObject(jstring.newString(tok.lexeme)))
proc addLocal(self: ref Compiler, name: Token) = proc addLocal(self: ref Compiler, name: Token) =
@ -457,22 +479,22 @@ proc declareVariable(self: ref Compiler) =
proc parseVariable(self: ref Compiler, message: string): uint8 = proc parseVariable(self: ref Compiler, message: string): uint8 =
## Parses variables and declares them ## Parses variables and declares them
self.parser.consume(ID, message) self.parser.consume(TokenType.ID, message)
self.declareVariable() self.declareVariable()
if self.scopeDepth > 0: if self.scopeDepth > 0:
return uint8 0 return uint8 0
return self.identifierConstant(self.parser.previous) return self.identifierConstant(self.parser.previous())
proc parseLongVariable(self: ref Compiler, message: string): array[3, uint8] = proc parseLongVariable(self: ref Compiler, message: string): array[3, uint8] =
## Parses variables and declares them. This is used in place ## Parses variables and declares them. This is used in place
## of parseVariable when there's more than 255 constants ## of parseVariable when there's more than 255 constants
## in the chunk table ## in the chunk table
self.parser.consume(ID, message) self.parser.consume(TokenType.ID, message)
self.declareVariable() self.declareVariable()
if self.scopeDepth > 0: if self.scopeDepth > 0:
return [uint8 0, uint8 0, uint8 0] return [uint8 0, uint8 0, uint8 0]
return self.identifierLongConstant(self.parser.previous) return self.identifierLongConstant(self.parser.previous())
proc markInitialized(self: ref Compiler) = proc markInitialized(self: ref Compiler) =
@ -589,7 +611,7 @@ proc varDeclaration(self: ref Compiler) =
self.expression() self.expression()
else: else:
self.emitByte(OpCode.Nil) self.emitByte(OpCode.Nil)
self.parser.consume(SEMICOLON, "Missing semicolon after var declaration") self.parser.consume(TokenType.SEMICOLON, "Missing semicolon after var declaration")
if useShort: if useShort:
self.defineVariable(shortName) self.defineVariable(shortName)
else: else:
@ -601,14 +623,14 @@ proc expressionStatement(self: ref Compiler) =
## an expression followed by a semicolon. It then ## an expression followed by a semicolon. It then
## emits a pop instruction ## emits a pop instruction
self.expression() self.expression()
self.parser.consume(SEMICOLON, "Missing semicolon after expression") self.parser.consume(TokenType.SEMICOLON, "Missing semicolon after expression")
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
# TODO: This code will not be used right now as it might clash with the future GC, fix this to make it GC aware! # TODO: This code will not be used right now as it might clash with the future GC, fix this to make it GC aware!
proc deleteVariable(self: ref Compiler, canAssign: bool) = proc deleteVariable(self: ref Compiler, canAssign: bool) =
self.expression() self.expression()
if self.parser.previous().kind in [NUMBER, STR]: if self.parser.previous().kind in [TokenType.NUMBER, TokenType.STR]:
self.compileError("cannot delete a literal") self.compileError("cannot delete a literal")
var code: OpCode var code: OpCode
if self.scopeDepth == 0: if self.scopeDepth == 0:
@ -628,9 +650,9 @@ proc deleteVariable(self: ref Compiler, canAssign: bool) =
proc parseBlock(self: ref Compiler) = proc parseBlock(self: ref Compiler) =
## Parses a block statement, which is basically ## Parses a block statement, which is basically
## a list of other statements ## a list of other statements
while not self.parser.check(RB) and not self.parser.check(EOF): while not self.parser.check(TokenType.RB) and not self.parser.check(TokenType.EOF):
self.declaration() self.declaration()
self.parser.consume(RB, "Expecting '}' after block statement") self.parser.consume(TokenType.RB, "Expecting '}' after block statement")
proc beginScope(self: ref Compiler) = proc beginScope(self: ref Compiler) =
@ -685,25 +707,25 @@ proc patchJump(self: ref Compiler, offset: int) =
proc ifStatement(self: ref Compiler) = proc ifStatement(self: ref Compiler) =
## Parses if statements in a C-style fashion ## Parses if statements in a C-style fashion
self.parser.consume(LP, "The if condition must be parenthesized") self.parser.consume(TokenType.LP, "The if condition must be parenthesized")
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.expression() self.expression()
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.parser.consume(RP, "The if condition must be parenthesized") self.parser.consume(TokenType.RP, "The if condition must be parenthesized")
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
var jump: int = self.emitJump(OpCode.JumpIfFalse) var jump: int = self.emitJump(OpCode.JumpIfFalse)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
self.statement() self.statement()
var elseJump = self.emitJump(OpCode.Jump) var elseJump = self.emitJump(OpCode.Jump)
self.patchJump(jump) self.patchJump(jump)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
if self.parser.match(ELSE): if self.parser.match(TokenType.ELSE):
self.statement() self.statement()
self.patchJump(elseJump) self.patchJump(elseJump)
else: else:
self.parser.parseError(self.parser.previous, "Invalid syntax") self.parser.parseError(self.parser.previous(), "Invalid syntax")
else: else:
self.parser.parseError(self.parser.previous, "The if condition must be parenthesized") self.parser.parseError(self.parser.previous(), "The if condition must be parenthesized")
proc emitLoop(self: ref Compiler, start: int) = proc emitLoop(self: ref Compiler, start: int) =
@ -739,12 +761,12 @@ proc whileStatement(self: ref Compiler) =
## Parses while loops in a C-style fashion ## Parses while loops in a C-style fashion
var loop = Loop(depth: self.scopeDepth, outer: self.loop, start: self.currentChunk.code.len, alive: true, loopEnd: -1) var loop = Loop(depth: self.scopeDepth, outer: self.loop, start: self.currentChunk.code.len, alive: true, loopEnd: -1)
self.loop = loop self.loop = loop
self.parser.consume(LP, "The loop condition must be parenthesized") self.parser.consume(TokenType.LP, "The loop condition must be parenthesized")
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.expression() self.expression()
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.parser.consume(RP, "The loop condition must be parenthesized") self.parser.consume(TokenType.RP, "The loop condition must be parenthesized")
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse) self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
self.loop.body = self.currentChunk.code.len self.loop.body = self.currentChunk.code.len
@ -753,56 +775,56 @@ proc whileStatement(self: ref Compiler) =
self.patchJump(self.loop.loopEnd) self.patchJump(self.loop.loopEnd)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
else: else:
self.parser.parseError(self.parser.previous, "Invalid syntax") self.parser.parseError(self.parser.previous(), "Invalid syntax")
else: else:
self.parser.parseError(self.parser.previous, "The loop condition must be parenthesized") self.parser.parseError(self.parser.previous(), "The loop condition must be parenthesized")
self.endLooping() self.endLooping()
proc forStatement(self: ref Compiler) = proc forStatement(self: ref Compiler) =
## Parses for loops in a C-style fashion ## Parses for loops in a C-style fashion
self.beginScope() self.beginScope()
self.parser.consume(LP, "The loop condition must be parenthesized") self.parser.consume(TokenType.LP, "The loop condition must be parenthesized")
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
if self.parser.match(SEMICOLON): if self.parser.match(TokenType.SEMICOLON):
discard discard
elif self.parser.match(VAR): elif self.parser.match(TokenType.VAR):
self.varDeclaration() self.varDeclaration()
else: else:
self.expressionStatement() self.expressionStatement()
var loop = Loop(depth: self.scopeDepth, outer: self.loop, start: self.currentChunk.code.len, alive: true, loopEnd: -1) var loop = Loop(depth: self.scopeDepth, outer: self.loop, start: self.currentChunk.code.len, alive: true, loopEnd: -1)
self.loop = loop self.loop = loop
if not self.parser.match(SEMICOLON): if not self.parser.match(TokenType.SEMICOLON):
self.expression() self.expression()
if self.parser.previous.kind != EOF: if self.parser.previous.kind != TokenType.EOF:
self.parser.consume(SEMICOLON, "Expecting ';'") self.parser.consume(TokenType.SEMICOLON, "Expecting ';'")
self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse) self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
else: else:
self.parser.current -= 1 self.parser.current -= 1
self.parser.parseError(self.parser.previous, "Invalid syntax") self.parser.parseError(self.parser.previous(), "Invalid syntax")
if not self.parser.match(RP): if not self.parser.match(RP):
var bodyJump = self.emitJump(OpCode.Jump) var bodyJump = self.emitJump(OpCode.Jump)
var incrementStart = self.currentChunk.code.len var incrementStart = self.currentChunk.code.len
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.expression() self.expression()
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
self.parser.consume(RP, "The loop condition must be parenthesized") self.parser.consume(TokenType.RP, "The loop condition must be parenthesized")
self.emitLoop(self.loop.start) self.emitLoop(self.loop.start)
self.loop.start = incrementStart self.loop.start = incrementStart
self.patchJump(bodyJump) self.patchJump(bodyJump)
if self.parser.peek.kind != EOF: if self.parser.peek.kind != TokenType.EOF:
self.loop.body = self.currentChunk.code.len self.loop.body = self.currentChunk.code.len
self.statement() self.statement()
self.emitLoop(self.loop.start) self.emitLoop(self.loop.start)
else: else:
self.parser.current -= 1 self.parser.current -= 1
self.parser.parseError(self.parser.previous, "Invalid syntax") self.parser.parseError(self.parser.previous(), "Invalid syntax")
if self.loop.loopEnd != -1: if self.loop.loopEnd != -1:
self.patchJump(self.loop.loopEnd) self.patchJump(self.loop.loopEnd)
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
else: else:
self.parser.parseError(self.parser.previous, "The loop condition must be parenthesized") self.parser.parseError(self.parser.previous(), "The loop condition must be parenthesized")
self.endLooping() self.endLooping()
self.endScope() self.endScope()
@ -814,13 +836,14 @@ proc parseBreak(self: ref Compiler) =
if not self.loop.alive: if not self.loop.alive:
self.parser.parseError(self.parser.previous, "'break' outside loop") self.parser.parseError(self.parser.previous, "'break' outside loop")
else: else:
self.parser.consume(SEMICOLON, "missing semicolon after statement") self.parser.consume(TokenType.SEMICOLON, "missing semicolon after statement")
var i = self.localCount - 1 var i = self.localCount - 1
while i >= 0 and self.locals[i].depth > self.loop.depth: while i >= 0 and self.locals[i].depth > self.loop.depth:
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
i -= 1 i -= 1
discard self.emitJump(OpCode.Break) discard self.emitJump(OpCode.Break)
proc parseAnd(self: ref Compiler, canAssign: bool) = proc parseAnd(self: ref Compiler, canAssign: bool) =
## Parses expressions such as a and b ## Parses expressions such as a and b
var jump = self.emitJump(OpCode.JumpIfFalse) var jump = self.emitJump(OpCode.JumpIfFalse)
@ -846,7 +869,7 @@ proc continueStatement(self: ref Compiler) =
if not self.loop.alive: if not self.loop.alive:
self.parser.parseError(self.parser.previous, "'continue' outside loop") self.parser.parseError(self.parser.previous, "'continue' outside loop")
else: else:
self.parser.consume(SEMICOLON, "missing semicolon after statement") self.parser.consume(TokenType.SEMICOLON, "missing semicolon after statement")
var i = self.localCount - 1 var i = self.localCount - 1
while i >= 0 and self.locals[i].depth > self.loop.depth: while i >= 0 and self.locals[i].depth > self.loop.depth:
self.emitByte(OpCode.Pop) self.emitByte(OpCode.Pop)
@ -887,7 +910,7 @@ proc parseFunction(self: ref Compiler, funType: FunctionType) =
self.function.arity -= 1 self.function.arity -= 1
self.function.optionals += 1 self.function.optionals += 1
self.expression() self.expression()
self.function.defaults[paramNames[len(paramNames) - 1]] = self.parser.previous.literal # self.function.defaults.add(self.parser.previous.lexeme) # TODO
defaultFollows = true defaultFollows = true
elif defaultFollows: elif defaultFollows:
self.compileError("non-default argument follows default argument") self.compileError("non-default argument follows default argument")
@ -900,10 +923,10 @@ proc parseFunction(self: ref Compiler, funType: FunctionType) =
var fun = self.endCompiler() var fun = self.endCompiler()
self = self.enclosing self = self.enclosing
if self.currentChunk.consts.len < 255: if self.currentChunk.consts.len < 255:
self.emitBytes(OpCode.Constant, self.makeConstant(Value(kind: OBJECT, obj: fun))) self.emitBytes(OpCode.Constant, self.makeConstant(fun))
else: else:
self.emitByte(OpCode.ConstantLong) self.emitByte(OpCode.ConstantLong)
self.emitBytes(self.makeLongConstant(Value(kind: OBJECT, obj: fun))) self.emitBytes(self.makeLongConstant(fun))
proc funDeclaration(self: ref Compiler) = proc funDeclaration(self: ref Compiler) =
@ -961,9 +984,9 @@ proc statement(self: ref Compiler) =
self.whileStatement() self.whileStatement()
elif self.parser.match(TokenType.RETURN): elif self.parser.match(TokenType.RETURN):
self.returnStatement() self.returnStatement()
elif self.parser.match(CONTINUE): elif self.parser.match(TokenType.CONTINUE):
self.continueStatement() self.continueStatement()
elif self.parser.match(BREAK): elif self.parser.match(TokenType.BREAK):
self.parseBreak() self.parseBreak()
elif self.parser.match(LB): elif self.parser.match(LB):
self.beginScope() self.beginScope()
@ -1012,7 +1035,7 @@ var rules: array[TokenType, ParseRule] = [
makeRule(nil, nil, Precedence.None), # RS makeRule(nil, nil, Precedence.None), # RS
makeRule(number, nil, Precedence.None), # NUMBER makeRule(number, nil, Precedence.None), # NUMBER
makeRule(strVal, nil, Precedence.None), # STR makeRule(strVal, nil, Precedence.None), # STR
makeRule(nil, nil, Precedence.None), # SEMICOLON makeRule(nil, nil, Precedence.None), # semicolon
makeRule(nil, parseAnd, Precedence.And), # AND makeRule(nil, parseAnd, Precedence.And), # AND
makeRule(nil, nil, Precedence.None), # CLASS makeRule(nil, nil, Precedence.None), # CLASS
makeRule(nil, nil, Precedence.None), # ELSE makeRule(nil, nil, Precedence.None), # ELSE
@ -1031,7 +1054,7 @@ var rules: array[TokenType, ParseRule] = [
makeRule(nil, nil, Precedence.None), # DEL # TODO: Fix del statement to make it GC-aware makeRule(nil, nil, Precedence.None), # DEL # TODO: Fix del statement to make it GC-aware
makeRule(nil, nil, Precedence.None), # BREAK makeRule(nil, nil, Precedence.None), # BREAK
makeRule(nil, nil, Precedence.None), # EOF makeRule(nil, nil, Precedence.None), # EOF
makeRule(nil, nil, Precedence.None), # COLON makeRule(nil, nil, Precedence.None), # TokenType.COLON
makeRule(nil, nil, Precedence.None), # CONTINUE makeRule(nil, nil, Precedence.None), # CONTINUE
makeRule(nil, binary, Precedence.Term), # CARET makeRule(nil, binary, Precedence.Term), # CARET
makeRule(nil, binary, Precedence.Term), # SHL makeRule(nil, binary, Precedence.Term), # SHL
@ -1069,6 +1092,9 @@ proc compile*(self: ref Compiler, source: string): ptr Function =
return nil return nil
proc initParser*(tokens: seq[Token], file: string): Parser =
result = Parser(current: 0, tokens: tokens, hadError: false, panicMode: false, file: file)
proc initCompiler*(context: FunctionType, enclosing: ref Compiler = nil, parser: Parser = initParser(@[], ""), file: string): ref Compiler = proc initCompiler*(context: FunctionType, enclosing: ref Compiler = nil, parser: Parser = initParser(@[], ""), file: string): ref Compiler =
## Initializes a new compiler object and returns a reference ## Initializes a new compiler object and returns a reference
## to it ## to it
@ -1088,7 +1114,7 @@ proc initCompiler*(context: FunctionType, enclosing: ref Compiler = nil, parser:
inc(result.localCount) inc(result.localCount)
result.function = result.markObject(newFunction()) result.function = result.markObject(newFunction())
if context != SCRIPT: # If we're compiling a function, we give it its name if context != SCRIPT: # If we're compiling a function, we give it its name
result.function.name = newString(enclosing.parser.previous().lexeme) result.function.name = jstring.newString(enclosing.parser.previous().lexeme)
# This way the compiler can be executed on its own # This way the compiler can be executed on its own
# without the VM # without the VM

View File

@ -12,14 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import tokentype
import ../types/japlvalue
# Token object
type const FRAMES_MAX* = 400 # TODO: Inspect why the VM crashes if this exceeds 400
Token* = ref object const JAPL_VERSION* = "0.2.0"
kind*: TokenType const JAPL_RELEASE* = "alpha"
lexeme*: string const DEBUG_TRACE_VM* = true # Traces VM execution
literal*: Value const DEBUG_TRACE_GC* = true # Traces the garbage collector (TODO)
line*: int const DEBUG_TRACE_ALLOCATION* = true # Traces memory allocation/deallocation (WIP)
const DEBUG_TRACE_COMPILER* = true # Traces the compiler (TODO)

View File

@ -17,20 +17,17 @@
## This module has been designed to be easily extendible in its functionality ## This module has been designed to be easily extendible in its functionality
## given that JAPL is in a state of high activity and many features are ## given that JAPL is in a state of high activity and many features are
## being added along the way. To add support for a new keyword, just create ## being added along the way. To add support for a new keyword, just create
## an appropriate TokenType entry in the enum in the file at meta/tokentype.nim ## an appropriate TokenType entry in the enum in the file at meta/token.nim
## and then add it to the constant RESERVED table. A similar approach applies for ## and then add it to the constant RESERVED table. A similar approach applies for
## other tokens, but multi-character ones require more tweaking. ## other tokens, but multi-character ones require more tweaking.
## Since this lexer scans the given source string character by character, unicode ## Since this lexer scans the given source string character by character, unicode
## identifiers are not supported ## identifiers are not supported (and are not planned to be anytime soon)
import system
import strutils import strutils
import strformat import strformat
import tables import tables
import meta/tokentype import meta/token
import meta/tokenobject
import types/stringtype
import types/japlvalue
# Table of all tokens except reserved keywords # Table of all tokens except reserved keywords
const TOKENS = to_table({ const TOKENS = to_table({
@ -126,11 +123,10 @@ proc peekNext(self: Lexer): char =
result = self.source[self.current + 1] result = self.source[self.current + 1]
proc createToken(self: var Lexer, tokenType: TokenType, literal: Value): Token = proc createToken(self: var Lexer, tokenType: TokenType): Token =
## Creates a token object for later use in the parser ## Creates a token object for later use in the parser
result = Token(kind: tokenType, result = Token(kind: tokenType,
lexeme: self.source[self.start..<self.current], lexeme: self.source[self.start..<self.current],
literal: literal,
line: self.line line: self.line
) )
@ -145,31 +141,19 @@ proc parseString(self: var Lexer, delimiter: char) =
stderr.write(&"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> Unterminated string literal\n") stderr.write(&"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> Unterminated string literal\n")
self.errored = true self.errored = true
discard self.step() discard self.step()
let value = self.source[self.start..<self.current].asStr() # Get the value between quotes let token = self.createToken(TokenType.STR)
let token = self.createToken(STR, value)
self.tokens.add(token) self.tokens.add(token)
proc parseNumber(self: var Lexer) = proc parseNumber(self: var Lexer) =
## Parses numeric literals ## Parses numeric literals
# TODO: Move the integer conversion to the
# compiler for finer error reporting and
# handling
while isDigit(self.peek()): while isDigit(self.peek()):
discard self.step() discard self.step()
try:
if self.peek() == '.': if self.peek() == '.':
discard self.step() discard self.step()
while self.peek().isDigit(): while self.peek().isDigit():
discard self.step() discard self.step()
var value = parseFloat(self.source[self.start..<self.current]).asFloat() self.tokens.add(self.createToken(TokenType.NUMBER))
self.tokens.add(self.createToken(TokenType.NUMBER, value))
else:
var value = parseInt(self.source[self.start..<self.current]).asInt()
self.tokens.add(self.createToken(TokenType.NUMBER, value))
except ValueError:
stderr.write(&"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> integer is too big to convert to int64\n")
self.errored = true
proc parseIdentifier(self: var Lexer) = proc parseIdentifier(self: var Lexer) =
@ -179,11 +163,10 @@ proc parseIdentifier(self: var Lexer) =
while self.peek().isAlphaNumeric(): while self.peek().isAlphaNumeric():
discard self.step() discard self.step()
var text: string = self.source[self.start..<self.current] var text: string = self.source[self.start..<self.current]
var keyword = text in RESERVED if text in RESERVED:
if keyword: self.tokens.add(self.createToken(RESERVED[text]))
self.tokens.add(self.createToken(RESERVED[text], text.asStr()))
else: else:
self.tokens.add(self.createToken(ID, text.asStr())) self.tokens.add(self.createToken(TokenType.ID))
proc parseComment(self: var Lexer) = proc parseComment(self: var Lexer) =
@ -191,6 +174,8 @@ proc parseComment(self: var Lexer) =
## with /* and end with */, and can be nested. ## with /* and end with */, and can be nested.
## A missing comment terminator will raise an ## A missing comment terminator will raise an
## error ## error
# TODO: Multi-line comments should be syntactically
# relevant for documenting modules/functions/classes
var closed = false var closed = false
while not self.done(): while not self.done():
var finish = self.peek() & self.peekNext() var finish = self.peek() & self.peekNext()
@ -231,21 +216,21 @@ proc scanToken(self: var Lexer) =
elif single == '/' and self.match('*'): elif single == '/' and self.match('*'):
self.parseComment() self.parseComment()
elif single == '=' and self.match('='): elif single == '=' and self.match('='):
self.tokens.add(self.createToken(DEQ, "==".asStr())) self.tokens.add(self.createToken(TokenType.DEQ))
elif single == '>' and self.match('='): elif single == '>' and self.match('='):
self.tokens.add(self.createToken(GE, ">=".asStr())) self.tokens.add(self.createToken(TokenType.GE))
elif single == '>' and self.match('>'): elif single == '>' and self.match('>'):
self.tokens.add(self.createToken(SHR, ">>".asStr())) self.tokens.add(self.createToken(TokenType.SHR))
elif single == '<' and self.match('='): elif single == '<' and self.match('='):
self.tokens.add(self.createToken(LE, "<=".asStr())) self.tokens.add(self.createToken(TokenType.LE))
elif single == '<' and self.match('<'): elif single == '<' and self.match('<'):
self.tokens.add(self.createToken(SHL, ">>".asStr())) self.tokens.add(self.createToken(TokenType.SHL))
elif single == '!' and self.match('='): elif single == '!' and self.match('='):
self.tokens.add(self.createToken(NE, "!=".asStr())) self.tokens.add(self.createToken(TokenType.NE))
elif single == '*' and self.match('*'): elif single == '*' and self.match('*'):
self.tokens.add(self.createToken(POW, "**".asStr())) self.tokens.add(self.createToken(TokenType.POW))
else: else:
self.tokens.add(self.createToken(TOKENS[single], asStr(&"{single}"))) self.tokens.add(self.createToken(TOKENS[single]))
else: else:
self.errored = true self.errored = true
stderr.write(&"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> Unexpected token '{single}'\n") stderr.write(&"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> Unexpected token '{single}'\n")
@ -257,6 +242,6 @@ proc lex*(self: var Lexer): seq[Token] =
while not self.done(): while not self.done():
self.start = self.current self.start = self.current
self.scanToken() self.scanToken()
self.tokens.add(Token(kind: EOF, lexeme: "EOF", literal: Value(kind: ValueType.Nil), line: self.line)) self.tokens.add(Token(kind: TokenType.EOF, lexeme: "EOF", line: self.line))
return self.tokens return self.tokens

56
src/meta/frame.nim Normal file
View File

@ -0,0 +1,56 @@
# 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 of JAPL call frames. A call frame
## is a subset of the VM's stack that represent
## functions' local space
import ../types/jobject
type
CallFrame* = ref object # FIXME: Call frames are broken (end indexes are likely wrong)
function*: ptr Function
ip*: int
slot*: int
endSlot*: int
stack*: seq[ptr Obj]
proc getView*(self: CallFrame): seq[ptr Obj] =
result = self.stack[self.slot..self.endSlot - 1]
proc getAbsIndex(self: CallFrame, idx: int): int =
return idx + len(self.getView()) - 1 # TODO: Inspect this code (locals, functions)
proc len*(self: CallFrame): int =
result = len(self.getView())
proc `[]`*(self: CallFrame, idx: int): ptr Obj =
result = self.stack[self.getAbsIndex(idx)]
proc `[]=`*(self: CallFrame, idx: int, val: ptr Obj) =
if idx < self.slot:
raise newException(IndexError, "CallFrame index out of range")
self.stack[self.getAbsIndex(idx)] = val
proc delete*(self: CallFrame, idx: int) =
if idx < self.slot or idx > self.endSlot:
raise newException(IndexError, "CallFrame index out of range")
self.stack.delete(idx)

View File

@ -15,7 +15,7 @@
## The module dedicated to the Chunk type ## The module dedicated to the Chunk type
## A chunk is a piece of bytecode. ## A chunk is a piece of bytecode.
import ../types/japlvalue import ../types/jobject
type type
OpCode* {.pure.} = enum OpCode* {.pure.} = enum
@ -104,13 +104,13 @@ proc freeChunk*(self: Chunk) =
self.lines = @[] self.lines = @[]
proc addConstant*(self: Chunk, constant: Value): int = proc addConstant*(self: Chunk, constant: ptr Obj): int =
## Adds a constant to a chunk. Returns its index. ## Adds a constant to a chunk. Returns its index.
self.consts.add(constant) self.consts.add(constant)
return self.consts.high() # The index of the constant return self.consts.high() # The index of the constant
proc writeConstant*(self: Chunk, constant: Value): array[3, uint8] = proc writeConstant*(self: Chunk, constant: ptr Obj): array[3, uint8] =
## Writes a constant to a chunk. Returns its index casted to an array. ## Writes a constant to a chunk. Returns its index casted to an array.
## TODO newdoc ## TODO newdoc
let index = self.addConstant(constant) let index = self.addConstant(constant)

View File

@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# Token types enumeration # Token object
type type
TokenType* = enum TokenType* {.pure.} = enum
# Token types enumeration
PLUS, MINUS, SLASH, STAR, PLUS, MINUS, SLASH, STAR,
NEG, NE, EQ, DEQ, LT, GE, NEG, NE, EQ, DEQ, LT, GE,
LE, MOD, POW, GT, LP, RP, LS LE, MOD, POW, GT, LP, RP, LS
@ -29,3 +30,7 @@ type
COLON, CONTINUE, CARET, COLON, CONTINUE, CARET,
SHL, SHR, NAN, INF, BAND, SHL, SHR, NAN, INF, BAND,
BOR, TILDE BOR, TILDE
Token* = ref object
kind*: TokenType
lexeme*: string
line*: int

View File

@ -15,10 +15,9 @@
## Defines JAPL exceptions ## Defines JAPL exceptions
import stringtype import jstring
import japlvalue import jobject
import strformat import strformat
import ../memory
proc stringify*(self: ptr JAPLException): string = proc stringify*(self: ptr JAPLException): string =

View File

@ -19,11 +19,10 @@
# code objects that can be compiled inside the JAPL runtime, pretty much # code objects that can be compiled inside the JAPL runtime, pretty much
# like in Python # like in Python
import stringtype import jstring
import jobject
import strformat import strformat
import ../memory
import ../meta/opcode import ../meta/opcode
import japlvalue
type type

View File

@ -14,3 +14,9 @@
#TODO: Implement #TODO: Implement
proc hashFloat(f: float): uint32 =
# TODO: Any improvement?
result = 2166136261u32
result = result xor uint32 f
result *= 16777619

View File

@ -13,4 +13,4 @@
# limitations under the License. # limitations under the License.
#TODO: Implement import jobject

View File

@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## Base structure for objs and objects in JAPL, all ## Base structure for objects in JAPL, all
## types inherit from this simple structure ## types inherit from this simple structure
import tables
import ../memory import ../memory
type type
Chunk* = ref object Chunk* = ref object
## A piece of bytecode. ## A piece of bytecode.
@ -36,7 +36,7 @@ type
Obj* = object of RootObj Obj* = object of RootObj
# The object that rules them all # The object that rules them all
kind*: ObjectType kind*: ObjectType
hashobj*: uint32 hashValue*: uint32
String* = object of Obj # A string object String* = object of Obj # A string object
str*: ptr UncheckedArray[char] # TODO -> Unicode support str*: ptr UncheckedArray[char] # TODO -> Unicode support
len*: int len*: int
@ -102,6 +102,7 @@ template allocateObj*(kind: untyped, objType: ObjectType): untyped =
## to a more specific type ## to a more specific type
cast[ptr kind](allocateObject(sizeof kind, objType)) cast[ptr kind](allocateObject(sizeof kind, objType))
proc objType*(obj: ptr Obj): ObjectType = proc objType*(obj: ptr Obj): ObjectType =
## Returns the type of the object ## Returns the type of the object
return obj.kind return obj.kind
@ -117,6 +118,16 @@ proc stringify*(obj: ptr Obj): string =
result = "<object (built-in type)>" result = "<object (built-in type)>"
proc isFalsey*(obj: ptr Obj): bool =
## Returns true if the given
## object is falsey
if obj.kind != ObjectType.BaseObject: # NOTE: Consider how to reduce the boilerplate
var newObj = convert obj
result = newObj.isFalsey()
else:
result = false
proc typeName*(obj: ptr Obj): string = proc typeName*(obj: ptr Obj): string =
## This method should return the ## This method should return the
## name of the object type ## name of the object type
@ -127,7 +138,7 @@ proc typeName*(obj: ptr Obj): string =
result = "object" result = "object"
# TODO migrate to operations
proc bool*(obj: ptr Obj): bool = proc bool*(obj: ptr Obj): bool =
## Returns wheter the object should ## Returns wheter the object should
## be considered a falsey obj ## be considered a falsey obj
@ -141,20 +152,21 @@ proc bool*(obj: ptr Obj): bool =
result = false result = false
# TODO migrate to operations
proc eq*(a: ptr Obj, b: ptr Obj): bool = proc eq*(a: ptr Obj, b: ptr Obj): bool =
## Compares two objects for equality ## Compares two objects for equality
if a.kind != ObjectType.BaseObject: if a.kind != ObjectType.BaseObject:
var newObj = convert(a) var newObj = convert(a)
result = newObj.eq(b) result = newObj.eq(b)
else: else:
result = a.kind == b.kind result = a.kind == b.kind
# TODO migrate to operations
proc hash*(self: ptr Obj): uint32 = proc hash*(self: ptr Obj): uint32 =
# TODO: Make this actually useful # TODO: Make this actually useful
result = 2166136261u32 if self.kind == ObjectType.BaseObject:
result = 2166136261u32
else:
result = convert(self).hash()
proc add(self, other: ptr Obj): ptr Obj = proc add(self, other: ptr Obj): ptr Obj =
@ -211,95 +223,110 @@ proc binaryXor(self, other: ptr Obj): ptr Obj =
result = nil result = nil
func isNil*(obj: ptr Obj): bool = proc isNil*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL nil object ## is a JAPL nil object
result = obj.kind == ObjectType.Nil result = obj.kind == ObjectType.Nil
func isBool*(obj: ptr Obj): bool = proc isBool*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL bool ## is a JAPL bool
result = obj.kind == ObjectType.Bool result = obj.kind == ObjectType.Bool
func isInt*(obj: ptr Obj): bool = proc isInt*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL integer ## is a JAPL integer
result = obj.kind == ObjectType.Integer result = obj.kind == ObjectType.Integer
func isFloat*(obj: ptr Obj): bool = proc isFloat*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL float ## is a JAPL float
result = obj.kind == ObjectType.Float result = obj.kind == ObjectType.Float
func isInf*(obj: ptr Obj): bool = proc isInf*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL inf object ## is a JAPL inf object
result = obj.kind == ObjectType.Infinity result = obj.kind == ObjectType.Infinity
func isNan*(obj: ptr Obj): bool = proc isNan*(obj: ptr Obj): bool =
## Returns true if the given obj ## Returns true if the given obj
## is a JAPL nan object ## is a JAPL nan object
result = obj.kind == ObjectType.Nan result = obj.kind == ObjectType.Nan
func isNum*(obj: ptr Obj): bool = proc isNum*(obj: ptr Obj): bool =
## Returns true if the given obj is ## Returns true if the given obj is
## either a JAPL number, nan or inf ## either a JAPL number, nan or inf
result = isInt(obj) or isFloat(obj) or isInf(obj) or isNan(obj) result = isInt(obj) or isFloat(obj) or isInf(obj) or isNan(obj)
proc isStr*(obj: ptr Obj): bool =
func isStr*(obj: ptr Obj): bool =
## Returns true if the given object is a JAPL string ## Returns true if the given object is a JAPL string
result = obj.kind == ObjectType.String result = obj.kind == ObjectType.String
func toBool*(obj: ptr Obj): bool = proc toBool*(obj: ptr Obj): bool =
## Converts a JAPL bool to a nim bool ## Converts a JAPL bool to a nim bool
result = cast[ptr Bool](obj).boolValue result = cast[ptr Bool](obj).boolValue
func toInt*(obj: ptr Obj): int = proc toInt*(obj: ptr Obj): int =
## Converts a JAPL int to a nim int ## Converts a JAPL int to a nim int
result = cast[ptr Integer](obj).intValue result = cast[ptr Integer](obj).intValue
func toFloat*(obj: ptr Obj): float = proc toFloat*(obj: ptr Obj): float =
## Converts a JAPL float to a nim float ## Converts a JAPL float to a nim float
result = cast[ptr Float](obj).floatValue result = cast[ptr Float](obj).floatValue
# TODO ambiguous naming: conflict with toString(obj: obj) that does JAPL->JAPL # TODO ambiguous naming: conflict with toString(obj: obj) that does JAPL->JAPL
func toStr*(obj: ptr Obj): string = proc toStr*(obj: ptr Obj): string =
## Converts a JAPL string into a nim string ## Converts a JAPL string into a nim string
var strObj = cast[ptr String](obj) var strObj = cast[ptr String](obj)
for i in 0..strObj.str.len - 1: for i in 0..strObj.str.len - 1:
result.add(strObj.str[i]) result.add(strObj.str[i])
func asInt*(n: int): ptr Obj = proc asInt*(n: int): ptr Integer =
## Creates an int object ## Creates an int object
result = allocateOb result = allocateObj(Integer, ObjectType.Integer)
result.intValue = n
func asFloat*(n: float): ptr Obj = proc asFloat*(n: float): ptr Float =
## Creates a float object (double) ## Creates a float object (double)
result = obj(kind: objType.Double, floatobj: n) result = allocateObj(Float, ObjectType.Float)
result.floatValue = n
func asBool*(b: bool): ptr Obj = proc asBool*(b: bool): ptr Bool =
## Creates a boolean object ## Creates a boolean object
result = obj(kind: objType.Bool, boolobj: b) result = allocateObj(Bool, ObjectType.Bool)
result.boolValue = b
func asObj*(obj: ptr Obj): obj = proc asNil*(): ptr Nil =
## Creates a object of ObjectType.BaseObject as type and obj (arg 1) as ## Creates a nil object
## contained obj result = allocateObj(Nil, ObjectType.Nil)
result = Object(kind: objType.Object, obj: obj)
proc asNan*(): ptr NotANumber =
## Creates a nil object
result = allocateObj(NotANumber, ObjectType.Nan)
proc asInf*(): ptr Infinity =
## Creates a nil object
result = allocateObj(Infinity, ObjectType.Infinity)
proc asObj*(obj: ptr Obj): ptr Obj =
## Creates a generic JAPL object
result = allocateObj(Obj, ObjectType.BaseObject)

View File

@ -17,7 +17,7 @@
# therefore immutable from the user's perspective. They are # therefore immutable from the user's perspective. They are
# natively ASCII encoded, but soon they will support for unicode. # natively ASCII encoded, but soon they will support for unicode.
import japlvalue import jobject
import strformat import strformat
import ../memory import ../memory
@ -67,6 +67,6 @@ proc typeName*(s: ptr String): string =
return "string" return "string"
proc asStr*(s: string): Value = proc asStr*(s: string): ptr Obj =
## Creates a string object ## Creates a string object
result = Value(kind: ValueType.Object, obj: newString(s)) result = newString(s)

View File

@ -16,8 +16,7 @@
## screen. ## screen.
import ../meta/opcode import ../meta/opcode
import ../types/japlvalue import ../types/jobject
import ../types/operations
import strformat import strformat

View File

@ -12,33 +12,29 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## The JAPL runtime environment, or virtual machine. This is ## A stack-based bytecode virtual machine implementation.
## a stack-based bytecode VM. ## This is the entire runtime environment for JAPL
import algorithm import algorithm
import bitops
import strutils
import strformat import strformat
import math import math
import lenientops import lenientops
import common import config
import compiler import compiler
import tables import tables
import meta/opcode import meta/opcode
import meta/frame
import types/exceptions import types/exceptions
import types/japlvalue import types/jobject
import types/stringtype import types/jstring
import types/function import types/function
import types/operations
import memory import memory
when DEBUG_TRACE_VM: when DEBUG_TRACE_VM:
import util/debug import util/debug
## Move these into appropriate int/float modules
proc `**`(a, b: int): int = pow(a.float, b.float).int proc `**`(a, b: int): int = pow(a.float, b.float).int
proc `**`(a, b: float): float = pow(a, b) proc `**`(a, b: float): float = pow(a, b)
@ -49,6 +45,16 @@ type
OK, OK,
COMPILE_ERROR, COMPILE_ERROR,
RUNTIME_ERROR RUNTIME_ERROR
VM* = ref object # The VM object
lastPop*: ptr Obj
frameCount*: int
source*: string
frames*: seq[CallFrame]
stack*: seq[ptr Obj]
stackTop*: int
objects*: seq[ptr Obj]
globals*: Table[string, ptr Obj]
file*: string
func handleInterrupt() {.noconv.} = func handleInterrupt() {.noconv.} =
@ -97,20 +103,20 @@ proc error*(self: var VM, error: ptr JAPLException) =
self.resetStack() self.resetStack()
proc pop*(self: var VM): Value = proc pop*(self: var VM): ptr Obj =
## Pops a value off the stack ## Pops a value off the stack
result = self.stack.pop() result = self.stack.pop()
self.stackTop -= 1 self.stackTop -= 1
proc push*(self: var VM, value: Value) = proc push*(self: var VM, obj: ptr Obj) =
## Pushes a value onto the stack ## Pushes an object onto the stack
self.stack.add(value) self.stack.add(obj)
self.stackTop += 1 self.stackTop += 1
proc peek*(self: var VM, distance: int): Value = proc peek*(self: var VM, distance: int): ptr Obj =
## Peeks a value (at a given distance from the ## Peeks an object (at a given distance from the
## current index) from the stack ## current index) from the stack
return self.stack[self.stackTop - distance - 1] return self.stack[self.stackTop - distance - 1]
@ -124,6 +130,7 @@ template addObject*(self: ptr VM, obj: ptr Obj): untyped =
temp temp
# TODO: Move this to jobject.nim
proc slice(self: var VM): bool = proc slice(self: var VM): bool =
## Handles single-operator slice expressions ## Handles single-operator slice expressions
## (consider moving this to an appropriate ## (consider moving this to an appropriate
@ -131,32 +138,29 @@ proc slice(self: var VM): bool =
var idx = self.pop() var idx = self.pop()
var peeked = self.pop() var peeked = self.pop()
case peeked.kind: case peeked.kind:
of OBJECT: of ObjectType.String:
case peeked.obj.kind: var str = peeked.toStr()
of ObjectType.String: if not idx.isInt():
var str = peeked.toStr() self.error(newTypeError("string indeces must be integers"))
if not idx.isInt(): return false
self.error(newTypeError("string indeces must be integers")) else:
return false var index: int = idx.toInt()
elif idx.toInt() < 0: if index < 0:
idx.intValue = len(str) + idx.toInt() index = len(str) + idx.toInt()
if idx.toInt() < 0: 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
if idx.toInt() - 1 > len(str) - 1:
self.error(newIndexError("string index out of bounds")) self.error(newIndexError("string index out of bounds"))
return false return false
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(&"{str[idx.toInt()]}")))) elif index - 1 > len(str) - 1:
return true self.error(newIndexError("string index out of bounds"))
else:
self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'"))
return false return false
else:
self.push(addObject(addr self, jstring.newString(&"{str[index]}")))
return true
else: else:
self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'")) self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'"))
return false return false
# TODO: Move this to jobject.nim
proc sliceRange(self: var VM): bool = proc sliceRange(self: var VM): bool =
## Handles slices when there's both a start ## Handles slices when there's both a start
## and an end index (even implicit ones) ## and an end index (even implicit ones)
@ -164,39 +168,36 @@ proc sliceRange(self: var VM): bool =
var sliceStart = self.pop() var sliceStart = self.pop()
var popped = self.pop() var popped = self.pop()
case popped.kind: case popped.kind:
of OBJECT: of ObjectType.String:
case popped.obj.kind: var str = popped.toStr()
of ObjectType.String: if sliceEnd.isNil():
var str = popped.toStr() sliceEnd = len(str).asInt()
if sliceEnd.isNil(): if sliceStart.isNil():
sliceEnd = Value(kind: ValueType.Integer, intValue: len(str)) sliceStart = asInt(0)
if sliceStart.isNil(): elif not sliceStart.isInt() or not sliceEnd.isInt():
sliceStart = Value(kind: ValueType.Integer, intValue: 0) self.error(newTypeError("string indexes must be integers"))
elif not sliceStart.isInt() or not sliceEnd.isInt(): return false
self.error(newTypeError("string indeces must be integers")) else:
return false var startIndex = sliceStart.toInt()
elif sliceStart.toInt() < 0: var endIndex = sliceEnd.toInt()
sliceStart.intValue = len(str) + sliceStart.toInt() if startIndex < 0:
if sliceEnd.toInt() < 0: sliceStart = (len(str) + sliceStart.toInt()).asInt()
sliceEnd.intValue = len(str) + sliceEnd.toInt() if startIndex < 0:
if sliceStart.toInt() - 1 > len(str) - 1: sliceStart = (len(str) + sliceEnd.toInt()).asInt()
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString("")))) elif startIndex - 1 > len(str) - 1:
return true self.push(addObject(addr self, jstring.newString("")))
if sliceEnd.toInt() - 1 > len(str) - 1:
sliceEnd = Value(kind: ValueType.Integer, intValue: len(str))
if sliceStart.toInt() > sliceEnd.toInt():
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(""))))
return true
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(str[sliceStart.toInt()..<sliceEnd.toInt()]))))
return true return true
else: if endIndex - 1 > len(str) - 1:
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'")) sliceEnd = len(str).asInt()
return false if startIndex > endIndex:
self.push(addObject(addr self, jstring.newString("")))
return true
self.push(addObject(addr self, jstring.newString(str[sliceStart.toInt()..<sliceEnd.toInt()])))
return true
else: else:
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'")) self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))
return false return false
proc call(self: var VM, function: ptr Function, argCount: uint8): bool = proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
## Sets up the call frame and performs error checking ## Sets up the call frame and performs error checking
## when calling callables ## when calling callables
@ -214,14 +215,13 @@ proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
return true return true
proc callValue(self: var VM, callee: Value, argCount: uint8): bool = proc callValue(self: var VM, callee: ptr Obj, argCount: uint8): bool =
## Wrapper around call() to do type checking ## Wrapper around call() to do type checking
if callee.isObj(): # TODO: Consider adding a callable() method case callee.kind:
case callee.obj.kind: of ObjectType.Function:
of ObjectType.Function: return self.call(cast[ptr Function](callee), argCount)
return self.call(cast[ptr Function](callee.obj), argCount) else:
else: discard # Not callable
discard # Not callable
self.error(newTypeError(&"object of type '{callee.typeName}' is not callable")) self.error(newTypeError(&"object of type '{callee.typeName}' is not callable"))
return false return false
@ -249,101 +249,17 @@ proc run(self: var VM, repl: bool): InterpretResult =
inc(frame.ip) inc(frame.ip)
inc(frame.ip) inc(frame.ip)
cast[uint16]((frame.function.chunk.code[frame.ip - 2] shl 8) or frame.function.chunk.code[frame.ip - 1]) cast[uint16]((frame.function.chunk.code[frame.ip - 2] shl 8) or frame.function.chunk.code[frame.ip - 1])
template readConstant: Value = template readConstant: ptr Obj =
## Reads a constant from the current ## Reads a constant from the current
## frame's constant table ## frame's constant table
frame.function.chunk.consts[int(readByte())] frame.function.chunk.consts[int(readByte())]
template readLongConstant: Value = template readLongConstant: ptr Obj =
## Reads a long constant from the ## Reads a long constant from the
## current frame's constant table ## current frame's constant table
var arr = [readByte(), readByte(), readByte()] var arr = [readByte(), readByte(), readByte()]
var idx: int var idx: int
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr)) copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
frame.function.chunk.consts[idx] frame.function.chunk.consts[idx]
template binOp(op, check) =
## Performs binary operations on types,
## this will be soon ditched in favor
## of a more idiomatic a.op(b)
var rightVal {.inject.} = self.pop()
var leftVal {.inject.} = self.pop()
if leftVal.isInf():
leftVal = Inf.asFloat()
elif leftVal.isNan():
leftVal = Nan.asFloat()
if rightVal.isNan():
rightVal = Nan.asFloat()
elif rightVal.isInf():
rightVal = Inf.asFloat()
if check(leftVal) and check(rightVal):
if leftVal.isFloat() and rightVal.isInt():
var res = `op`(leftVal.toFloat(), float rightVal.toInt())
if res is bool:
self.push(Value(kind: BOOL, boolValue: bool res))
else:
var res = float res
if res == Inf:
self.push(Value(kind: ValueType.Inf))
elif res == -Inf:
self.push(Value(kind: ValueType.Minf))
else:
self.push(Value(kind: DOUBLE, floatValue: float res))
elif leftVal.isInt() and rightVal.isFloat():
var res = `op`(float leftVal.toInt(), rightVal.toFloat())
if res is bool:
self.push(Value(kind: BOOL, boolValue: bool res))
else:
var res = float res
if res == Inf:
self.push(Value(kind: ValueType.Inf))
elif res == -Inf:
self.push(Value(kind: ValueType.Minf))
else:
self.push(Value(kind: DOUBLE, floatValue: float res))
elif leftVal.isFloat() and rightVal.isFloat():
var res = `op`(leftVal.toFloat(), rightVal.toFloat())
if res is bool:
self.push(Value(kind: BOOL, boolValue: bool res))
else:
var res = float res
if res == Inf:
self.push(Value(kind: ValueType.Inf))
elif res == -Inf:
self.push(Value(kind: ValueType.Minf))
else:
self.push(Value(kind: DOUBLE, floatValue: float res))
else:
var tmp = `op`(leftVal.toInt(), rightVal.toInt())
var res = float tmp
if tmp is int:
self.push(Value(kind: ValueType.Integer, intValue: int tmp))
elif res == Inf:
self.push(Value(kind: ValueType.Inf))
elif res == -Inf:
self.push(Value(kind: ValueType.Minf))
elif tmp is bool:
self.push(Value(kind: ValueType.Bool, boolValue: bool tmp))
else:
self.push(Value(kind: ValueType.Double, floatValue: float tmp))
else:
self.error(newTypeError(&"unsupported binary operator for objects of type '{leftVal.typeName()}' and '{rightVal.typeName()}'"))
return RUNTIME_ERROR
template binBitWise(op): untyped =
## Handles binary bitwise operators
var rightVal {.inject.} = self.pop()
var leftVal {.inject.} = self.pop()
if isInt(leftVal) and isInt(rightVal):
self.push(Value(kind: ValueType.Integer, intValue: `op`(leftVal.toInt(), rightVal.toInt())))
else:
self.error(newTypeError(&"unsupported binary operator for objects of type '{leftVal.typeName()}' and '{rightVal.typeName()}'"))
return RUNTIME_ERROR
template unBitWise(op): untyped =
## Handles unary bitwise operators
var leftVal {.inject.} = self.pop()
if isInt(leftVal):
self.push(Value(kind: ValueType.Integer, intValue: `op`(leftVal.toInt())))
else:
self.error(newTypeError(&"unsupported unary operator for object of type '{leftVal.typeName()}'"))
return RUNTIME_ERROR
var instruction: uint8 var instruction: uint8
var opcode: OpCode var opcode: OpCode
while true: while true:
@ -377,108 +293,55 @@ proc run(self: var VM, repl: bool): InterpretResult =
discard disassembleInstruction(frame.function.chunk, frame.ip - 1) discard disassembleInstruction(frame.function.chunk, frame.ip - 1)
case opcode: # Main OpCodes dispatcher case opcode: # Main OpCodes dispatcher
of OpCode.Constant: of OpCode.Constant:
var constant: Value = readConstant() var constant: ptr Obj = readConstant()
self.push(constant) self.push(constant)
of OpCode.ConstantLong: of OpCode.ConstantLong:
var constant: Value = readLongConstant() var constant: ptr Obj = readLongConstant()
self.push(constant) self.push(constant)
of OpCode.Negate: of OpCode.Negate: # TODO: Call appropriate methods
var cur = self.pop() discard
case cur.kind:
of ValueType.Double:
cur.floatValue = -cur.toFloat()
self.push(cur)
of ValueType.Integer:
cur.intValue = -cur.toInt()
self.push(cur)
of ValueType.Inf:
self.push(Value(kind: ValueType.Minf))
of ValueType.Minf:
self.push(Value(kind: ValueType.Inf))
else:
self.error(newTypeError(&"unsupported unary operator for object of type '{cur.typeName()}'"))
return RUNTIME_ERROR
of OpCode.Add: of OpCode.Add:
if self.peek(0).isObj() and self.peek(1).isObj(): discard
if self.peek(0).isStr() and self.peek(1).isStr():
var r = self.peek(0).toStr()
var l = self.peek(1).toStr()
let res = Value(kind: OBJECT, obj: addObject(addr self, newString(l & r)))
discard self.pop() # Garbage collector-related paranoia here
discard self.pop()
self.push(res)
else:
self.error(newTypeError(&"unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}'"))
return RUNTIME_ERROR
else:
binOp(`+`, isNum)
of OpCode.Shl: of OpCode.Shl:
binBitWise(`shl`) discard
of OpCode.Shr: of OpCode.Shr:
binBitWise(`shr`) discard
of OpCode.Xor: of OpCode.Xor:
binBitWise(`xor`) discard
of OpCode.Bor: of OpCode.Bor:
binBitWise(bitor) discard
of OpCode.Bnot: of OpCode.Bnot:
unBitWise(bitnot) discard
of OpCode.Band: of OpCode.Band:
binBitWise(bitand) discard
of OpCode.Subtract: of OpCode.Subtract:
binOp(`-`, isNum) discard
of OpCode.Divide: of OpCode.Divide:
binOp(`/`, isNum) discard
of OpCode.Multiply: of OpCode.Multiply:
if self.peek(0).isInt() and self.peek(1).isObj(): discard
if self.peek(1).isStr():
var r = self.pop().toInt() # We don't peek here because integers are not garbage collected (not by us at least)
var l = self.peek(0).toStr()
let res = Value(kind: OBJECT, obj: addObject(addr self, newString(l.repeat(r))))
discard self.pop()
self.push(res)
else:
self.error(newTypeError(&"unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}'"))
return RUNTIME_ERROR
elif self.peek(0).isObj() and self.peek(1).isInt():
if self.peek(0).isStr():
var r = self.peek(0).toStr()
var l = self.peek(1).toInt()
let res = Value(kind: OBJECT, obj: addObject(addr self, newString(r.repeat(l))))
discard self.pop()
self.push(res)
else:
self.error(newTypeError(&"unsupported binary operator for objects of type '{self.peek(0).typeName()}' and '{self.peek(1).typeName()}"))
return RUNTIME_ERROR
else:
binOp(`*`, isNum)
of OpCode.Mod: of OpCode.Mod:
binOp(floorMod, isNum) discard
of OpCode.Pow: of OpCode.Pow:
binOp(`**`, isNum) discard
of OpCode.True: of OpCode.True:
self.push(Value(kind: ValueType.Bool, boolValue: true)) # TODO asBool() ? self.push((true).asBool())
of OpCode.False: of OpCode.False:
self.push(Value(kind: ValueType.Bool, boolValue: false)) self.push((false).asBool())
of OpCode.Nil: of OpCode.Nil:
self.push(Value(kind: ValueType.Nil)) self.push(asNil())
of OpCode.Nan: of OpCode.Nan:
self.push(Value(kind: ValueType.Nan)) self.push(asNan())
of OpCode.Inf: of OpCode.Inf:
self.push(Value(kind: ValueType.Inf)) self.push(asInf())
of OpCode.Not: of OpCode.Not:
self.push(Value(kind: ValueType.Bool, boolValue: isFalsey(self.pop()))) self.push(self.pop().isFalsey().asBool())
of OpCode.Equal: of OpCode.Equal:
var a = self.pop() discard
var b = self.pop()
if a.isFloat() and b.isInt():
b = Value(kind: ValueType.Double, floatValue: float b.toInt())
elif b.isFloat() and a.isInt():
a = Value(kind: ValueType.Double, floatValue: float a.toInt())
self.push(Value(kind: ValueType.Bool, boolValue: eq(a, b)))
of OpCode.Less: of OpCode.Less:
binOp(`<`, isNum) discard
of OpCode.Greater: of OpCode.Greater:
binOp(`>`, isNum) discard
of OpCode.Slice: of OpCode.Slice:
if not self.slice(): if not self.slice():
return RUNTIME_ERROR return RUNTIME_ERROR
@ -586,7 +449,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
if not self.lastPop.isNil() and self.frameCount == 1: # This is to avoid if not self.lastPop.isNil() and self.frameCount == 1: # This is to avoid
# useless output with recursive calls # useless output with recursive calls
echo stringify(self.lastPop) echo stringify(self.lastPop)
self.lastPop = Value(kind: ValueType.Nil) # TODO: asNil()? self.lastPop = asNil()
self.frameCount -= 1 self.frameCount -= 1
discard self.frames.pop() discard self.frames.pop()
if self.frameCount == 0: if self.frameCount == 0:
@ -643,9 +506,8 @@ proc freeVM*(self: var VM) =
proc initVM*(): VM = proc initVM*(): VM =
## Initializes the VM ## Initializes the VM
setControlCHook(handleInterrupt) setControlCHook(handleInterrupt)
var globals: Table[string, Value] = initTable[string, Value]() var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]()
result = VM(lastPop: Value(kind: ValueType.Nil), objects: @[], globals: globals, source: "", file: "") result = VM(lastPop: asNil(), objects: @[], globals: globals, source: "", file: "")
# TODO asNil() ?
proc interpret*(self: var VM, source: string, repl: bool = false, file: string): InterpretResult = proc interpret*(self: var VM, source: string, repl: bool = false, file: string): InterpretResult =
@ -660,8 +522,8 @@ proc interpret*(self: var VM, source: string, repl: bool = false, file: string):
# to the vm # to the vm
if compiled == nil: if compiled == nil:
return COMPILE_ERROR return COMPILE_ERROR
self.push(Value(kind: ValueType.Object, obj: compiled)) self.push(compiled)
discard self.callValue(Value(kind: ValueType.Object, obj: compiled), 0) discard self.callValue(compiled, 0)
when DEBUG_TRACE_VM: when DEBUG_TRACE_VM:
echo "==== VM debugger starts ====\n" echo "==== VM debugger starts ====\n"
try: try:

View File

@ -1,6 +0,0 @@
# temporary nim file so I can see if the stuff I've touched compiles :'D
import meta/chunk
import meta/valueobject
import types/objecttype
import util/debug

6
tests/test.nim Normal file
View File

@ -0,0 +1,6 @@
# temporary nim file so I can see if the stuff I've touched compiles :'D
import ..meta/chunk
import ..meta/valueobject
import ..types/objecttype
import ..util/debug

View File

@ -1,68 +0,0 @@
# 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 japlvalue
import stringtype
import function
import exceptions
import strutils
func stringify(obj: ptr Obj): string =
## Returns a string representation of an object
result = convert(obj).stringify()
func isFalsey*(obj: ptr Obj): bool =
case obj.kind:
of ObjectType.String:
result = cast[ptr String](value.obj).isFalsey()
of ObjectType.Function:
result = cast[ptr Function](value.obj).isFalsey()
of ObjectType.Exception:
result = cast[ptr JaplException](value.obj).isFalsey()
of ObjectType.Class:
discard # TODO Class
of ObjectType.Module:
discard # TODO Module
of ObjectType.BaseObject:
result = cast[ptr BaseObject](value.obj).isFalsey() # TODO BaseObject
func typeName*(obj: ptr Obj): string =
## Returns the name of the type of the object
case obj.kind:
of ObjectType.String:
result = cast[ptr String](obj).typeName()
of ObjectType.Function:
result = cast[ptr Function](obj).typeName()
else:
result = "" # TODO unimplemented
proc eq*(a: ptr Obj, b: ptr Obj): bool =
if a.kind != b.kind:
result = false
else:
case a.kind:
of ObjectType.String:
var a = cast[ptr String](a)
var b = cast[ptr String](b)
result = eq(a, b)
of ObjectType.Function:
var a = cast[ptr Function](a)
var b = cast[ptr Function](b)
result = eq(a, b)
else:
discard # TODO: Implement