mirror of https://github.com/japl-lang/japl.git
Huge refactoring for the type system and object structure, VM needs opcode refactoring
This commit is contained in:
parent
823f4c622b
commit
673b88f9de
126
common.nim
126
common.nim
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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 =
|
|
@ -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
|
|
@ -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
|
|
@ -13,4 +13,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
#TODO: Implement
|
import jobject
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
6
test.nim
6
test.nim
|
@ -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
|
|
|
@ -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
|
|
@ -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
|
|
Loading…
Reference in New Issue