From 673b88f9de3d15464431e062d7b49d3901cde55f Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Sun, 25 Oct 2020 12:45:03 +0100 Subject: [PATCH] Huge refactoring for the type system and object structure, VM needs opcode refactoring --- common.nim | 126 ------- examples/{examples.jpl => syntax.jpl} | 0 compiler.nim => src/compiler.nim | 218 ++++++----- meta/tokenobject.nim => src/config.nim | 17 +- lexer.nim => src/lexer.nim | 57 ++- main.nim => src/main.nim | 0 memory.nim => src/memory.nim | 0 src/meta/frame.nim | 56 +++ {meta => src/meta}/looptype.nim | 0 {meta => src/meta}/opcode.nim | 6 +- meta/tokentype.nim => src/meta/token.nim | 9 +- {tools => src/tools}/README.md | 0 {tools => src/tools}/importmap.nim | 0 {types => src/types}/arraylist.nim | 0 {types => src/types}/exceptions.nim | 5 +- {types => src/types}/function.nim | 5 +- {types => src/types}/hashmap.nim | 0 types/int.nim => src/types/jfloat.nim | 6 + types/float.nim => src/types/jint.nim | 2 +- types/japlvalue.nim => src/types/jobject.nim | 91 +++-- types/stringtype.nim => src/types/jstring.nim | 6 +- {util => src/util}/debug.nim | 3 +- vm.nim => src/vm.nim | 344 ++++++------------ test.nim | 6 - tests/test.nim | 6 + types/operations.nim | 68 ---- 26 files changed, 400 insertions(+), 631 deletions(-) delete mode 100644 common.nim rename examples/{examples.jpl => syntax.jpl} (100%) rename compiler.nim => src/compiler.nim (85%) rename meta/tokenobject.nim => src/config.nim (58%) rename lexer.nim => src/lexer.nim (81%) rename main.nim => src/main.nim (100%) rename memory.nim => src/memory.nim (100%) create mode 100644 src/meta/frame.nim rename {meta => src/meta}/looptype.nim (100%) rename {meta => src/meta}/opcode.nim (95%) rename meta/tokentype.nim => src/meta/token.nim (86%) rename {tools => src/tools}/README.md (100%) rename {tools => src/tools}/importmap.nim (100%) rename {types => src/types}/arraylist.nim (100%) rename {types => src/types}/exceptions.nim (97%) rename {types => src/types}/function.nim (97%) rename {types => src/types}/hashmap.nim (100%) rename types/int.nim => src/types/jfloat.nim (80%) rename types/float.nim => src/types/jint.nim (97%) rename types/japlvalue.nim => src/types/jobject.nim (82%) rename types/stringtype.nim => src/types/jstring.nim (94%) rename {util => src/util}/debug.nim (98%) rename vm.nim => src/vm.nim (57%) delete mode 100644 test.nim create mode 100644 tests/test.nim delete mode 100644 types/operations.nim diff --git a/common.nim b/common.nim deleted file mode 100644 index 4491ae3..0000000 --- a/common.nim +++ /dev/null @@ -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 - - diff --git a/examples/examples.jpl b/examples/syntax.jpl similarity index 100% rename from examples/examples.jpl rename to examples/syntax.jpl diff --git a/compiler.nim b/src/compiler.nim similarity index 85% rename from compiler.nim rename to src/compiler.nim index ebbf296..069b767 100644 --- a/compiler.nim +++ b/src/compiler.nim @@ -19,13 +19,11 @@ import strutils import algorithm import strformat import lexer -import common import meta/opcode -import meta/tokenobject -import meta/tokentype +import meta/token import meta/looptype -import types/japlvalue -import types/stringtype +import types/jobject +import types/jstring import types/function import tables when isMainModule: @@ -44,6 +42,18 @@ type loop*: Loop objects*: seq[ptr Obj] 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 None, Assignment, @@ -160,29 +170,30 @@ proc emitBytes(self: ref Compiler, bytarr: array[3, uint8]) = 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 ## 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 ## 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) -proc emitConstant(self: ref Compiler, value: Value) = +proc emitConstant(self: ref Compiler, obj: ptr Obj) = ## Emits a Constant or ConstantLong instruction along ## with its operand if self.currentChunk().consts.len > 255: self.emitByte(OpCode.ConstantLong) - self.emitBytes(self.makeLongConstant(value)) + self.emitBytes(self.makeLongConstant(obj)) 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 statement(self: ref Compiler) proc declaration(self: ref Compiler) @@ -236,39 +247,39 @@ proc binary(self: ref Compiler, canAssign: bool) = var rule = getRule(operator) self.parsePrecedence(Precedence((int rule.precedence) + 1)) case operator: - of PLUS: + of TokenType.PLUS: self.emitByte(OpCode.Add) - of MINUS: + of TokenType.MINUS: self.emitByte(OpCode.Subtract) - of SLASH: + of TokenType.SLASH: self.emitByte(OpCode.Divide) - of STAR: + of TokenType.STAR: self.emitByte(OpCode.Multiply) - of MOD: + of TokenType.MOD: self.emitByte(OpCode.Mod) - of POW: + of TokenType.POW: self.emitByte(OpCode.Pow) - of NE: + of TokenType.NE: self.emitBytes(OpCode.Equal, OpCode.Not) - of DEQ: + of TokenType.DEQ: self.emitByte(OpCode.Equal) - of GT: + of TokenType.GT: self.emitByte(OpCode.Greater) - of GE: + of TokenType.GE: self.emitBytes(OpCode.Less, OpCode.Not) - of LT: + of TokenType.LT: self.emitByte(OpCode.Less) - of LE: + of TokenType.LE: self.emitBytes(OpCode.Greater, OpCode.Not) - of CARET: + of TokenType.CARET: self.emitByte(OpCode.Xor) - of SHL: + of TokenType.SHL: self.emitByte(OpCode.Shl) - of SHR: + of TokenType.SHR: self.emitByte(OpCode.Shr) - of BOR: + of TokenType.BOR: self.emitByte(OpCode.Bor) - of BAND: + of TokenType.BAND: self.emitByte(OpCode.Band) else: discard # Unreachable @@ -294,11 +305,12 @@ proc unary(self: ref Compiler, canAssign: bool) = 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) ## for the VM to reclaim space later on - self.objects.add(obj) - obj + let temp = obj + self.objects.add(temp) + temp 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 delimiter = &"{str[0]}" # TODO: Add proper escape sequences support 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) = @@ -328,26 +340,26 @@ proc bracket(self: ref Compiler, canAssign: bool) = ## Like in Python, using an end index that's out of bounds ## will not raise an error. Doing "hello"[0:999] will just ## return the whole string instead - if self.parser.peek.kind == COLON: + if self.parser.peek.kind == TokenType.COLON: self.emitByte(OpCode.Nil) discard self.parser.advance() - if self.parser.peek().kind == RS: + if self.parser.peek().kind == TokenType.RS: self.emitByte(OpCode.Nil) else: self.parsePrecedence(Precedence.Term) self.emitByte(OpCode.SliceRange) else: self.parsePrecedence(Precedence.Term) - if self.parser.peek().kind == RS: + if self.parser.peek().kind == TokenType.RS: self.emitByte(OpCode.Slice) - elif self.parser.peek().kind == COLON: + elif self.parser.peek().kind == TokenType.COLON: discard self.parser.advance() - if self.parser.peek().kind == RS: + if self.parser.peek().kind == TokenType.RS: self.emitByte(OpCode.Nil) else: self.parsePrecedence(Precedence.Term) self.emitByte(OpCode.SliceRange) - if self.parser.peek().kind == EQ: + if self.parser.peek().kind == TokenType.EQ: discard self.parser.advance() self.parsePrecedence(Precedence.Term) 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) = ## Parses literal values such as true, nan and inf case self.parser.previous().kind: - of TRUE: + of TokenType.TRUE: self.emitByte(OpCode.True) - of FALSE: + of TokenType.FALSE: self.emitByte(OpCode.False) of TokenType.NIL: self.emitByte(OpCode.Nil) @@ -372,21 +384,29 @@ proc literal(self: ref Compiler, canAssign: bool) = proc number(self: ref Compiler, canAssign: bool) = ## Parses numerical constants - var value = self.parser.previous().literal - self.emitConstant(value) + var value = self.parser.previous().lexeme + 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) = ## Parses parenthesized expressions. The only interesting ## semantic about parentheses is that they allow lower-precedence ## 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 ')'") elif self.parser.match(RP): self.emitByte(OpCode.Nil) else: self.expression() - self.parser.consume(RP, "Expecting ')' after parentheszed expression") + self.parser.consume(TokenType.RP, "Expecting ')' after parentheszed expression") proc synchronize(self: ref Compiler) = @@ -408,11 +428,13 @@ proc synchronize(self: ref Compiler) = ## parsing from there. Note that hadError is never reset, but ## panidMode is self.parser.panicMode = false - while self.parser.peek().kind != EOF: # Infinite loops are bad, so we must take EOF into account - if self.parser.previous().kind == SEMICOLON: + while self.parser.peek().kind != TokenType.EOF: # Infinite loops are bad, so we must take EOF into account + if self.parser.previous().kind == TokenType.SEMICOLON: return 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 else: discard @@ -421,13 +443,13 @@ proc synchronize(self: ref Compiler) = proc identifierConstant(self: ref Compiler, tok: Token): uint8 = ## 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] = ## Same as identifierConstant, but this is used when the constant table is longer ## 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) = @@ -457,22 +479,22 @@ proc declareVariable(self: ref Compiler) = proc parseVariable(self: ref Compiler, message: string): uint8 = ## Parses variables and declares them - self.parser.consume(ID, message) + self.parser.consume(TokenType.ID, message) self.declareVariable() if self.scopeDepth > 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] = ## 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(ID, message) + 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.identifierLongConstant(self.parser.previous()) proc markInitialized(self: ref Compiler) = @@ -589,7 +611,7 @@ proc varDeclaration(self: ref Compiler) = self.expression() else: 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: self.defineVariable(shortName) else: @@ -601,14 +623,14 @@ proc expressionStatement(self: ref Compiler) = ## an expression followed by a semicolon. It then ## emits a pop instruction self.expression() - self.parser.consume(SEMICOLON, "Missing semicolon after expression") + self.parser.consume(TokenType.SEMICOLON, "Missing semicolon after expression") 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! proc deleteVariable(self: ref Compiler, canAssign: bool) = 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") var code: OpCode if self.scopeDepth == 0: @@ -628,9 +650,9 @@ proc deleteVariable(self: ref Compiler, canAssign: bool) = proc parseBlock(self: ref Compiler) = ## Parses a block statement, which is basically ## 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.parser.consume(RB, "Expecting '}' after block statement") + self.parser.consume(TokenType.RB, "Expecting '}' after block statement") proc beginScope(self: ref Compiler) = @@ -685,25 +707,25 @@ proc patchJump(self: ref Compiler, offset: int) = proc ifStatement(self: ref Compiler) = ## Parses if statements in a C-style fashion - self.parser.consume(LP, "The if condition must be parenthesized") - if self.parser.peek.kind != EOF: + self.parser.consume(TokenType.LP, "The if condition must be parenthesized") + if self.parser.peek.kind != TokenType.EOF: self.expression() - if self.parser.peek.kind != EOF: - self.parser.consume(RP, "The if condition must be parenthesized") - if self.parser.peek.kind != EOF: + if self.parser.peek.kind != TokenType.EOF: + self.parser.consume(TokenType.RP, "The if condition must be parenthesized") + if self.parser.peek.kind != TokenType.EOF: var jump: int = self.emitJump(OpCode.JumpIfFalse) self.emitByte(OpCode.Pop) self.statement() var elseJump = self.emitJump(OpCode.Jump) self.patchJump(jump) self.emitByte(OpCode.Pop) - if self.parser.match(ELSE): + if self.parser.match(TokenType.ELSE): self.statement() self.patchJump(elseJump) else: - self.parser.parseError(self.parser.previous, "Invalid syntax") + self.parser.parseError(self.parser.previous(), "Invalid syntax") 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) = @@ -739,12 +761,12 @@ proc whileStatement(self: ref Compiler) = ## 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) self.loop = loop - self.parser.consume(LP, "The loop condition must be parenthesized") - if self.parser.peek.kind != EOF: + self.parser.consume(TokenType.LP, "The loop condition must be parenthesized") + if self.parser.peek.kind != TokenType.EOF: self.expression() - if self.parser.peek.kind != EOF: - self.parser.consume(RP, "The loop condition must be parenthesized") - if self.parser.peek.kind != EOF: + if self.parser.peek.kind != TokenType.EOF: + self.parser.consume(TokenType.RP, "The loop condition must be parenthesized") + if self.parser.peek.kind != TokenType.EOF: self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse) self.emitByte(OpCode.Pop) self.loop.body = self.currentChunk.code.len @@ -753,56 +775,56 @@ proc whileStatement(self: ref Compiler) = self.patchJump(self.loop.loopEnd) self.emitByte(OpCode.Pop) else: - self.parser.parseError(self.parser.previous, "Invalid syntax") + self.parser.parseError(self.parser.previous(), "Invalid syntax") 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() proc forStatement(self: ref Compiler) = ## Parses for loops in a C-style fashion self.beginScope() - self.parser.consume(LP, "The loop condition must be parenthesized") - if self.parser.peek.kind != EOF: - if self.parser.match(SEMICOLON): + self.parser.consume(TokenType.LP, "The loop condition must be parenthesized") + if self.parser.peek.kind != TokenType.EOF: + if self.parser.match(TokenType.SEMICOLON): discard - elif self.parser.match(VAR): + elif self.parser.match(TokenType.VAR): self.varDeclaration() else: self.expressionStatement() var loop = Loop(depth: self.scopeDepth, outer: self.loop, start: self.currentChunk.code.len, alive: true, loopEnd: -1) self.loop = loop - if not self.parser.match(SEMICOLON): + if not self.parser.match(TokenType.SEMICOLON): self.expression() - if self.parser.previous.kind != EOF: - self.parser.consume(SEMICOLON, "Expecting ';'") + if self.parser.previous.kind != TokenType.EOF: + self.parser.consume(TokenType.SEMICOLON, "Expecting ';'") self.loop.loopEnd = self.emitJump(OpCode.JumpIfFalse) self.emitByte(OpCode.Pop) else: 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): var bodyJump = self.emitJump(OpCode.Jump) var incrementStart = self.currentChunk.code.len - if self.parser.peek.kind != EOF: + if self.parser.peek.kind != TokenType.EOF: self.expression() 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.loop.start = incrementStart self.patchJump(bodyJump) - if self.parser.peek.kind != EOF: + if self.parser.peek.kind != TokenType.EOF: self.loop.body = self.currentChunk.code.len self.statement() self.emitLoop(self.loop.start) else: 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: self.patchJump(self.loop.loopEnd) self.emitByte(OpCode.Pop) 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.endScope() @@ -814,13 +836,14 @@ proc parseBreak(self: ref Compiler) = if not self.loop.alive: self.parser.parseError(self.parser.previous, "'break' outside loop") else: - self.parser.consume(SEMICOLON, "missing semicolon after statement") + self.parser.consume(TokenType.SEMICOLON, "missing semicolon after statement") var i = self.localCount - 1 while i >= 0 and self.locals[i].depth > self.loop.depth: self.emitByte(OpCode.Pop) i -= 1 discard self.emitJump(OpCode.Break) + proc parseAnd(self: ref Compiler, canAssign: bool) = ## Parses expressions such as a and b var jump = self.emitJump(OpCode.JumpIfFalse) @@ -846,7 +869,7 @@ proc continueStatement(self: ref Compiler) = if not self.loop.alive: self.parser.parseError(self.parser.previous, "'continue' outside loop") else: - self.parser.consume(SEMICOLON, "missing semicolon after statement") + self.parser.consume(TokenType.SEMICOLON, "missing semicolon after statement") var i = self.localCount - 1 while i >= 0 and self.locals[i].depth > self.loop.depth: self.emitByte(OpCode.Pop) @@ -887,7 +910,7 @@ proc parseFunction(self: ref Compiler, funType: FunctionType) = self.function.arity -= 1 self.function.optionals += 1 self.expression() - self.function.defaults[paramNames[len(paramNames) - 1]] = self.parser.previous.literal + # self.function.defaults.add(self.parser.previous.lexeme) # TODO defaultFollows = true elif defaultFollows: self.compileError("non-default argument follows default argument") @@ -900,10 +923,10 @@ proc parseFunction(self: ref Compiler, funType: FunctionType) = var fun = self.endCompiler() self = self.enclosing 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: self.emitByte(OpCode.ConstantLong) - self.emitBytes(self.makeLongConstant(Value(kind: OBJECT, obj: fun))) + self.emitBytes(self.makeLongConstant(fun)) proc funDeclaration(self: ref Compiler) = @@ -961,9 +984,9 @@ proc statement(self: ref Compiler) = self.whileStatement() elif self.parser.match(TokenType.RETURN): self.returnStatement() - elif self.parser.match(CONTINUE): + elif self.parser.match(TokenType.CONTINUE): self.continueStatement() - elif self.parser.match(BREAK): + elif self.parser.match(TokenType.BREAK): self.parseBreak() elif self.parser.match(LB): self.beginScope() @@ -1012,7 +1035,7 @@ var rules: array[TokenType, ParseRule] = [ makeRule(nil, nil, Precedence.None), # RS makeRule(number, nil, Precedence.None), # NUMBER 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, nil, Precedence.None), # CLASS 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), # BREAK 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, binary, Precedence.Term), # CARET makeRule(nil, binary, Precedence.Term), # SHL @@ -1069,6 +1092,9 @@ proc compile*(self: ref Compiler, source: string): ptr Function = 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 = ## Initializes a new compiler object and returns a reference ## to it @@ -1088,7 +1114,7 @@ proc initCompiler*(context: FunctionType, enclosing: ref Compiler = nil, parser: inc(result.localCount) result.function = result.markObject(newFunction()) 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 # without the VM diff --git a/meta/tokenobject.nim b/src/config.nim similarity index 58% rename from meta/tokenobject.nim rename to src/config.nim index 0c3bb50..2569761 100644 --- a/meta/tokenobject.nim +++ b/src/config.nim @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import tokentype -import ../types/japlvalue -# Token object -type - Token* = ref object - kind*: TokenType - lexeme*: string - literal*: Value - line*: int +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) + diff --git a/lexer.nim b/src/lexer.nim similarity index 81% rename from lexer.nim rename to src/lexer.nim index e31b6e4..133b524 100644 --- a/lexer.nim +++ b/src/lexer.nim @@ -17,20 +17,17 @@ ## 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 ## 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 ## other tokens, but multi-character ones require more tweaking. ## 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 strformat import tables -import meta/tokentype -import meta/tokenobject -import types/stringtype -import types/japlvalue +import meta/token + # Table of all tokens except reserved keywords const TOKENS = to_table({ @@ -126,11 +123,10 @@ proc peekNext(self: Lexer): char = 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 result = Token(kind: tokenType, lexeme: self.source[self.start.. Unterminated string literal\n") self.errored = true discard self.step() - let value = self.source[self.start.. integer is too big to convert to int64\n") - self.errored = true + self.tokens.add(self.createToken(TokenType.NUMBER)) proc parseIdentifier(self: var Lexer) = @@ -179,11 +163,10 @@ proc parseIdentifier(self: var Lexer) = while self.peek().isAlphaNumeric(): discard self.step() var text: string = self.source[self.start..' and self.match('='): - self.tokens.add(self.createToken(GE, ">=".asStr())) + self.tokens.add(self.createToken(TokenType.GE)) elif single == '>' and self.match('>'): - self.tokens.add(self.createToken(SHR, ">>".asStr())) + self.tokens.add(self.createToken(TokenType.SHR)) elif single == '<' and self.match('='): - self.tokens.add(self.createToken(LE, "<=".asStr())) + self.tokens.add(self.createToken(TokenType.LE)) elif single == '<' and self.match('<'): - self.tokens.add(self.createToken(SHL, ">>".asStr())) + self.tokens.add(self.createToken(TokenType.SHL)) elif single == '!' and self.match('='): - self.tokens.add(self.createToken(NE, "!=".asStr())) + self.tokens.add(self.createToken(TokenType.NE)) elif single == '*' and self.match('*'): - self.tokens.add(self.createToken(POW, "**".asStr())) + self.tokens.add(self.createToken(TokenType.POW)) else: - self.tokens.add(self.createToken(TOKENS[single], asStr(&"{single}"))) + self.tokens.add(self.createToken(TOKENS[single])) else: self.errored = true 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(): self.start = self.current 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 diff --git a/main.nim b/src/main.nim similarity index 100% rename from main.nim rename to src/main.nim diff --git a/memory.nim b/src/memory.nim similarity index 100% rename from memory.nim rename to src/memory.nim diff --git a/src/meta/frame.nim b/src/meta/frame.nim new file mode 100644 index 0000000..603edf0 --- /dev/null +++ b/src/meta/frame.nim @@ -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) diff --git a/meta/looptype.nim b/src/meta/looptype.nim similarity index 100% rename from meta/looptype.nim rename to src/meta/looptype.nim diff --git a/meta/opcode.nim b/src/meta/opcode.nim similarity index 95% rename from meta/opcode.nim rename to src/meta/opcode.nim index 39db873..85e5fa1 100644 --- a/meta/opcode.nim +++ b/src/meta/opcode.nim @@ -15,7 +15,7 @@ ## The module dedicated to the Chunk type ## A chunk is a piece of bytecode. -import ../types/japlvalue +import ../types/jobject type OpCode* {.pure.} = enum @@ -104,13 +104,13 @@ proc freeChunk*(self: Chunk) = 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. self.consts.add(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. ## TODO newdoc let index = self.addConstant(constant) diff --git a/meta/tokentype.nim b/src/meta/token.nim similarity index 86% rename from meta/tokentype.nim rename to src/meta/token.nim index ed2d20a..f815658 100644 --- a/meta/tokentype.nim +++ b/src/meta/token.nim @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Token types enumeration +# Token object type - TokenType* = enum + TokenType* {.pure.} = enum + # Token types enumeration PLUS, MINUS, SLASH, STAR, NEG, NE, EQ, DEQ, LT, GE, LE, MOD, POW, GT, LP, RP, LS @@ -29,3 +30,7 @@ type COLON, CONTINUE, CARET, SHL, SHR, NAN, INF, BAND, BOR, TILDE + Token* = ref object + kind*: TokenType + lexeme*: string + line*: int \ No newline at end of file diff --git a/tools/README.md b/src/tools/README.md similarity index 100% rename from tools/README.md rename to src/tools/README.md diff --git a/tools/importmap.nim b/src/tools/importmap.nim similarity index 100% rename from tools/importmap.nim rename to src/tools/importmap.nim diff --git a/types/arraylist.nim b/src/types/arraylist.nim similarity index 100% rename from types/arraylist.nim rename to src/types/arraylist.nim diff --git a/types/exceptions.nim b/src/types/exceptions.nim similarity index 97% rename from types/exceptions.nim rename to src/types/exceptions.nim index 4d2b4d9..c7ceacd 100644 --- a/types/exceptions.nim +++ b/src/types/exceptions.nim @@ -15,10 +15,9 @@ ## Defines JAPL exceptions -import stringtype -import japlvalue +import jstring +import jobject import strformat -import ../memory proc stringify*(self: ptr JAPLException): string = diff --git a/types/function.nim b/src/types/function.nim similarity index 97% rename from types/function.nim rename to src/types/function.nim index 1b4df05..89f6210 100644 --- a/types/function.nim +++ b/src/types/function.nim @@ -19,11 +19,10 @@ # code objects that can be compiled inside the JAPL runtime, pretty much # like in Python -import stringtype +import jstring +import jobject import strformat -import ../memory import ../meta/opcode -import japlvalue type diff --git a/types/hashmap.nim b/src/types/hashmap.nim similarity index 100% rename from types/hashmap.nim rename to src/types/hashmap.nim diff --git a/types/int.nim b/src/types/jfloat.nim similarity index 80% rename from types/int.nim rename to src/types/jfloat.nim index d710341..1dcb4f0 100644 --- a/types/int.nim +++ b/src/types/jfloat.nim @@ -14,3 +14,9 @@ #TODO: Implement + +proc hashFloat(f: float): uint32 = + # TODO: Any improvement? + result = 2166136261u32 + result = result xor uint32 f + result *= 16777619 diff --git a/types/float.nim b/src/types/jint.nim similarity index 97% rename from types/float.nim rename to src/types/jint.nim index d710341..d28fb6d 100644 --- a/types/float.nim +++ b/src/types/jint.nim @@ -13,4 +13,4 @@ # limitations under the License. -#TODO: Implement +import jobject diff --git a/types/japlvalue.nim b/src/types/jobject.nim similarity index 82% rename from types/japlvalue.nim rename to src/types/jobject.nim index f5cbed3..a9f230d 100644 --- a/types/japlvalue.nim +++ b/src/types/jobject.nim @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # 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 -import tables import ../memory + type Chunk* = ref object ## A piece of bytecode. @@ -36,7 +36,7 @@ type Obj* = object of RootObj # The object that rules them all kind*: ObjectType - hashobj*: uint32 + hashValue*: uint32 String* = object of Obj # A string object str*: ptr UncheckedArray[char] # TODO -> Unicode support len*: int @@ -102,6 +102,7 @@ template allocateObj*(kind: untyped, objType: ObjectType): untyped = ## to a more specific type cast[ptr kind](allocateObject(sizeof kind, objType)) + proc objType*(obj: ptr Obj): ObjectType = ## Returns the type of the object return obj.kind @@ -117,6 +118,16 @@ proc stringify*(obj: ptr Obj): string = result = "" +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 = ## This method should return the ## name of the object type @@ -127,7 +138,7 @@ proc typeName*(obj: ptr Obj): string = result = "object" -# TODO migrate to operations + proc bool*(obj: ptr Obj): bool = ## Returns wheter the object should ## be considered a falsey obj @@ -141,20 +152,21 @@ proc bool*(obj: ptr Obj): bool = result = false -# TODO migrate to operations proc eq*(a: ptr Obj, b: ptr Obj): bool = ## Compares two objects for equality - if a.kind != ObjectType.BaseObject: var newObj = convert(a) result = newObj.eq(b) else: result = a.kind == b.kind -# TODO migrate to operations + proc hash*(self: ptr Obj): uint32 = # 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 = @@ -211,95 +223,110 @@ proc binaryXor(self, other: ptr Obj): ptr Obj = result = nil -func isNil*(obj: ptr Obj): bool = +proc isNil*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL nil object result = obj.kind == ObjectType.Nil -func isBool*(obj: ptr Obj): bool = +proc isBool*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL bool result = obj.kind == ObjectType.Bool -func isInt*(obj: ptr Obj): bool = +proc isInt*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL integer result = obj.kind == ObjectType.Integer -func isFloat*(obj: ptr Obj): bool = +proc isFloat*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL float result = obj.kind == ObjectType.Float -func isInf*(obj: ptr Obj): bool = +proc isInf*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL inf object result = obj.kind == ObjectType.Infinity -func isNan*(obj: ptr Obj): bool = +proc isNan*(obj: ptr Obj): bool = ## Returns true if the given obj ## is a JAPL nan object result = obj.kind == ObjectType.Nan -func isNum*(obj: ptr Obj): bool = +proc isNum*(obj: ptr Obj): bool = ## Returns true if the given obj is ## either a JAPL number, nan or inf result = isInt(obj) or isFloat(obj) or isInf(obj) or isNan(obj) - -func isStr*(obj: ptr Obj): bool = +proc isStr*(obj: ptr Obj): bool = ## Returns true if the given object is a JAPL 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 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 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 result = cast[ptr Float](obj).floatValue # 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 var strObj = cast[ptr String](obj) for i in 0..strObj.str.len - 1: result.add(strObj.str[i]) -func asInt*(n: int): ptr Obj = +proc asInt*(n: int): ptr Integer = ## 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) - 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 - result = obj(kind: objType.Bool, boolobj: b) + result = allocateObj(Bool, ObjectType.Bool) + result.boolValue = b -func asObj*(obj: ptr Obj): obj = - ## Creates a object of ObjectType.BaseObject as type and obj (arg 1) as - ## contained obj - - result = Object(kind: objType.Object, obj: obj) +proc asNil*(): ptr Nil = + ## Creates a nil object + result = allocateObj(Nil, ObjectType.Nil) + + +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) diff --git a/types/stringtype.nim b/src/types/jstring.nim similarity index 94% rename from types/stringtype.nim rename to src/types/jstring.nim index af5fce6..3eba28d 100644 --- a/types/stringtype.nim +++ b/src/types/jstring.nim @@ -17,7 +17,7 @@ # therefore immutable from the user's perspective. They are # natively ASCII encoded, but soon they will support for unicode. -import japlvalue +import jobject import strformat import ../memory @@ -67,6 +67,6 @@ proc typeName*(s: ptr String): string = return "string" -proc asStr*(s: string): Value = +proc asStr*(s: string): ptr Obj = ## Creates a string object - result = Value(kind: ValueType.Object, obj: newString(s)) + result = newString(s) diff --git a/util/debug.nim b/src/util/debug.nim similarity index 98% rename from util/debug.nim rename to src/util/debug.nim index 004cc67..dedceef 100644 --- a/util/debug.nim +++ b/src/util/debug.nim @@ -16,8 +16,7 @@ ## screen. import ../meta/opcode -import ../types/japlvalue -import ../types/operations +import ../types/jobject import strformat diff --git a/vm.nim b/src/vm.nim similarity index 57% rename from vm.nim rename to src/vm.nim index 96b4b9d..735e61c 100644 --- a/vm.nim +++ b/src/vm.nim @@ -12,33 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. -## The JAPL runtime environment, or virtual machine. This is -## a stack-based bytecode VM. +## A stack-based bytecode virtual machine implementation. +## This is the entire runtime environment for JAPL import algorithm -import bitops -import strutils import strformat import math import lenientops -import common +import config import compiler import tables import meta/opcode +import meta/frame import types/exceptions -import types/japlvalue -import types/stringtype +import types/jobject +import types/jstring import types/function -import types/operations import memory when DEBUG_TRACE_VM: import util/debug - +## Move these into appropriate int/float modules proc `**`(a, b: int): int = pow(a.float, b.float).int - - proc `**`(a, b: float): float = pow(a, b) @@ -49,6 +45,16 @@ type OK, COMPILE_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.} = @@ -97,20 +103,20 @@ proc error*(self: var VM, error: ptr JAPLException) = self.resetStack() -proc pop*(self: var VM): Value = +proc pop*(self: var VM): ptr Obj = ## Pops a value off the stack result = self.stack.pop() self.stackTop -= 1 -proc push*(self: var VM, value: Value) = - ## Pushes a value onto the stack - self.stack.add(value) +proc push*(self: var VM, obj: ptr Obj) = + ## Pushes an object onto the stack + self.stack.add(obj) self.stackTop += 1 -proc peek*(self: var VM, distance: int): Value = - ## Peeks a value (at a given distance from the +proc peek*(self: var 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] @@ -124,6 +130,7 @@ template addObject*(self: ptr VM, obj: ptr Obj): untyped = temp +# TODO: Move this to jobject.nim proc slice(self: var VM): bool = ## Handles single-operator slice expressions ## (consider moving this to an appropriate @@ -131,32 +138,29 @@ proc slice(self: var VM): bool = var idx = self.pop() var peeked = self.pop() case peeked.kind: - of OBJECT: - case peeked.obj.kind: - of ObjectType.String: - var str = peeked.toStr() - if not idx.isInt(): - self.error(newTypeError("string indeces must be integers")) - return false - elif idx.toInt() < 0: - idx.intValue = len(str) + idx.toInt() - if idx.toInt() < 0: - self.error(newIndexError("string index out of bounds")) - return false - if idx.toInt() - 1 > len(str) - 1: + 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 - self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(&"{str[idx.toInt()]}")))) - return true - - else: - self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'")) + elif index - 1 > len(str) - 1: + self.error(newIndexError("string index out of bounds")) return false + else: + self.push(addObject(addr self, jstring.newString(&"{str[index]}"))) + return true else: self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'")) return false - +# TODO: Move this to jobject.nim proc sliceRange(self: var VM): bool = ## Handles slices when there's both a start ## and an end index (even implicit ones) @@ -164,39 +168,36 @@ proc sliceRange(self: var VM): bool = var sliceStart = self.pop() var popped = self.pop() case popped.kind: - of OBJECT: - case popped.obj.kind: - of ObjectType.String: - var str = popped.toStr() - if sliceEnd.isNil(): - sliceEnd = Value(kind: ValueType.Integer, intValue: len(str)) - if sliceStart.isNil(): - sliceStart = Value(kind: ValueType.Integer, intValue: 0) - elif not sliceStart.isInt() or not sliceEnd.isInt(): - self.error(newTypeError("string indeces must be integers")) - return false - elif sliceStart.toInt() < 0: - sliceStart.intValue = len(str) + sliceStart.toInt() - if sliceEnd.toInt() < 0: - sliceEnd.intValue = len(str) + sliceEnd.toInt() - if sliceStart.toInt() - 1 > len(str) - 1: - self.push(Value(kind: OBJECT, obj: addObject(addr self, newString("")))) - return true - 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().. len(str) - 1: + self.push(addObject(addr self, jstring.newString(""))) return true - else: - self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'")) - return false + if endIndex - 1 > len(str) - 1: + sliceEnd = len(str).asInt() + if startIndex > endIndex: + self.push(addObject(addr self, jstring.newString(""))) + return true + self.push(addObject(addr self, jstring.newString(str[sliceStart.toInt()..`, isNum) + discard of OpCode.Slice: if not self.slice(): 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 # useless output with recursive calls echo stringify(self.lastPop) - self.lastPop = Value(kind: ValueType.Nil) # TODO: asNil()? + self.lastPop = asNil() self.frameCount -= 1 discard self.frames.pop() if self.frameCount == 0: @@ -643,9 +506,8 @@ proc freeVM*(self: var VM) = proc initVM*(): VM = ## Initializes the VM setControlCHook(handleInterrupt) - var globals: Table[string, Value] = initTable[string, Value]() - result = VM(lastPop: Value(kind: ValueType.Nil), objects: @[], globals: globals, source: "", file: "") - # TODO asNil() ? + var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]() + result = VM(lastPop: asNil(), objects: @[], globals: globals, source: "", file: "") 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 if compiled == nil: return COMPILE_ERROR - self.push(Value(kind: ValueType.Object, obj: compiled)) - discard self.callValue(Value(kind: ValueType.Object, obj: compiled), 0) + self.push(compiled) + discard self.callValue(compiled, 0) when DEBUG_TRACE_VM: echo "==== VM debugger starts ====\n" try: diff --git a/test.nim b/test.nim deleted file mode 100644 index eec90a7..0000000 --- a/test.nim +++ /dev/null @@ -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 diff --git a/tests/test.nim b/tests/test.nim new file mode 100644 index 0000000..71855a5 --- /dev/null +++ b/tests/test.nim @@ -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 diff --git a/types/operations.nim b/types/operations.nim deleted file mode 100644 index 5007d19..0000000 --- a/types/operations.nim +++ /dev/null @@ -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