Move towards unboxed types in the Peon VM

This commit is contained in:
Mattia Giambirtone 2022-08-17 17:31:15 +02:00
parent fc14cfec2d
commit 19a089f4a2
21 changed files with 663 additions and 971 deletions

View File

@ -1,112 +0,0 @@
# Copyright 2022 Mattia Giambirtone & All Contributors
#
# 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 strformat
import strutils
type
ObjectKind* = enum
## Enumeration of Peon
## types
Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Function, CustomType,
Nil, Nan, Bool, Inf, Reference, Pointer
PeonObject* = object
## A generic Peon object
case kind*: ObjectKind:
of String:
str*: string
of Bool:
boolean*: bool
of Inf:
positive*: bool
of Byte:
`byte`*: byte
of Int8:
tiny*: int8
of UInt8:
uTiny*: uint8
of Int16:
short*: int16
of UInt16:
uShort*: uint16
of Int32:
`int`*: int32
of UInt32:
uInt*: uint32
of Int64:
long*: int64
of UInt64:
uLong*: uint64
of Nil, Nan:
discard
of CustomType:
fields*: seq[PeonObject]
of Float32:
halfFloat*: float32
of Float64:
`float`*: float
of Function:
ip*: uint32
of Reference, Pointer:
value*: ptr PeonObject
else:
discard # TODO
proc `$`*(self: PeonObject): string =
## Returns a string representation
## of a peon object
case self.kind:
of Int64:
result = &"{self.long}'i64"
of UInt64:
result = &"{self.uLong}'u64"
of Int32:
result = &"{self.`int`}'i32"
of UInt32:
result = &"{self.uInt}'u32"
of Int16:
result = &"{self.short}'i16"
of UInt16:
result = &"{self.uShort}'u16"
of Int8:
result = &"{self.tiny}'i8"
of UInt8:
result = &"{self.uTiny}'u8"
of Float32:
result = &"{self.halfFloat}'f32"
of Float64:
result = &"{self.`float`}'f64"
of ObjectKind.Inf:
if self.positive:
result ="inf"
else:
result ="-inf"
of ObjectKind.Nan, Nil:
result =($self.kind).toLowerAscii()
of Function:
result = &"fn(ip: {self.ip})"
of CustomType:
result = "typevar"
of String:
result = self.str
of Bool:
if self.boolean:
result = "true"
else:
result = "false"
else:
discard

View File

@ -11,46 +11,57 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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 Peon runtime environment ## The Peon runtime environment
{.push checks:off.} # The VM is a critical point where checks are deleterious
import std/monotimes import std/monotimes
import std/math
import types
import ../config import ../config
import ../frontend/meta/bytecode import ../frontend/meta/bytecode
import ../util/multibyte import ../util/multibyte
export types
import strutils
when DEBUG_TRACE_VM: when DEBUG_TRACE_VM:
import strutils import std/strformat
import strformat
type type
PeonVM* = ref object PeonVM* = ref object
## The Peon Virtual Machine ## The Peon Virtual Machine.
calls: seq[PeonObject] # Our call stack ## Note how the only data
operands: seq[PeonObject] # Our operand stack ## type we handle here is
ip: uint32 # Instruction pointer ## a 64-bit unsigned integer:
cache: array[6, PeonObject] # Singletons cache ## This is to allow the use
## of unboxed primitive types.
## For more complex types, the
## value represents a pointer to
## some stack- or heap-allocated
## object. The VM has no concept
## of type by itself and it relies
## on the compiler to produce the
## correct results
ip: uint64 # Instruction pointer
chunk: Chunk # Piece of bytecode to execute chunk: Chunk # Piece of bytecode to execute
frames: seq[int] # Stores the bottom of stack frames calls: seq[uint64] # Our call stack
closedOver: seq[PeonObject] # Stores variables that do not have stack semantics operands: seq[uint64] # Our operand stack
results: seq[PeonObject] # Stores function's results cache: array[6, uint64] # Singletons cache
frames: seq[uint64] # Stores the bottom of stack frames
closedOver: seq[uint64] # Stores variables that do not have stack semantics
results: seq[uint64] # Stores function's results (return values)
proc initCache*(self: PeonVM) = proc initCache*(self: PeonVM) =
## Initializes the VM's ## Initializes the VM's
## singletons cache ## singletons cache
self.cache[0] = PeonObject(kind: Nil) self.cache[0] = 0x0 # Nil
self.cache[1] = PeonObject(kind: Bool, boolean: true) self.cache[1] = 0x1 # True
self.cache[2] = PeonObject(kind: Bool, boolean: false) self.cache[2] = 0x2 # False
self.cache[3] = PeonObject(kind: ObjectKind.Inf, positive: true) self.cache[3] = 0x3 # Positive inf
self.cache[4] = PeonObject(kind: ObjectKind.Inf, positive: false) self.cache[4] = 0x4 # Negative inf
self.cache[5] = PeonObject(kind: ObjectKind.Nan) self.cache[5] = 0x5 # NaN
proc newPeonVM*: PeonVM = proc newPeonVM*: PeonVM =
@ -59,26 +70,26 @@ proc newPeonVM*: PeonVM =
new(result) new(result)
result.ip = 0 result.ip = 0
result.frames = @[] result.frames = @[]
result.calls = newSeq[PeonObject]() result.calls = newSeq[uint64]()
result.operands = newSeq[PeonObject]() result.operands = newSeq[uint64]()
result.initCache() result.initCache()
# Getters for singleton types (they are cached!) # Getters for singleton types (they are cached!)
proc getNil*(self: PeonVM): PeonObject {.inline.} = self.cache[0] proc getNil*(self: PeonVM): uint64 {.inline.} = self.cache[2]
proc getBool*(self: PeonVM, value: bool): PeonObject {.inline.} = proc getBool*(self: PeonVM, value: bool): uint64 {.inline.} =
if value: if value:
return self.cache[1] return self.cache[1]
return self.cache[2] return self.cache[0]
proc getInf*(self: PeonVM, positive: bool): PeonObject {.inline.} = proc getInf*(self: PeonVM, positive: bool): uint64 {.inline.} =
if positive: if positive:
return self.cache[3] return self.cache[3]
return self.cache[4] return self.cache[4]
proc getNan*(self: PeonVM): PeonObject {.inline.} = self.cache[5] proc getNan*(self: PeonVM): uint64 {.inline.} = self.cache[5]
# Thanks to nim's *genius* idea of making x !> y a template # Thanks to nim's *genius* idea of making x !> y a template
@ -96,58 +107,68 @@ proc `!>=`[T](a, b: T): auto {.inline, used.} =
b <= a b <= a
# Stack primitives. Note: all stack accessing that goes # Stack primitives. Note: all accesses to the call stack
# through the get(c)/set(c)/peek(c) wrappers is frame-relative, # that go through the getc/setc wrappers is frame-relative,
# meaning that the index is added to the current stack frame's # meaning that the index is added to the current stack frame's
# bottom to obtain an absolute stack index. # bottom to obtain an absolute stack index
{.push inline.}
proc push(self: PeonVM, obj: PeonObject) = proc push(self: PeonVM, obj: uint64) =
## Pushes a Peon object onto the ## Pushes a value object onto the
## operand stack ## operand stack
self.operands.add(obj) self.operands.add(obj)
proc pop(self: PeonVM): PeonObject = proc pop(self: PeonVM): uint64 =
## Pops a Peon object off the ## Pops a value off the
## operand stack. The object ## operand stack and
## is returned ## returns it
return self.operands.pop() return self.operands.pop()
proc peek(self: PeonVM, distance: int = 0): PeonObject = proc peekb(self: PeonVM, distance: BackwardsIndex = ^1): uint64 =
## Returns the Peon object at the ## Returns the value at the
## given (backwards) distance from the top of
## the operand stack without consuming it
return self.operands[distance]
proc peek(self: PeonVM, distance: int = 0): uint64 =
## Returns the value at the
## given distance from the top of ## given distance from the top of
## the operand stack without consuming it ## the operand stack without consuming it
if distance < 0:
return self.peekb(^(-distance))
return self.operands[self.operands.high() + distance] return self.operands[self.operands.high() + distance]
proc pushc(self: PeonVM, val: PeonObject) =
## Pushes a new object to the proc pushc(self: PeonVM, val: uint64) =
## Pushes a value to the
## call stack ## call stack
self.calls.add(val) self.calls.add(val)
proc popc(self: PeonVM): PeonObject = proc popc(self: PeonVM): uint64 =
## Pops an object off the call ## Pops a value off the call
## stack and returns it ## stack and returns it
return self.calls.pop() return self.calls.pop()
proc peekc(self: PeonVM, distance: int = 0): PeonObject {.used.} = proc peekc(self: PeonVM, distance: int = 0): uint64 {.used.} =
## Returns the Peon object at the ## Returns the value at the
## given distance from the top of ## given distance from the top of
## the call stack without consuming it ## the call stack without consuming it
return self.calls[self.calls.high() + distance] return self.calls[self.calls.high() + distance]
proc getc(self: PeonVM, idx: int): PeonObject = proc getc(self: PeonVM, idx: uint): uint64 =
## Accessor method that abstracts ## Accessor method that abstracts
## indexing our call stack through stack ## indexing our call stack through stack
## frames ## frames
return self.calls[idx + self.frames[^1]] return self.calls[idx + self.frames[^1]]
proc setc(self: PeonVM, idx: int, val: PeonObject) = proc setc(self: PeonVM, idx: uint, val: uint64) =
## Setter method that abstracts ## Setter method that abstracts
## indexing our call stack through stack ## indexing our call stack through stack
## frames ## frames
@ -156,7 +177,7 @@ proc setc(self: PeonVM, idx: int, val: PeonObject) =
# Byte-level primitives to read and decode # Byte-level primitives to read and decode
# bytecode # bytecode
proc readByte(self: PeonVM): uint8 = proc readByte(self: PeonVM): uint8 =
## Reads a single byte from the ## Reads a single byte from the
## bytecode and returns it as an ## bytecode and returns it as an
## unsigned 8 bit integer ## unsigned 8 bit integer
@ -164,7 +185,7 @@ proc readByte(self: PeonVM): uint8 =
return self.chunk.code[self.ip - 1] return self.chunk.code[self.ip - 1]
proc readShort(self: PeonVM): uint16 = proc readShort(self: PeonVM): uint16 =
## Reads two bytes from the ## Reads two bytes from the
## bytecode and returns them ## bytecode and returns them
## as an unsigned 16 bit ## as an unsigned 16 bit
@ -172,7 +193,7 @@ proc readShort(self: PeonVM): uint16 =
return [self.readByte(), self.readByte()].fromDouble() return [self.readByte(), self.readByte()].fromDouble()
proc readLong(self: PeonVM): uint32 = proc readLong(self: PeonVM): uint32 =
## Reads three bytes from the ## Reads three bytes from the
## bytecode and returns them ## bytecode and returns them
## as an unsigned 32 bit ## as an unsigned 32 bit
@ -182,7 +203,7 @@ proc readLong(self: PeonVM): uint32 =
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple()) return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple())
proc readUInt(self: PeonVM): uint32 = proc readUInt(self: PeonVM): uint32 =
## Reads three bytes from the ## Reads three bytes from the
## bytecode and returns them ## bytecode and returns them
## as an unsigned 32 bit ## as an unsigned 32 bit
@ -193,122 +214,100 @@ proc readUInt(self: PeonVM): uint32 =
# Functions to read primitives from the chunk's # Functions to read primitives from the chunk's
# constants table # constants table
proc constReadInt64(self: PeonVM, idx: int): PeonObject = proc constReadInt64(self: PeonVM, idx: int): int64 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an int64
## the constant is an Int64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3], self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5], self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7], self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
] ]
result = PeonObject(kind: Int64) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.long.addr, arr.addr, sizeof(arr))
proc constReadUInt64(self: PeonVM, idx: int): PeonObject = proc constReadUInt64(self: PeonVM, idx: int): uint64 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an uint64
## the constant is an UInt64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3], self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5], self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7], self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
] ]
result = PeonObject(kind: UInt64) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
proc constReadUInt32(self: PeonVM, idx: int): PeonObject = proc constReadUInt32(self: PeonVM, idx: int): uint32 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an int32
## the constant is an UInt32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]] self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: UInt32) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
proc constReadInt32(self: PeonVM, idx: int): PeonObject = proc constReadInt32(self: PeonVM, idx: int): int32 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an uint32
## the constant is an Int32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]] self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: Int32) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.`int`.addr, arr.addr, sizeof(arr))
proc constReadInt16(self: PeonVM, idx: int): PeonObject = proc constReadInt16(self: PeonVM, idx: int): int16 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an int16
## the constant is an Int16
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]] var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
result = PeonObject(kind: Int16) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.short.addr, arr.addr, sizeof(arr))
proc constReadUInt16(self: PeonVM, idx: int): PeonObject = proc constReadUInt16(self: PeonVM, idx: int): uint16 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an uint16
## the constant is an UInt16
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]] var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
result = PeonObject(kind: UInt16) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.uShort.addr, arr.addr, sizeof(arr))
proc constReadInt8(self: PeonVM, idx: int): PeonObject = proc constReadInt8(self: PeonVM, idx: int): int8 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an int8
## the constant is an Int8 result = int8(self.chunk.consts[idx])
result = PeonObject(kind: Int8, tiny: int8(self.chunk.consts[idx]))
proc constReadUInt8(self: PeonVM, idx: int): PeonObject = proc constReadUInt8(self: PeonVM, idx: int): uint8 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as an uint8
## the constant is an UInt8 result = self.chunk.consts[idx]
result = PeonObject(kind: UInt8, uTiny: self.chunk.consts[idx])
proc constReadString(self: PeonVM, size: int, idx: int): PeonObject =
proc constReadFloat32(self: PeonVM, idx: int): float32 =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as a float32
## the constant is a string
result = PeonObject(kind: String, str: self.chunk.consts[idx..<idx + size].fromBytes())
proc constReadFloat32(self: PeonVM, idx: int): PeonObject =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is a Float32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]] self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: Float32) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.halfFloat.addr, arr.addr, sizeof(arr))
proc constReadFloat64(self: PeonVM, idx: int): PeonObject = proc constReadFloat64(self: PeonVM, idx: int): float =
## Reads a constant from the ## Reads a constant from the
## chunk's constant table and ## chunk's constant table and
## returns a Peon object. Assumes ## returns it as a float
## the constant is a Float64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1], var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3], self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5], self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7]] self.chunk.consts[idx + 6], self.chunk.consts[idx + 7]]
result = PeonObject(kind: Float64) copyMem(result.addr, arr.addr, sizeof(arr))
copyMem(result.`float`.addr, arr.addr, sizeof(arr))
{.pop.}
proc dispatch*(self: PeonVM) = proc dispatch*(self: PeonVM) =
@ -345,62 +344,68 @@ proc dispatch*(self: PeonVM) =
of LoadInf: of LoadInf:
self.push(self.getInf(true)) self.push(self.getInf(true))
of LoadInt64: of LoadInt64:
self.push(self.constReadInt64(int(self.readLong()))) self.push(uint64(self.constReadInt64(int(self.readLong()))))
of LoadUInt64: of LoadUInt64:
self.push(self.constReadUInt64(int(self.readLong()))) self.push(uint64(self.constReadUInt64(int(self.readLong()))))
of LoadUInt32: of LoadUInt32:
self.push(self.constReadUInt32(int(self.readLong()))) self.push(uint64(self.constReadUInt32(int(self.readLong()))))
of LoadInt32: of LoadInt32:
self.push(self.constReadInt32(int(self.readLong()))) self.push(uint64(self.constReadInt32(int(self.readLong()))))
of LoadInt16: of LoadInt16:
self.push(self.constReadInt16(int(self.readLong()))) self.push(uint64(self.constReadInt16(int(self.readLong()))))
of LoadUInt16: of LoadUInt16:
self.push(self.constReadUInt16(int(self.readLong()))) self.push(uint64(self.constReadUInt16(int(self.readLong()))))
of LoadInt8: of LoadInt8:
self.push(self.constReadInt8(int(self.readLong()))) self.push(uint64(self.constReadInt8(int(self.readLong()))))
of LoadUInt8: of LoadUInt8:
self.push(self.constReadUInt8(int(self.readLong()))) self.push(uint64(self.constReadUInt8(int(self.readLong()))))
of LoadString: of LoadString:
self.push(self.constReadString(int(self.readLong()), int(self.readLong()))) # TODO: Use constReadString with own memory manager
# Strings are broken rn!!
let size = int(self.readLong())
let idx = int(self.readLong())
var str = self.chunk.consts[idx..<idx + size].fromBytes()
self.push(cast[uint64](str.addr))
# We cast instead of converting because, unlike with integers,
# we don't want nim to touch any of the bits of the underlying
# value!
of LoadFloat32: of LoadFloat32:
self.push(self.constReadFloat32(int(self.readLong()))) self.push(cast[uint64](self.constReadFloat32(int(self.readLong()))))
of LoadFloat64: of LoadFloat64:
self.push(self.constReadFloat64(int(self.readLong()))) self.push(cast[uint64](self.constReadFloat64(int(self.readLong()))))
of LoadFunction: of LoadFunction:
# Loads a function onto the operand stack by reading its # Loads a function address onto the operand stack
# instruction pointer self.push(uint64(self.readLong()))
self.push(PeonObject(kind: Function, ip: self.readLong()))
of LoadReturnAddress: of LoadReturnAddress:
# Loads a 32-bit unsigned integer onto the operand stack. # Loads a 32-bit unsigned integer onto the operand stack.
# Used to load function return addresses # Used to load function return addresses
self.push(PeonObject(kind: UInt32, uInt: self.readUInt())) self.push(uint64(self.readUInt()))
of Call: of Call:
# Calls a function. The calling convention for peon # Calls a peon function. The calling convention here
# functions is pretty simple: the first item in the # is pretty simple: the first value in the frame is
# frame is a function object which contains the new # the new instruction pointer to jump to, then a
# instruction pointer to jump to, followed by a 32-bit # 32-bit return address follows. After that, all
# return address. After that, all arguments and locals # arguments and locals follow. Note that, due to
# follow. Note that, due to how the stack works, all # how the stack works, all arguments before the call
# arguments before the call are in the reverse order in # are in the reverse order in which they are passed
# which they are passed to the function # to the function
var argc {.used.} = self.readLong().int var argc {.used.} = self.readLong().int
let retAddr = self.peek(-argc) # Return address let retAddr = self.peek(-argc - 1) # Return address
let fnObj = self.peek(-argc - 1) # Function object let jmpAddr = self.peek(-argc - 2) # Function address
self.ip = fnObj.ip self.ip = jmpAddr
self.pushc(fnObj) self.pushc(jmpAddr)
self.pushc(retAddr) self.pushc(retAddr)
# Creates a new result slot for the # Creates a new result slot for the
# function's return value # function's return value
self.results.add(self.getNil()) self.results.add(self.getNil())
# Creates a new call frame # Creates a new call frame
self.frames.add(self.calls.len() - 2) self.frames.add(uint64(self.calls.len() - 2))
# Loads the arguments onto the stack # Loads the arguments onto the stack
for _ in 0..<argc: for _ in 0..<argc:
self.pushc(self.pop()) self.pushc(self.pop())
# Pops the function object and # Pops the function and return address
# return address off the operand # off the operand stack since they're
# stack since they're not needed # not needed there anymore
# there anymore
discard self.pop() discard self.pop()
discard self.pop() discard self.pop()
# TODO: Use the frame's initial size once # TODO: Use the frame's initial size once
@ -416,12 +421,12 @@ proc dispatch*(self: PeonVM) =
# in a hidden function, so this # in a hidden function, so this
# will also exit the VM if we're # will also exit the VM if we're
# at the end of the program # at the end of the program
while self.calls.len() !> self.frames[^1] + 2: while self.calls.len().uint64 !> self.frames[^1] + 2'u64:
# Discards the function's local variables, # Discards the function's local variables,
# if there is any # if there is any
discard self.popc() discard self.popc()
let ret = self.popc() # Return address let ret = self.popc() # Return address
discard self.popc() # Function object discard self.popc() # Function address
if self.readByte() == 1: if self.readByte() == 1:
# Function is non-void! # Function is non-void!
self.push(self.results.pop()) self.push(self.results.pop())
@ -444,8 +449,10 @@ proc dispatch*(self: PeonVM) =
of StoreVar: of StoreVar:
# Stores the value at the top of the operand stack # Stores the value at the top of the operand stack
# into the given call stack index # into the given call stack index
let idx = int(self.readLong()) let idx = self.readLong()
if idx + self.frames[^1] <= self.calls.high(): when DEBUG_TRACE_VM:
assert idx.int - self.calls.high() <= 1, "StoreVar index is bigger than the length of the call stack"
if idx + self.frames[^1] <= self.calls.high().uint:
self.setc(idx, self.pop()) self.setc(idx, self.pop())
else: else:
self.pushc(self.pop()) self.pushc(self.pop())
@ -466,7 +473,7 @@ proc dispatch*(self: PeonVM) =
of LoadVar: of LoadVar:
# Pushes a variable onto the operand # Pushes a variable onto the operand
# stack # stack
self.push(self.getc(int(self.readLong()))) self.push(self.getc(self.readLong()))
of NoOp: of NoOp:
# Does nothing # Does nothing
continue continue
@ -488,257 +495,164 @@ proc dispatch*(self: PeonVM) =
discard self.pop() discard self.pop()
continue continue
echo self.pop() echo self.pop()
of GenericPrint:
# Prints the peon object at the top
# of the operand stack
echo self.pop()
of PopN: of PopN:
# Pops N elements off the call stack # Pops N elements off the call stack
for _ in 0..<int(self.readShort()): for _ in 0..<int(self.readShort()):
discard self.popc() discard self.popc()
# Jump opcodes # Jump opcodes
of Jump: of Jump:
# Absolute jump
self.ip = self.readLong() self.ip = self.readLong()
of JumpForwards: of JumpForwards:
# Relative, forward-jump
self.ip += self.readLong() self.ip += self.readLong()
of JumpBackwards: of JumpBackwards:
# Relative, backward-jump
self.ip -= self.readLong() self.ip -= self.readLong()
of JumpIfFalse: of JumpIfFalse:
if not self.peek().boolean: # Conditional positive jump
if not self.peek().bool:
self.ip += self.readLong() self.ip += self.readLong()
of JumpIfTrue: of JumpIfTrue:
if self.peek().boolean: # Conditional positive jump
if self.peek().bool:
self.ip += self.readLong() self.ip += self.readLong()
of JumpIfFalsePop: of JumpIfFalsePop:
let ip = self.readLong() let ip = self.readLong()
if not self.peek().boolean: if not self.peek().bool:
self.ip += ip self.ip += ip
discard self.pop() discard self.pop()
of JumpIfFalseOrPop: of JumpIfFalseOrPop:
if not self.peek().boolean: if not self.peek().bool:
self.ip += self.readLong() self.ip += self.readLong()
else: else:
discard self.pop() discard self.pop()
# Built-in operations on primitive types # Built-in operations on primitive types.
of AddInt64: # Note: for operations where the order of
self.push(PeonObject(kind: Int64, long: self.pop().long + self.pop().long)) # the operands matters, we don't need to
of SubInt64: # swap the order of the calls to pop: this
self.push(PeonObject(kind: Int64, long: self.pop().long - self.pop().long)) # is because operators are handled like peon
of MulInt64: # functions, which means the arguments are
self.push(PeonObject(kind: Int64, long: self.pop().long * self.pop().long)) # already reversed on the stack when we
of DivInt64: # execute the instruction
self.push(PeonObject(kind: Int64, long: self.pop().long div self.pop().long)) of Negate:
of AddUInt64: self.push(uint64(-int64(self.pop())))
self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong + self.pop().uLong)) of NegateFloat64:
of SubUInt64: self.push(cast[uint64](-cast[float](self.pop())))
self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong - self.pop().uLong)) of NegateFloat32:
of MulUInt64: self.push(cast[uint64](-cast[float32](self.pop())))
self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong * self.pop().uLong)) of Add:
of DivUInt64: self.push(self.pop() + self.pop())
self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong div self.pop().uLong)) of Subtract:
of AddInt32: self.push(self.pop() - self.pop())
self.push(PeonObject(kind: Int32, `int`: self.pop().`int` + self.pop().`int`)) of Multiply:
of SubInt32: self.push(self.pop() * self.pop())
self.push(PeonObject(kind: Int32, `int`: self.pop().`int` - self.pop().`int`)) of Divide:
of MulInt32: self.push(self.pop() div self.pop())
self.push(PeonObject(kind: Int32, `int`: self.pop().`int` * self.pop().`int`)) of SignedDivide:
of DivInt32: self.push(uint64(int64(self.pop()) div int64(self.pop())))
self.push(PeonObject(kind: Int32, `int`: self.pop().`int` div self.pop().`int`))
of AddUInt32:
self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt + self.pop().uInt))
of SubUInt32:
self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt - self.pop().uInt))
of MulUInt32:
self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt * self.pop().uInt))
of DivUInt32:
self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt div self.pop().uInt))
of AddInt16:
self.push(PeonObject(kind: Int16, short: self.pop().short + self.pop().short))
of SubInt16:
self.push(PeonObject(kind: Int16, short: self.pop().short - self.pop().short))
of MulInt16:
self.push(PeonObject(kind: Int16, short: self.pop().short * self.pop().short))
of DivInt16:
self.push(PeonObject(kind: Int16, short: self.pop().short div self.pop().short))
of AddUInt16:
self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort + self.pop().uShort))
of SubUInt16:
self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort - self.pop().uShort))
of MulUInt16:
self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort * self.pop().uShort))
of DivUInt16:
self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort div self.pop().uShort))
of AddInt8:
self.push(PeonObject(kind: Int8, tiny: self.pop().tiny + self.pop().tiny))
of SubInt8:
self.push(PeonObject(kind: Int8, tiny: self.pop().tiny - self.pop().tiny))
of MulInt8:
self.push(PeonObject(kind: Int8, tiny: self.pop().tiny * self.pop().tiny))
of DivInt8:
self.push(PeonObject(kind: Int8, tiny: self.pop().tiny div self.pop().tiny))
of AddUInt8:
self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny + self.pop().uTiny))
of SubUInt8:
self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny - self.pop().uTiny))
of MulUInt8:
self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny * self.pop().uTiny))
of DivUInt8:
self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny div self.pop().uTiny))
of AddFloat64: of AddFloat64:
self.push(PeonObject(kind: Float64, `float`: self.pop().`float` + self.pop().`float`)) self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop())))
of SubFloat64: of SubtractFloat64:
self.push(PeonObject(kind: Float64, `float`: self.pop().`float` - self.pop().`float`)) self.push(cast[uint64](cast[float](self.pop()) - cast[float](self.pop())))
of MulFloat64: of MultiplyFloat64:
self.push(PeonObject(kind: Float64, `float`: self.pop().`float` * self.pop().`float`)) self.push(cast[uint64](cast[float](self.pop()) * cast[float](self.pop())))
of DivFloat64: of DivideFloat64:
self.push(PeonObject(kind: Float64, `float`: self.pop().`float` / self.pop().`float`)) self.push(cast[uint64](cast[float](self.pop()) / cast[float](self.pop())))
of AddFloat32: of AddFloat32:
self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat + self.pop().halfFloat)) self.push(cast[uint64](cast[float32](self.pop()) + cast[float32](self.pop())))
of SubFloat32: of SubtractFloat32:
self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat - self.pop().halfFloat)) self.push(cast[uint64](cast[float32](self.pop()) - cast[float32](self.pop())))
of MulFloat32: of MultiplyFloat32:
self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat * self.pop().halfFloat)) self.push(cast[uint64](cast[float32](self.pop()) * cast[float32](self.pop())))
of DivFloat32: of DivideFloat32:
self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat / self.pop().halfFloat)) self.push(cast[uint64](cast[float32](self.pop()) / cast[float32](self.pop())))
of NegInt64: of Pow:
self.push(PeonObject(kind: Int64, long: -self.pop().long)) self.push(uint64(self.pop() ^ self.pop()))
of NegInt32: of SignedPow:
self.push(PeonObject(kind: Int32, `int`: -self.pop().`int`)) self.push(uint64(int64(self.pop()) ^ int64(self.pop())))
of NegInt16: of PowFloat64:
self.push(PeonObject(kind: Int16, short: -self.pop().short)) self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of NegInt8: of PowFloat32:
self.push(PeonObject(kind: Int8, tiny: -self.pop().tiny)) self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of NegFloat64: of Mod:
self.push(PeonObject(kind: Float64, `float`: -self.pop().`float`)) self.push(uint64(self.pop() mod self.pop()))
of NegFloat32: of SignedMod:
self.push(PeonObject(kind: Float32, halfFloat: -self.pop().halfFloat)) self.push(uint64(int64(self.pop()) mod int64(self.pop())))
of LessThanInt64: of ModFloat64:
self.push(PeonObject(kind: Bool, boolean: self.pop().long < self.pop().long)) self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of GreaterThanInt64: of ModFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().long !> self.pop().long)) self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of EqualInt64: of LShift:
self.push(PeonObject(kind: Bool, boolean: self.pop().long == self.pop().long)) self.push(self.pop() shl self.pop())
of NotEqualInt64: of RShift:
self.push(PeonObject(kind: Bool, boolean: self.pop().long != self.pop().long)) self.push(self.pop() shr self.pop())
of LessThanUInt64: of Xor:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong < self.pop().uLong)) self.push(self.pop() xor self.pop())
of GreaterThanUInt64: of Not:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong !> self.pop().uLong)) self.push(not self.pop())
of EqualUInt64: of And:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong == self.pop().uLong)) self.push(self.pop() and self.pop())
of NotEqualUInt64: of Equal:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong != self.pop().uLong)) self.push(self.getBool(self.pop() == self.pop()))
of LessThanInt32: of NotEqual:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` < self.pop().`int`)) self.push(self.getBool(self.pop() != self.pop()))
of GreaterThanInt32: of GreaterThan:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` !> self.pop().`int`)) self.push(self.getBool(self.pop() !> self.pop()))
of EqualInt32: of LessThan:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` == self.pop().`int`)) self.push(self.getBool(self.pop() < self.pop()))
of NotEqualInt32: of GreaterOrEqual:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` != self.pop().`int`)) self.push(self.getBool(self.pop() !>= self.pop()))
of LessThanUInt32: of LessOrEqual:
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt < self.pop().uInt)) self.push(self.getBool(self.pop() <= self.pop()))
of GreaterThanUInt32: of PrintInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt !> self.pop().uInt)) # Prints the value at the top of the stack
of EqualUInt32: # as an int64
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt == self.pop().uInt)) echo int64(self.pop())
of NotEqualUInt32: of PrintUInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt != self.pop().uInt)) # Prints the value at the top of the stack
of LessThanInt16: echo self.pop()
self.push(PeonObject(kind: Bool, boolean: self.pop().short < self.pop().short)) of PrintInt32:
of GreaterThanInt16: echo int32(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().short !> self.pop().short)) of PrintUInt32:
of EqualInt16: echo uint32(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().short == self.pop().short)) of PrintInt16:
of NotEqualInt16: echo int16(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().short != self.pop().short)) of PrintUInt16:
of LessThanUInt16: echo uint16(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort < self.pop().uShort)) of PrintInt8:
of GreaterThanUInt16: echo int8(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort !> self.pop().uShort)) of PrintUInt8:
of EqualUInt16: echo uint8(self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort == self.pop().uShort)) of PrintFloat32:
of NotEqualUInt16: echo cast[float32](self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort != self.pop().uShort)) of PrintFloat64:
of LessThanInt8: echo cast[float](self.pop())
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny < self.pop().tiny)) of PrintHex:
of GreaterThanInt8: # Prints the value at the top of the stack
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny !> self.pop().tiny)) # as a hexadecimal integer
of EqualInt8: echo "0x" & self.pop().toHex().strip(chars={'0'})
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny == self.pop().tiny)) of PrintBool:
of NotEqualInt8: if self.pop().bool:
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny != self.pop().tiny)) echo "true"
of LessThanUInt8: else:
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny < self.pop().uTiny)) echo "false"
of GreaterThanUInt8: of PrintInf:
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny !> self.pop().uTiny)) if self.pop() == 0x3:
of EqualUInt8: echo "-inf"
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny == self.pop().uTiny)) else:
of NotEqualUInt8: echo "inf"
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny != self.pop().uTiny)) of PrintNan:
of LessThanFloat64: echo "nan"
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` < self.pop().`float`)) of PrintString:
of GreaterThanFloat64: echo cast[ptr string](self.pop())[] # TODO
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` !> self.pop().`float`))
of EqualFloat64:
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` == self.pop().`float`))
of NotEqualFLoat64:
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` != self.pop().`float`))
of LessThanFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat < self.pop().halfFloat))
of GreaterThanFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat !> self.pop().halfFloat))
of EqualFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat == self.pop().halfFloat))
of NotEqualFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat != self.pop().halfFloat))
of GreaterOrEqualInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().long !>= self.pop().long))
of LessOrEqualInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().long <= self.pop().long))
of GreaterOrEqualUInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong !>= self.pop().uLong))
of LessOrEqualUInt64:
self.push(PeonObject(kind: Bool, boolean: self.pop().uLong <= self.pop().uLong))
of GreaterOrEqualInt32:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` !>= self.pop().`int`))
of LessOrEqualInt32:
self.push(PeonObject(kind: Bool, boolean: self.pop().`int` <= self.pop().`int`))
of GreaterOrEqualUInt32:
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt !>= self.pop().uInt))
of LessOrEqualUInt32:
self.push(PeonObject(kind: Bool, boolean: self.pop().uInt <= self.pop().uInt))
of GreaterOrEqualInt16:
self.push(PeonObject(kind: Bool, boolean: self.pop().short !>= self.pop().short))
of LessOrEqualInt16:
self.push(PeonObject(kind: Bool, boolean: self.pop().short <= self.pop().short))
of GreaterOrEqualUInt16:
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort !>= self.pop().uShort))
of LessOrEqualUInt16:
self.push(PeonObject(kind: Bool, boolean: self.pop().uShort <= self.pop().uShort))
of GreaterOrEqualInt8:
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny !>= self.pop().tiny))
of LessOrEqualInt8:
self.push(PeonObject(kind: Bool, boolean: self.pop().tiny <= self.pop().tiny))
of GreaterOrEqualUInt8:
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny !>= self.pop().uTiny))
of LessOrEqualUInt8:
self.push(PeonObject(kind: Bool, boolean: self.pop().uTiny <= self.pop().uTiny))
of GreaterOrEqualFloat64:
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` !>= self.pop().`float`))
of LessOrEqualFloat64:
self.push(PeonObject(kind: Bool, boolean: self.pop().`float` <= self.pop().`float`))
of GreaterOrEqualFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat !>= self.pop().halfFloat))
of LessOrEqualFloat32:
self.push(PeonObject(kind: Bool, boolean: self.pop().halfFloat <= self.pop().halfFloat))
of SysClock64: of SysClock64:
# Pushes the value of a monotonic clock # Pushes the value of a monotonic clock
# as a 64 bit float onto the operand stack. # onto the operand stack. This can be used
# Used to track system runtime accurately, # to track system time accurately, but it
# but cannot be converted to a date. The number # cannot be converted to a date. The number
# is in seconds # is in seconds
self.push(PeonObject(kind: Float64, `float`: getMonoTime().ticks().float() / 1_000_000_000)) self.push(cast[uint64](getMonoTime().ticks().float() / 1_000_000_000))
else: else:
discard discard
@ -751,3 +665,5 @@ proc run*(self: PeonVM, chunk: Chunk) =
self.operands = @[] self.operands = @[]
self.ip = 0 self.ip = 0
self.dispatch() self.dispatch()
{.pop.}

View File

@ -21,10 +21,10 @@ when HEAP_GROW_FACTOR <= 1:
{.fatal: "Heap growth factor must be > 1".} {.fatal: "Heap growth factor must be > 1".}
const PEON_VERSION* = (major: 0, minor: 1, patch: 0) const PEON_VERSION* = (major: 0, minor: 1, patch: 0)
const PEON_RELEASE* = "alpha" const PEON_RELEASE* = "alpha"
const PEON_COMMIT_HASH* = "e90ac2f4eb7ea5b95467bae5439cd21d2e89a812" const PEON_COMMIT_HASH* = "b273cd744883458a4a6354a0cc5f4f5d0f560c31"
when len(PEON_COMMIT_HASH) != 40: when len(PEON_COMMIT_HASH) != 40:
{.fatal: "The git commit hash must be exactly 40 characters long".} {.fatal: "The git commit hash must be exactly 40 characters long".}
const PEON_BRANCH* = "master" const PEON_BRANCH* = "unboxed-types"
when len(PEON_BRANCH) > 255: when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".} {.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = debugVM # Traces VM execution const DEBUG_TRACE_VM* = debugVM # Traces VM execution

View File

@ -183,6 +183,8 @@ type
source: string source: string
# Currently imported modules # Currently imported modules
modules: HashSet[string] modules: HashSet[string]
# TODO
scopes: seq[Type]
CompileError* = ref object of PeonException CompileError* = ref object of PeonException
compiler*: Compiler compiler*: Compiler
node*: ASTNode node*: ASTNode
@ -372,6 +374,9 @@ proc patchJump(self: Compiler, offset: int) =
var jump: int = self.chunk.code.len() - offset var jump: int = self.chunk.code.len() - offset
if jump > 16777215: if jump > 16777215:
self.error("cannot jump more than 16777215 instructions") self.error("cannot jump more than 16777215 instructions")
# We subtract 4 because that's the size of our jump instruction
# which the caller of patchJump doesn't take into account (and
# that's by design)
let offsetArray = (jump - 4).toTriple() let offsetArray = (jump - 4).toTriple()
self.chunk.code[offset + 1] = offsetArray[0] self.chunk.code[offset + 1] = offsetArray[0]
self.chunk.code[offset + 2] = offsetArray[1] self.chunk.code[offset + 2] = offsetArray[1]
@ -395,13 +400,13 @@ proc resolve(self: Compiler, name: IdentExpr,
return nil return nil
proc getStackPos(self: Compiler, name: Name, depth: int = self.scopeDepth): int = proc getStackPos(self: Compiler, name: Name): int =
## Returns the predicted call stack position of a given name, relative ## Returns the predicted call stack position of a given name, relative
## to the current frame ## to the current frame
var found = false var found = false
result = 2 result = 2
for variable in self.names: for variable in self.names:
if variable.isFunDecl: if variable.isFunDecl or variable.valueType.kind in {CustomType, Generic}:
continue continue
if name == variable: if name == variable:
found = true found = true
@ -455,12 +460,13 @@ proc detectClosureVariable(self: Compiler, name: var Name, depth: int = self.sco
## unpredictably or crash ## unpredictably or crash
if name.isNil() or name.depth == 0 or name.isClosedOver: if name.isNil() or name.depth == 0 or name.isClosedOver:
return return
elif name.depth < depth: elif name.depth < depth and self.scopes[name.depth - 1] != self.scopes[depth - 1]:
# Ding! The given name is closed over: we need to # Ding! The given name is closed over in another function:
# change the dummy Jump instruction that self.declareName # we need to change the Jump instruction that self.declareName
# put in place for us into a StoreClosure. We also update # put in place for us into a StoreClosure. We also update
# the name's isClosedOver field so that self.identifier() # the name's isClosedOver field so that self.identifier()
# can emit a LoadClosure instruction instead of a LoadVar # can emit a LoadClosure instruction instead of a LoadVar
# once this name is referenced in the future
if not name.isFunctionArgument: if not name.isFunctionArgument:
# We handle closed-over function arguments later # We handle closed-over function arguments later
self.closedOver.add(name) self.closedOver.add(name)
@ -535,13 +541,13 @@ proc toIntrinsic(name: string): Type =
## otherwise ## otherwise
if name in ["int", "int64", "i64"]: if name in ["int", "int64", "i64"]:
return Type(kind: Int64) return Type(kind: Int64)
elif name in ["uint64", "u64"]: elif name in ["uint64", "u64", "uint"]:
return Type(kind: UInt64) return Type(kind: UInt64)
elif name in ["int32", "i32"]: elif name in ["int32", "i32"]:
return Type(kind: Int32) return Type(kind: Int32)
elif name in ["uint32", "u32"]: elif name in ["uint32", "u32"]:
return Type(kind: UInt32) return Type(kind: UInt32)
elif name in ["int16", "i16"]: elif name in ["int16", "i16", "short"]:
return Type(kind: Int16) return Type(kind: Int16)
elif name in ["uint16", "u16"]: elif name in ["uint16", "u16"]:
return Type(kind: UInt16) return Type(kind: UInt16)
@ -553,9 +559,9 @@ proc toIntrinsic(name: string): Type =
return Type(kind: Float64) return Type(kind: Float64)
elif name in ["f32", "float32"]: elif name in ["f32", "float32"]:
return Type(kind: Float32) return Type(kind: Float32)
elif name == "byte": elif name in ["byte", "b"]:
return Type(kind: Byte) return Type(kind: Byte)
elif name == "char": elif name in ["char", "c"]:
return Type(kind: Char) return Type(kind: Char)
elif name == "nan": elif name == "nan":
return Type(kind: Nan) return Type(kind: Nan)
@ -802,12 +808,12 @@ proc check(self: Compiler, term: Expression, kind: Type) =
let k = self.inferType(term) let k = self.inferType(term)
if k.isNil(): if k.isNil():
if term.kind == identExpr: if term.kind == identExpr:
self.error(&"reference to undeclared name '{term.token.lexeme}'") self.error(&"reference to undeclared name '{term.token.lexeme}'", term)
elif term.kind == callExpr and CallExpr(term).callee.kind == identExpr: elif term.kind == callExpr and CallExpr(term).callee.kind == identExpr:
self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'") self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'", term)
self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type") self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type", term)
elif not self.compareTypes(k, kind): elif not self.compareTypes(k, kind):
self.error(&"expecting value of type '{self.typeToStr(kind)}', got '{self.typeToStr(k)}' instead") self.error(&"expecting value of type '{self.typeToStr(kind)}', got '{self.typeToStr(k)}' instead", term)
@ -921,101 +927,65 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
self.expression(args[0]) self.expression(args[0])
elif len(args) == 1: elif len(args) == 1:
self.expression(args[0]) self.expression(args[0])
const codes: Table[string, OpCode] = {"Negate": Negate,
"NegateFloat32": NegateFloat32,
"NegateFloat64": NegateFloat64,
"Add": Add,
"Subtract": Subtract,
"Divide": Divide,
"Multiply": Multiply,
"SignedDivide": SignedDivide,
"AddFloat64": AddFloat64,
"SubtractFloat64": SubtractFloat64,
"DivideFloat64": DivideFloat64,
"MultiplyFloat64": MultiplyFloat64,
"AddFloat32": AddFloat32,
"SubtractFloat32": SubtractFloat32,
"DivideFloat32": DivideFloat32,
"MultiplyFloat32": MultiplyFloat32,
"Pow": Pow,
"SignedPow": SignedPow,
"PowFloat32": PowFloat32,
"PowFloat64": PowFloat64,
"Mod": Mod,
"SignedMod": SignedMod,
"ModFloat32": ModFloat32,
"ModFloat64": ModFloat64,
"Or": Or,
"And": And,
"Xor": Xor,
"Not": Not,
"LShift": LShift,
"RShift": RShift,
"Equal": Equal,
"NotEqual": NotEqual,
"LessThan": LessThan,
"GreaterThan": GreaterThan,
"LessOrEqual": LessOrEqual,
"GreaterOrEqual": GreaterOrEqual,
"PrintInt64": PrintInt64,
"PrintUInt64": PrintUInt64,
"PrintInt32": PrintInt32,
"PrintUInt32": PrintUInt32,
"PrintInt16": PrintInt16,
"PrintUInt16": PrintUInt16,
"PrintInt8": PrintInt8,
"PrintUInt8": PrintUInt8,
"PrintFloat64": PrintFloat64,
"PrintFloat32": PrintFloat32,
"PrintHex": PrintHex,
"PrintBool": PrintBool,
"PrintNan": PrintNan,
"PrintInf": PrintInf,
"PrintString": PrintString,
"SysClock64": SysClock64
}.to_table()
if fn.valueType.builtinOp in codes:
self.emitByte(codes[fn.valueType.builtinOp])
return
# Some builtin operations are slightly more complex
# so we handle them separately
case fn.valueType.builtinOp: case fn.valueType.builtinOp:
of "GenericPrint":
self.emitByte(GenericPrint)
of "AddInt64":
self.emitByte(AddInt64)
of "SubInt64":
self.emitByte(SubInt64)
of "DivInt64":
self.emitByte(DivInt64)
of "MulInt64":
self.emitByte(MulInt64)
of "AddInt32":
self.emitByte(AddInt32)
of "SubInt32":
self.emitByte(SubInt32)
of "DivInt32":
self.emitByte(DivInt32)
of "MulInt32":
self.emitByte(MulInt32)
of "AddInt16":
self.emitByte(AddInt16)
of "SubInt16":
self.emitByte(SubInt16)
of "DivInt16":
self.emitByte(DivInt16)
of "MulInt16":
self.emitByte(MulInt16)
of "AddInt8":
self.emitByte(AddInt8)
of "SubInt8":
self.emitByte(SubInt8)
of "DivInt8":
self.emitByte(DivInt8)
of "MulInt8":
self.emitByte(MulInt8)
of "AddUInt64":
self.emitByte(AddUInt64)
of "SubUInt64":
self.emitByte(SubUInt64)
of "DivUInt64":
self.emitByte(DivUInt64)
of "MulUInt64":
self.emitByte(MulUInt64)
of "AddUInt32":
self.emitByte(AddUInt32)
of "SubUInt32":
self.emitByte(SubUInt32)
of "DivUInt32":
self.emitByte(DivUInt32)
of "MulUInt32":
self.emitByte(MulUInt32)
of "AddUInt16":
self.emitByte(AddUInt16)
of "SubUInt16":
self.emitByte(SubUInt16)
of "DivUInt16":
self.emitByte(DivUInt16)
of "MulUInt16":
self.emitByte(MulUInt16)
of "AddUInt8":
self.emitByte(AddUInt8)
of "SubUInt8":
self.emitByte(SubUInt8)
of "DivUInt8":
self.emitByte(DivUInt8)
of "MulUInt8":
self.emitByte(MulUInt8)
of "AddFloat64":
self.emitByte(AddFloat64)
of "SubFloat64":
self.emitByte(SubFloat64)
of "DivFloat64":
self.emitByte(DivFloat64)
of "MulFloat64":
self.emitByte(MulFloat64)
of "AddFloat32":
self.emitByte(AddFloat32)
of "SubFloat32":
self.emitByte(SubFloat32)
of "DivFloat32":
self.emitByte(DivFloat32)
of "MulFloat32":
self.emitByte(MulFloat32)
of "NegInt64":
self.emitByte(NegInt64)
of "NegInt32":
self.emitByte(NegInt32)
of "NegInt16":
self.emitByte(NegInt16)
of "NegInt8":
self.emitByte(NegInt8)
of "NegFloat64":
self.emitByte(NegFloat64)
of "NegFloat32":
self.emitByte(NegFloat32)
of "LogicalOr": of "LogicalOr":
self.expression(args[0]) self.expression(args[0])
let jump = self.emitJump(JumpIfTrue) let jump = self.emitJump(JumpIfTrue)
@ -1026,130 +996,8 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
var jump = self.emitJump(JumpIfFalseOrPop) var jump = self.emitJump(JumpIfFalseOrPop)
self.expression(args[1]) self.expression(args[1])
self.patchJump(jump) self.patchJump(jump)
of "LessThanInt64":
self.emitByte(LessThanInt64)
of "GreaterThanInt64":
self.emitByte(GreaterThanInt64)
of "EqualInt64":
self.emitByte(EqualInt64)
of "NotEqualInt64":
self.emitByte(NotEqualInt64)
of "LessThanUInt64":
self.emitByte(LessThanUInt64)
of "GreaterThanUInt64":
self.emitByte(GreaterThanUInt64)
of "EqualUInt64":
self.emitByte(EqualUInt64)
of "NotEqualUInt64":
self.emitByte(NotEqualUInt64)
of "LessThanInt32":
self.emitByte(LessThanInt32)
of "GreaterThanInt32":
self.emitByte(GreaterThanInt32)
of "EqualInt32":
self.emitByte(EqualInt32)
of "NotEqualInt32":
self.emitByte(NotEqualInt32)
of "LessThanUInt32":
self.emitByte(LessThanUInt32)
of "GreaterThanUInt32":
self.emitByte(GreaterThanUInt32)
of "EqualUInt32":
self.emitByte(EqualUInt32)
of "NotEqualUInt32":
self.emitByte(NotEqualUInt32)
of "LessThanInt16":
self.emitByte(LessThanInt16)
of "GreaterThanInt16":
self.emitByte(GreaterThanInt16)
of "EqualInt16":
self.emitByte(EqualInt16)
of "NotEqualInt16":
self.emitByte(NotEqualInt16)
of "LessThanUInt16":
self.emitByte(LessThanUInt16)
of "GreaterThanUInt16":
self.emitByte(GreaterThanUInt16)
of "EqualUInt16":
self.emitByte(EqualUInt16)
of "NotEqualUInt16":
self.emitByte(NotEqualUInt16)
of "LessThanInt8":
self.emitByte(LessThanInt8)
of "GreaterThanInt8":
self.emitByte(GreaterThanInt8)
of "EqualInt8":
self.emitByte(EqualInt8)
of "NotEqualInt8":
self.emitByte(NotEqualInt8)
of "LessThanUInt8":
self.emitByte(LessThanUInt8)
of "GreaterThanUInt8":
self.emitByte(GreaterThanUInt8)
of "EqualUInt8":
self.emitByte(EqualUInt8)
of "NotEqualUInt8":
self.emitByte(NotEqualUInt8)
of "LessThanFloat64":
self.emitByte(LessThanFloat64)
of "GreaterThanFloat64":
self.emitByte(GreaterThanFloat64)
of "EqualFloat64":
self.emitByte(EqualFloat64)
of "NotEqualFloat64":
self.emitByte(NotEqualFloat64)
of "LessThanFloat32":
self.emitByte(LessThanFloat32)
of "GreaterThanFloat32":
self.emitByte(GreaterThanFloat32)
of "EqualFloat32":
self.emitByte(EqualFloat32)
of "NotEqualFloat32":
self.emitByte(NotEqualFloat32)
of "GreaterOrEqualInt64":
self.emitByte(GreaterOrEqualInt64)
of "LessOrEqualInt64":
self.emitByte(LessOrEqualInt64)
of "GreaterOrEqualUInt64":
self.emitByte(GreaterOrEqualUInt64)
of "LessOrEqualUInt64":
self.emitByte(LessOrEqualUInt64)
of "GreaterOrEqualInt32":
self.emitByte(GreaterOrEqualInt32)
of "LessOrEqualInt32":
self.emitByte(LessOrEqualInt32)
of "GreaterOrEqualUInt32":
self.emitByte(GreaterOrEqualUInt32)
of "LessOrEqualUInt32":
self.emitByte(LessOrEqualUInt32)
of "GreaterOrEqualInt16":
self.emitByte(GreaterOrEqualInt16)
of "LessOrEqualInt16":
self.emitByte(LessOrEqualInt16)
of "GreaterOrEqualUInt16":
self.emitByte(GreaterOrEqualUInt16)
of "LessOrEqualUInt16":
self.emitByte(LessOrEqualUInt16)
of "GreaterOrEqualInt8":
self.emitByte(GreaterOrEqualInt8)
of "LessOrEqualInt8":
self.emitByte(LessOrEqualInt8)
of "GreaterOrEqualUInt8":
self.emitByte(GreaterOrEqualUInt8)
of "LessOrEqualUInt8":
self.emitByte(LessOrEqualUInt8)
of "GreaterOrEqualFloat64":
self.emitByte(GreaterOrEqualFloat64)
of "LessOrEqualFloat64":
self.emitByte(LessOrEqualFloat64)
of "GreaterOrEqualFloat32":
self.emitByte(GreaterOrEqualFloat32)
of "LessOrEqualFloat32":
self.emitByte(LessOrEqualFloat32)
of "SysClock64":
self.emitByte(SysClock64)
else: else:
self.error(&"unknown built-in: '{fn.valueType.builtinOp}'") self.error(&"unknown built-in: '{fn.valueType.builtinOp}'", fn.valueType.fun)
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) = proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) =
@ -1349,11 +1197,11 @@ proc assignment(self: Compiler, node: ASTNode) =
let name = IdentExpr(node.name) let name = IdentExpr(node.name)
var r = self.resolve(name) var r = self.resolve(name)
if r.isNil(): if r.isNil():
self.error(&"assignment to undeclared name '{name.token.lexeme}'") self.error(&"assignment to undeclared name '{name.token.lexeme}'", name)
elif r.isConst: elif r.isConst:
self.error(&"cannot assign to '{name.token.lexeme}' (constant)") self.error(&"cannot assign to '{name.token.lexeme}' (constant)", name)
elif r.isLet: elif r.isLet:
self.error(&"cannot reassign '{name.token.lexeme}'") self.error(&"cannot reassign '{name.token.lexeme}'", name)
self.expression(node.value) self.expression(node.value)
self.detectClosureVariable(r) self.detectClosureVariable(r)
if not r.isClosedOver: if not r.isClosedOver:
@ -1379,6 +1227,7 @@ proc beginScope(self: Compiler) =
## Begins a new local scope by incrementing the current ## Begins a new local scope by incrementing the current
## scope's depth ## scope's depth
inc(self.scopeDepth) inc(self.scopeDepth)
self.scopes.add(self.currentFunction)
proc endScope(self: Compiler) = proc endScope(self: Compiler) =
@ -1386,6 +1235,8 @@ proc endScope(self: Compiler) =
if self.scopeDepth < 0: if self.scopeDepth < 0:
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
dec(self.scopeDepth) dec(self.scopeDepth)
if self.scopeDepth > 0:
discard self.scopes.pop()
var names: seq[Name] = @[] var names: seq[Name] = @[]
var popCount = 0 var popCount = 0
for name in self.names: for name in self.names:
@ -1450,7 +1301,7 @@ proc ifStmt(self: Compiler, node: IfStmt) =
proc emitLoop(self: Compiler, begin: int) = proc emitLoop(self: Compiler, begin: int) =
## Emits a JumpBackwards instruction with the correct ## Emits a JumpBackwards instruction with the correct
## jump offset ## jump offset
let offset = self.chunk.code.len() - begin + 4 let offset = self.chunk.code.high() - begin + 4
if offset > 16777215: if offset > 16777215:
self.error("cannot jump more than 16777215 bytecode instructions") self.error("cannot jump more than 16777215 bytecode instructions")
self.emitByte(JumpBackwards) self.emitByte(JumpBackwards)
@ -1461,8 +1312,8 @@ proc whileStmt(self: Compiler, node: WhileStmt) =
## Compiles C-style while loops and ## Compiles C-style while loops and
## desugared C-style for loops ## desugared C-style for loops
self.check(node.condition, Type(kind: Bool)) self.check(node.condition, Type(kind: Bool))
let start = self.chunk.code.high()
self.expression(node.condition) self.expression(node.condition)
let start = self.chunk.code.len()
let jump = self.emitJump(JumpIfFalsePop) let jump = self.emitJump(JumpIfFalsePop)
self.statement(node.body) self.statement(node.body)
self.patchJump(jump) self.patchJump(jump)
@ -1666,14 +1517,45 @@ proc statement(self: Compiler, node: Statement) =
## Compiles all statements ## Compiles all statements
case node.kind: case node.kind:
of exprStmt: of exprStmt:
var expression = ExprStmt(node).expression let expression = ExprStmt(node).expression
let kind = self.inferType(expression)
self.expression(expression) self.expression(expression)
if expression.kind == callExpr and self.inferType(CallExpr(expression).callee).returnType.isNil(): if kind.isNil():
# The expression has no type, so we don't have to # The expression has no type and produces no value,
# pop anything # so we don't have to pop anything
discard discard
elif self.replMode: elif self.replMode:
self.emitByte(PopRepl) case kind.kind:
of Int64:
self.emitByte(PrintInt64)
of UInt64:
self.emitByte(PrintUInt64)
of Int32:
self.emitByte(PrintInt32)
of UInt32:
self.emitByte(PrintInt32)
of Int16:
self.emitByte(PrintInt16)
of UInt16:
self.emitByte(PrintUInt16)
of Int8:
self.emitByte(PrintInt8)
of UInt8:
self.emitByte(PrintUInt8)
of Float64:
self.emitByte(PrintFloat64)
of Float32:
self.emitByte(PrintFloat32)
of Bool:
self.emitByte(PrintBool)
of Nan:
self.emitByte(PrintNan)
of Inf:
self.emitByte(PrintInf)
of String:
self.emitByte(PrintString)
else:
self.emitByte(PrintHex)
else: else:
self.emitByte(Pop) self.emitByte(Pop)
of NodeKind.ifStmt: of NodeKind.ifStmt:
@ -1734,7 +1616,7 @@ proc varDecl(self: Compiler, node: VarDecl) =
self.expression(node.value) self.expression(node.value)
self.declareName(node, mutable=node.token.kind == TokenType.Var) self.declareName(node, mutable=node.token.kind == TokenType.Var)
self.emitByte(StoreVar) self.emitByte(StoreVar)
self.emitBytes((self.getStackPos(self.names[^1]) + 1).toTriple()) self.emitBytes(self.getStackPos(self.names[^1]).toTriple())
proc typeDecl(self: Compiler, node: TypeDecl) = proc typeDecl(self: Compiler, node: TypeDecl) =
@ -2047,4 +1929,3 @@ proc compileModule(self: Compiler, filename: string) =
self.names.add(name) self.names.add(name)
self.modules.incl(path) self.modules.incl(path)
self.closedOver &= compiler.closedOver self.closedOver &= compiler.closedOver
compiler.endScope()

View File

@ -68,7 +68,7 @@ type
# or 24 bit numbers that are defined statically # or 24 bit numbers that are defined statically
# at compilation time into the bytecode # at compilation time into the bytecode
# These push a constant at position x in the # These push a constant at position x in the
# constant table onto the stack # constant table onto the stack
LoadInt64 = 0u8, LoadInt64 = 0u8,
LoadUInt64, LoadUInt64,
@ -90,114 +90,58 @@ type
LoadNan, LoadNan,
LoadInf, LoadInf,
## Operations on primitive types ## Operations on primitive types
GenericPrint, Negate,
NegInt64, # No unsigned variants (how would you negate something that has no sign?) NegateFloat64,
NegInt32, NegateFloat32,
NegInt16, Add,
NegInt8, Subtract,
NegFloat32, Multiply,
NegFloat64, Divide,
AddInt64, SignedDivide,
AddUInt64,
AddInt32,
AddUInt32
AddInt16,
AddUInt16,
AddInt8,
AddUInt8,
SubInt64,
SubUInt64,
SubInt32,
SubUInt32,
SubInt16,
SubUInt16,
SubInt8,
SubUInt8,
MulInt64,
MulUInt64,
MulInt32,
MulUInt32,
MulInt16,
MulUInt16,
MulInt8,
MulUInt8,
DivInt64,
DivUInt64,
DivInt32,
DivUInt32,
DivInt16,
DivUInt16,
DivInt8,
DivUInt8,
AddFloat64, AddFloat64,
SubFloat64, SubtractFloat64,
DivFloat64, MultiplyFloat64,
MulFloat64, DivideFloat64,
AddFloat32, AddFloat32,
SubFloat32, SubtractFloat32,
DivFloat32, MultiplyFloat32,
MulFloat32, DivideFloat32,
LessThanInt64, Pow,
GreaterThanInt64, SignedPow,
EqualInt64, Mod,
NotEqualInt64, SignedMod,
LessThanUInt64, PowFloat64,
GreaterThanUInt64, PowFloat32,
EqualUInt64, ModFloat64,
NotEqualUInt64, ModFloat32,
LessThanInt32, LShift,
GreaterThanInt32, RSHift,
EqualInt32, Xor,
NotEqualInt32, Or,
LessThanUInt32, And,
GreaterThanUInt32, Not,
EqualUInt32, Equal,
NotEqualUInt32, NotEqual,
LessThanInt16, GreaterThan,
GreaterThanInt16, LessThan,
EqualInt16, GreaterOrEqual,
NotEqualInt16, LessOrEqual,
LessThanUInt16, ## Print opcodes
GreaterThanUInt16, PrintInt64,
EqualUInt16, PrintUInt64,
NotEqualUInt16, PrintInt32,
LessThanInt8, PrintUInt32,
GreaterThanInt8, PrintInt16,
EqualInt8, PrintUint16,
NotEqualInt8, PrintInt8,
LessThanUInt8, PrintUInt8,
GreaterThanUInt8, PrintFloat64,
EqualUInt8, PrintFloat32,
NotEqualUInt8, PrintHex,
LessThanFloat64, PrintBool,
GreaterThanFloat64, PrintNan,
EqualFloat64, PrintInf,
NotEqualFloat64, PrintString,
LessThanFloat32,
GreaterThanFloat32,
EqualFloat32,
NotEqualFloat32,
GreaterOrEqualInt64,
LessOrEqualInt64,
GreaterOrEqualUInt64,
LessOrEqualUInt64,
GreaterOrEqualInt32,
LessOrEqualInt32,
GreaterOrEqualUInt32,
LessOrEqualUInt32,
GreaterOrEqualInt16,
LessOrEqualInt16,
GreaterOrEqualUInt16,
LessOrEqualUInt16,
GreaterOrEqualInt8,
LessOrEqualInt8,
GreaterOrEqualUInt8,
LessOrEqualUInt8,
GreaterOrEqualFloat64,
LessOrEqualFloat64,
GreaterOrEqualFloat32,
LessOrEqualFloat32,
SysClock64,
## Basic stack operations ## Basic stack operations
Pop, # Pops an element off the stack and discards it Pop, # Pops an element off the stack and discards it
PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode) PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode)
@ -229,10 +173,11 @@ type
## Coroutines ## Coroutines
Await, # Calls an asynchronous function Await, # Calls an asynchronous function
## Misc ## Misc
Assert, # Raises an AssertionFailed exception if x is false Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op NoOp, # Just a no-op
PopC, # Pop off the call stack onto the operand stack PopC, # Pop off the call stack onto the operand stack
PushC # Pop off the operand stack onto the call stack PushC, # Pop off the operand stack onto the call stack
SysClock64 # Pushes the output of a monotonic clock on the stack
# We group instructions by their operation/operand types for easier handling when debugging # We group instructions by their operation/operand types for easier handling when debugging
@ -241,60 +186,62 @@ type
const simpleInstructions* = {Return, LoadNil, const simpleInstructions* = {Return, LoadNil,
LoadTrue, LoadFalse, LoadTrue, LoadFalse,
LoadNan, LoadInf, LoadNan, LoadInf,
Pop, PopRepl, Raise, Pop, Raise,
BeginTry, FinishTry, Yield, BeginTry, FinishTry, Yield,
Await, NoOp, SetResult, Await, NoOp, SetResult,
PopC, PushC, PopC, PushC, SysClock64,
AddInt64, AddUInt64, AddInt32, Negate,
AddUInt32, AddInt16, AddUInt16, NegateFloat64,
AddInt8, AddUInt8, SubInt64, NegateFloat32,
SubUInt64, SubInt32, SubUInt32, Add,
SubInt16, SubUInt16, SubInt8, Subtract,
SubUInt8, MulInt64, MulUInt64, Multiply,
MulInt32, MulUInt32, MulInt16, Divide,
MulUInt16, MulInt8, MulUInt8, SignedDivide,
DivInt64, DivUInt64, DivInt32, AddFloat64,
DivUInt32, DivInt16, DivUInt16, SubtractFloat64,
DivInt8, DivUInt8, AddFloat64, MultiplyFloat64,
SubFloat64, DivFloat64, MulFloat64, DivideFloat64,
AddFloat32, SubFloat32, DivFloat32, AddFloat32,
MulFloat32, NegFloat32, NegFloat64, SubtractFloat32,
LessThanInt64, SysClock64, GenericPrint, MultiplyFloat32,
GreaterThanInt64, EqualInt64, NotEqualInt64, DivideFloat32,
LessThanUInt64, GreaterThanUInt64, EqualUInt64, Pow,
NotEqualUInt64, LessThanInt32, GreaterThanInt32, SignedPow,
EqualInt32, NotEqualInt32, LessThanUInt32, Mod,
GreaterThanUInt32, EqualUInt32, NotEqualUInt32, SignedMod,
LessThanInt16, GreaterThanInt16, EqualInt16, PowFloat64,
NotEqualInt16, LessThanUInt16, GreaterThanUInt16, PowFloat32,
EqualUInt16, NotEqualUInt16, LessThanInt8, ModFloat64,
GreaterThanInt8,EqualInt8, NotEqualInt8, ModFloat32,
LessThanUInt8, GreaterThanUInt8, EqualUInt8, LShift,
NotEqualUInt8, LessThanFloat64, GreaterThanFloat64, RSHift,
EqualFloat64,NotEqualFloat64, LessThanFloat32, Xor,
GreaterThanFloat32, EqualFloat32, NotEqualFloat32, Or,
GreaterOrEqualInt64, And,
LessOrEqualInt64, Not,
GreaterOrEqualUInt64, Equal,
LessOrEqualUInt64, NotEqual,
GreaterOrEqualInt32, GreaterThan,
LessOrEqualInt32, LessThan,
GreaterOrEqualUInt32, GreaterOrEqual,
LessOrEqualUInt32, LessOrEqual,
GreaterOrEqualInt16, PrintInt64,
LessOrEqualInt16, PrintUInt64,
GreaterOrEqualUInt16, PrintInt32,
LessOrEqualUInt16, PrintUInt32,
GreaterOrEqualInt8, PrintInt16,
LessOrEqualInt8, PrintUint16,
GreaterOrEqualUInt8, PrintInt8,
LessOrEqualUInt8, PrintUInt8,
GreaterOrEqualFloat64, PrintFloat64,
LessOrEqualFloat64, PrintFloat32,
GreaterOrEqualFloat32, PrintHex,
LessOrEqualFloat32, PrintBool,
SysClock64, NegInt16 PrintNan,
} PrintInf,
PrintString,
}
# Constant instructions are instructions that operate on the bytecode constant table # Constant instructions are instructions that operate on the bytecode constant table
const constantInstructions* = {LoadInt64, LoadUInt64, const constantInstructions* = {LoadInt64, LoadUInt64,

View File

@ -36,7 +36,7 @@ const debugRuntime {.booldefine.} = false
proc repl(vm: PeonVM = newPeonVM()) = proc repl =
styledEcho fgMagenta, "Welcome into the peon REPL!" styledEcho fgMagenta, "Welcome into the peon REPL!"
var var
keep = true keep = true
@ -47,6 +47,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
tokenizer = newLexer() tokenizer = newLexer()
parser = newParser() parser = newParser()
compiler = newCompiler(replMode=true) compiler = newCompiler(replMode=true)
vm = newPeonVM()
debugger = newDebugger() debugger = newDebugger()
serializer = newSerializer() serializer = newSerializer()
editor = getLineEditor() editor = getLineEditor()
@ -90,7 +91,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
break break
styledEcho fgGreen, "\t", $token styledEcho fgGreen, "\t", $token
echo "" echo ""
tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input) tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input, persist=true)
if tree.len() == 0: if tree.len() == 0:
continue continue
when debugParser: when debugParser:
@ -135,7 +136,9 @@ proc repl(vm: PeonVM = newPeonVM()) =
vm.run(serialized.chunk) vm.run(serialized.chunk)
except LexingError: except LexingError:
input = "" input = ""
let exc = LexingError(getCurrentException()) var exc = LexingError(getCurrentException())
if exc.lexeme == "":
exc.line -= 1
let relPos = exc.lexer.getRelPos(exc.line) let relPos = exc.lexer.getRelPos(exc.line)
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
@ -147,7 +150,9 @@ proc repl(vm: PeonVM = newPeonVM()) =
input = "" input = ""
let exc = ParseError(getCurrentException()) let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme let lexeme = exc.token.lexeme
let lineNo = exc.token.line var lineNo = exc.token.line
if exc.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.parser.getRelPos(lineNo) let relPos = exc.parser.getRelPos(lineNo)
let fn = parser.getCurrentFunction() let fn = parser.getCurrentFunction()
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
@ -162,7 +167,9 @@ proc repl(vm: PeonVM = newPeonVM()) =
except CompileError: except CompileError:
let exc = CompileError(getCurrentException()) let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line var lineNo = exc.node.token.line
if exc.node.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.compiler.getRelPos(lineNo) let relPos = exc.compiler.getRelPos(lineNo)
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = exc.compiler.getCurrentFunction() var fn = exc.compiler.getCurrentFunction()
@ -263,8 +270,9 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
styledEcho fgCyan, "\n\nExecution step: " styledEcho fgCyan, "\n\nExecution step: "
vm.run(serialized.chunk) vm.run(serialized.chunk)
except LexingError: except LexingError:
input = "" var exc = LexingError(getCurrentException())
let exc = LexingError(getCurrentException()) if exc.lexeme == "":
exc.line -= 1
let relPos = exc.lexer.getRelPos(exc.line) let relPos = exc.lexer.getRelPos(exc.line)
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
@ -273,10 +281,11 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError: except ParseError:
input = ""
let exc = ParseError(getCurrentException()) let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme let lexeme = exc.token.lexeme
let lineNo = exc.token.line var lineNo = exc.token.line
if exc.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.parser.getRelPos(lineNo) let relPos = exc.parser.getRelPos(lineNo)
let fn = parser.getCurrentFunction() let fn = parser.getCurrentFunction()
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
@ -291,7 +300,9 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
except CompileError: except CompileError:
let exc = CompileError(getCurrentException()) let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line var lineNo = exc.node.token.line
if exc.node.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.compiler.getRelPos(lineNo) let relPos = exc.compiler.getRelPos(lineNo)
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = exc.compiler.getCurrentFunction() var fn = exc.compiler.getCurrentFunction()
@ -310,8 +321,7 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}") stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
except OSError: except OSError:
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]") stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
if interactive:
repl(vm)
when isMainModule: when isMainModule:

View File

@ -1,4 +0,0 @@
import std;
import b;
print("a");

View File

@ -1,3 +0,0 @@
import std;
print("b");

View File

@ -1,3 +1,7 @@
# Tests simple calls
import std;
fn noReturn(n: int) { fn noReturn(n: int) {
var n = n; var n = n;
var `17` = 17; var `17` = 17;
@ -12,4 +16,4 @@ fn fooBar(a, b: int): int {
noReturn(1); noReturn(1);
fooBar(1, 3); print(fooBar(1, 3)); # 1

View File

@ -1,3 +1,7 @@
# Tests closures
import std;
fn makeClosure(n: int): fn: int { fn makeClosure(n: int): fn: int {
let n = n; # Workaround let n = n; # Workaround
fn inner: int { fn inner: int {
@ -8,4 +12,5 @@ fn makeClosure(n: int): fn: int {
var closure = makeClosure(1)(); var closure = makeClosure(1)();
closure; print(closure); # 1
print(makeClosure(2)()); # 2

View File

@ -1,4 +1,7 @@
# Tests that comparisons work
import std; import std;
# int64 # int64
print(3 > 2); # true print(3 > 2); # true
print(2 < 3); # true print(2 < 3); # true

View File

@ -1,3 +1,6 @@
# Tests operator dispatching
operator `+`(a: int): int { operator `+`(a: int): int {
return a; return a;
} }

View File

@ -1,4 +1,40 @@
import std; operator `<`(a, b: int): bool {
#pragma[magic: "LessThan", pure]
}
operator `+`(a, b: int): int {
#pragma[magic: "Add", pure]
}
operator `-`(a, b: int): int {
#pragma[magic: "Subtract", pure]
}
operator `-`(a, b: float): float {
#pragma[magic: "SubtractFloat64", pure]
}
fn clock: float {
#pragma[magic: "SysClock64"]
}
fn print(x: int) {
#pragma[magic: "PrintInt64"]
}
fn print(x: float) {
#pragma[magic: "PrintFloat64"]
}
fn print(x: string) {
#pragma[magic: "PrintString"]
}
fn fib(n: int): int { fn fib(n: int): int {
@ -9,8 +45,8 @@ fn fib(n: int): int {
} }
print("Computing the value of fib(25)"); print("Computing the value of fib(30)");
var x = clock(); var x = clock();
# print(fib(25)); print(fib(30));
print(clock() - x); print(clock() - x);
print("Done!"); print("Done!");

View File

@ -1,4 +1,9 @@
fn getFunction: fn (n: int): int { # Tests first class functions
import std;
fn outer: fn (n: int): int {
fn inner(n: int): int { fn inner(n: int): int {
return n; return n;
} }
@ -6,11 +11,19 @@ fn getFunction: fn (n: int): int {
} }
operator `+`(a, b: int): int {
#pragma[magic: "AddInt64"] fn getAdder(a, b: int): fn: int64 {
var x = a;
var y = b;
fn adder: int {
return x + y;
}
return adder;
} }
getFunction()(5); print(outer()(1)); # 1
var x = getFunction; var a = 1;
x()(3); var b = 2;
var adder = getAdder(a, b);
print(adder()); # 3

View File

@ -1,5 +1,6 @@
import std; # Tests var parameters
import std;
operator `+=`(a: var int, b: int) { operator `+=`(a: var int, b: int) {

View File

@ -1,3 +1,7 @@
# Tests nested calls
import std;
fn outer: int { fn outer: int {
fn inner: int { fn inner: int {
return 69420; return 69420;
@ -14,5 +18,5 @@ fn outerTwo(n: int): int {
} }
outerTwo(5); print(outerTwo(5));
outer(); print(outer());

View File

@ -1,6 +1,10 @@
operator `+`(a, b: int): int { # Tests the creation and use of custom operators
#pragma[magic: "AddInt64", pure] import std;
operator `sum`(a, b: int): int {
return a + b;
} }
2 + 2; # Works! :D print(2 sum 2); # 4

View File

@ -1,9 +0,0 @@
fn outer: fn (n: int): int {
fn inner(n: int): int {
return n;
}
return inner;
}
outer()(1);

View File

@ -1,3 +1,4 @@
# Tests local scopes
import std; import std;

View File

@ -9,37 +9,37 @@
operator `+`*(a, b: int): int { operator `+`*(a, b: int): int {
#pragma[magic: "AddInt64", pure] #pragma[magic: "SignedAdd", pure]
} }
operator `+`*(a, b: uint64): uint64 { operator `+`*(a, b: uint64): uint64 {
#pragma[magic: "AddUInt64", pure] #pragma[magic: "Add", pure]
} }
operator `+`*(a, b: int32): int32 { operator `+`*(a, b: int32): int32 {
#pragma[magic: "AddInt32", pure] #pragma[magic: "SignedAdd", pure]
} }
operator `+`*(a, b: uint32): uint32 { operator `+`*(a, b: uint32): uint32 {
#pragma[magic: "AddUInt32", pure] #pragma[magic: "Add", pure]
} }
operator `+`*(a, b: int16): int16 { operator `+`*(a, b: int16): int16 {
#pragma[magic: "AddInt16", pure] #pragma[magic: "SignedAdd", pure]
} }
operator `+`*(a, b: uint16): uint16 { operator `+`*(a, b: uint16): uint16 {
#pragma[magic: "AddUInt16", pure] #pragma[magic: "Add", pure]
} }
operator `+`*(a, b: int8): int8 { operator `+`*(a, b: int8): int8 {
#pragma[magic: "AddInt8", pure] #pragma[magic: "Add", pure]
} }
@ -109,7 +109,7 @@ operator `-`*(a, b: float32): float32 {
operator `*`*(a, b: int): int { operator `*`*(a, b: int): int {
#pragma[magic: "MulInt64", pure] #pragma[magic: "SignedMultiply", pure]
} }
@ -119,7 +119,7 @@ operator `*`*(a, b: uint64): uint64 {
operator `*`*(a, b: int32): int32 { operator `*`*(a, b: int32): int32 {
#pragma[magic: "MulInt32", pure] #pragma[magic: "SignedMultiply", pure]
} }
@ -129,7 +129,7 @@ operator `*`*(a, b: uint32): uint32 {
operator `*`*(a, b: int16): int16 { operator `*`*(a, b: int16): int16 {
#pragma[magic: "MulInt16", pure] #pragma[magic: "SignedMultiply", pure]
} }
@ -139,7 +139,7 @@ operator `*`*(a, b: uint16): uint16 {
operator `*`*(a, b: int8): int8 { operator `*`*(a, b: int8): int8 {
#pragma[magic: "MulInt8", pure] #pragma[magic: "SignedMultiply", pure]
} }
@ -208,6 +208,11 @@ operator `/`*(a, b: float32): float32 {
} }
operator `**`*(a, b: int64): int64 {
#pragma[magic: "PowInt64", pure]
}
# Comparison operators # Comparison operators
operator `>`*(a, b: int): bool { operator `>`*(a, b: int): bool {
@ -508,11 +513,22 @@ operator `<=`*(a, b: float32): bool {
#pragma[magic: "LessOrEqualFloat32", pure] #pragma[magic: "LessOrEqualFloat32", pure]
} }
# Assignment operator
# TODO operator `and`*(a, b: bool): bool {
#operator `=`[T](a: var T, b: T) { #pragma[magic: "LogicalAnd", pure]
# #pragma[magic: "GenericAssign"] }
#}
operator `or`*(a, b: bool): bool {
#pragma[magic: "LogicalOr", pure]
}
# Assignment operators
operator `=`[T: Any](a: var T, b: T) {
#pragma[magic: "GenericAssign"]
}
# Some useful builtins # Some useful builtins
@ -541,4 +557,4 @@ fn print*(x: string) {
fn print*(x: bool) { fn print*(x: bool) {
#pragma[magic: "GenericPrint"] #pragma[magic: "GenericPrint"]
} }

View File

@ -1,24 +0,0 @@
operator `+`(a, b: int): int {
#pragma[magic: "AddInt64", pure]
}
fn print(x: int) {
#pragma[magic: "GenericPrint"]
}
fn getAdder(a, b: int): fn: int64 {
var x = a;
var y = b;
fn adder: int {
return x + y;
}
return adder;
}
var a = 1;
var b = 2;
var adder = getAdder(a, b);
print(adder());