Removed broken closure implementation, changed local resolution mechanism, many bug fixes to namespaces and tests

This commit is contained in:
Mattia Giambirtone 2022-12-04 16:54:18 +01:00
parent 142e575497
commit 21738b9382
24 changed files with 430 additions and 503 deletions

View File

@ -92,8 +92,6 @@ type
operands: seq[uint64] # The operand stack
cache: array[6, uint64] # The singletons cache
frames: seq[uint64] # Stores the bottom of stack frames
closures: seq[uint64] # Stores closure environment offsets
envs: seq[uint64] # Stores closure variables
results: seq[uint64] # Stores function return values
gc: PeonGC # A reference to the VM's garbage collector
breakpoints: seq[uint64] # Breakpoints where we call our debugger
@ -231,9 +229,6 @@ proc markRoots(self: var PeonVM): seq[ptr HeapObject] =
for obj in self.operands:
if obj in self.gc.pointers:
live.incl(obj)
for obj in self.envs:
if obj in self.gc.pointers:
live.incl(obj)
# We preallocate the space on the seq
result = newSeqOfCap[ptr HeapObject](len(live))
var obj: ptr HeapObject
@ -368,7 +363,6 @@ proc newPeonVM*: PeonVM =
result.frames = @[]
result.operands = @[]
result.results = @[]
result.envs = @[]
result.calls = @[]
@ -470,30 +464,6 @@ proc setc(self: var PeonVM, idx: int, val: uint64) =
self.calls[idx.uint + self.frames[^1]] = val
proc getClosure(self: PeonVM, idx: int): uint64 =
## Getter method that abstracts
## indexing closure environments
return self.envs[idx.uint + self.closures[^1]]
proc setClosure(self: var PeonVM, idx: int, val: uint64) =
## Setter method that abstracts
## indexing closure environments
if idx == self.envs.len():
self.envs.add(val)
else:
self.envs[idx.uint + self.closures[^1]] = val
proc popClosure(self: var PeonVM, idx: int): uint64 =
## Pop method that abstracts
## popping values off closure
## environments
var idx = idx.uint + self.closures[^1]
result = self.envs[idx]
self.envs.delete(idx)
# Byte-level primitives to read and decode
# bytecode
@ -700,20 +670,6 @@ when debugVM: # So nim shuts up
if i < self.frames.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
of "cl", "closures":
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
for i, e in self.closures:
stdout.styledWrite(fgYellow, $e)
if i < self.closures.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
of "e", "env", "environments":
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
for i, e in self.envs:
stdout.styledWrite(fgYellow, $e)
if i < self.envs.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
of "r", "results":
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
for i, e in self.results:
@ -769,9 +725,6 @@ proc dispatch*(self: var PeonVM) =
of LoadString:
# Loads the string's pointer onto the stack
self.push(cast[uint64](self.constReadString(int(self.readLong()), int(self.readLong()))))
# 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:
self.push(cast[uint64](self.constReadFloat32(int(self.readLong()))))
of LoadFloat64:
@ -804,32 +757,6 @@ proc dispatch*(self: var PeonVM) =
# not needed there anymore
discard self.pop()
discard self.pop()
of CallClosure:
# Calls a peon closure. The code here is
# mostly identical to the one for Call,
# but we also create a new environment
# containing the function's closed-over variables
let argc = self.readLong().int
let offset = self.readLong().uint64
let retAddr = self.peek(-argc - 1) # Return address
let jmpAddr = self.peek(-argc - 2) # Function address
self.ip = jmpAddr
self.pushc(jmpAddr)
self.pushc(retAddr)
# Creates a new result slot for the
# function's return value
self.results.add(self.getNil())
# Creates a new call frame
self.frames.add(uint64(self.calls.len() - 2))
self.closures.add(offset - 1)
# Loads the arguments onto the stack
for _ in 0..<argc:
self.pushc(self.pop())
# Pops the function and return address
# off the operand stack since they're
# not needed there anymore
discard self.pop()
discard self.pop()
of Return:
# Returns from a function.
# Every peon program is wrapped
@ -890,25 +817,6 @@ proc dispatch*(self: var PeonVM) =
# condition in the VM's bytecode dispatch loop (which is
# not a great idea)
self.pushc(self.pop())
of LoadClosure:
# Loads a closed-over variable from the current
# environment onto the operand stack
self.push(self.getClosure(self.readLong().int))
of StoreClosure:
# Updates the value of a closed-over
# variable
let item = self.getc(self.readLong().int)
self.setClosure(self.readLong().int, item)
of LoadTos:
# Copies the top of the call stack
# (TOS: Top of Stack) onto the
# operand stack
self.push(self.peekc())
of AddClosure:
# Stores the value at the top of the
# operand stack in the topmost closure
# environment
self.envs.add(self.pop())
of LoadVar:
# Pushes a variable from the call stack
# onto the operand stack
@ -987,7 +895,7 @@ proc dispatch*(self: var PeonVM) =
# types, we don't need specialized instructions
# to operate on them
of Negate:
self.push(cast[uint64](-int64(self.pop())))
self.push(cast[uint64](-(cast[int64](self.pop()))))
of NegateFloat64:
self.push(cast[uint64](-cast[float](self.pop())))
of NegateFloat32:
@ -1001,7 +909,7 @@ proc dispatch*(self: var PeonVM) =
of Divide:
self.push(self.pop() div self.pop())
of SignedDivide:
self.push(uint64(int64(self.pop()) div int64(self.pop())))
self.push(uint64(cast[int64](self.pop()) div cast[int64](self.pop())))
of AddFloat64:
self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop())))
of SubtractFloat64:
@ -1021,7 +929,7 @@ proc dispatch*(self: var PeonVM) =
of Pow:
self.push(uint64(self.pop() ^ self.pop()))
of SignedPow:
self.push(uint64(int64(self.pop()) ^ int64(self.pop())))
self.push(uint64(cast[int64](self.pop()) ^ cast[int64](self.pop())))
of PowFloat64:
self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of PowFloat32:
@ -1029,7 +937,7 @@ proc dispatch*(self: var PeonVM) =
of Mod:
self.push(uint64(self.pop() mod self.pop()))
of SignedMod:
self.push(uint64(int64(self.pop()) mod int64(self.pop())))
self.push(uint64(cast[int64](self.pop()) mod cast[int64](self.pop())))
of ModFloat64:
self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of ModFloat32:
@ -1056,7 +964,31 @@ proc dispatch*(self: var PeonVM) =
of GreaterOrEqual:
self.push(self.getBool(self.pop() !>= self.pop()))
of LessOrEqual:
self.push(self.getBool(self.pop() <= self.pop()))
self.push(self.getBool(cast[int64](self.pop()) <= cast[int64](self.pop())))
of SignedGreaterThan:
self.push(self.getBool(cast[int64](self.pop()) !> cast[int64](self.pop())))
of SignedLessThan:
self.push(self.getBool(cast[int64](self.pop()) < cast[int64](self.pop())))
of SignedGreaterOrEqual:
self.push(self.getBool(cast[int64](self.pop()) !>= cast[int64](self.pop())))
of SignedLessOrEqual:
self.push(self.getBool(cast[int64](self.pop()) <= cast[int64](self.pop())))
of Float64GreaterThan:
self.push(self.getBool(cast[float64](self.pop()) !> cast[float64](self.pop())))
of Float64LessThan:
self.push(self.getBool(cast[float64](self.pop()) < cast[float64](self.pop())))
of Float64GreaterOrEqual:
self.push(self.getBool(cast[float64](self.pop()) !>= cast[float64](self.pop())))
of Float64LessOrEqual:
self.push(self.getBool(cast[float64](self.pop()) <= cast[float64](self.pop())))
of Float32GreaterThan:
self.push(self.getBool(cast[float32](self.pop()) !> cast[float32](self.pop())))
of Float32LessThan:
self.push(self.getBool(cast[float32](self.pop()) < cast[float32](self.pop())))
of Float32GreaterOrEqual:
self.push(self.getBool(cast[float32](self.pop()) !>= cast[float32](self.pop())))
of Float32LessOrEqual:
self.push(self.getBool(cast[float32](self.pop()) <= cast[float32](self.pop())))
# Print opcodes
of PrintInt64:
echo cast[int64](self.pop())

View File

@ -62,6 +62,7 @@ Options
-d, --disassemble Disassemble the given bytecode file instead of executing it
-m, --mode Set the compilation mode. Acceptable values are 'debug' and
'release'
-c, --compile Compile the code, but do not execute it
--warnings Turn warnings on/off (default: on). Acceptable values are
yes/on and no/off
--noWarn Disable a specific warning (for example, --noWarn unusedVariable)

View File

@ -59,10 +59,6 @@ type
returnType: Type
builtinOp: string
fun: Declaration
isClosure: bool
envLen: int
children: seq[Type]
parent: Type
retJumps: seq[int]
forwarded: bool
location: int
@ -90,7 +86,8 @@ export bytecode
type
WarningKind* {.pure.} = enum
## A warning enumeration type
UnreachableCode, UnusedName, ShadowOuterScope
UnreachableCode, UnusedName, ShadowOuterScope,
MutateOuterScope
CompileMode* {.pure.} = enum
## A compilation mode enumeration
Debug, Release
@ -134,8 +131,6 @@ type
belongsTo: Name
# Where is this node declared in its file?
line: int
# Has this name been closed over?
isClosedOver: bool
# Has this name been referenced at least once?
resolved: bool
# The AST node associated with this node. This
@ -151,6 +146,12 @@ type
isReal: bool
# Is this name a builtin?
isBuiltin: bool
# The location of this name on the stack.
# Only makes sense for names that actually
# materialize on the call stack at runtime
# (except for functions, where we use it to
# signal where the function's frame starts)
position: int
Loop = object
## A "loop object" used
@ -243,6 +244,9 @@ type
showMismatches: bool
# Are we compiling in debug mode?
mode: CompileMode
# The topmost occupied stack slot
# in the current frame (0-indexed)
stackIndex: int
PragmaKind = enum
## An enumeration of pragma types
Immediate,
@ -253,8 +257,8 @@ type
kind: PragmaKind
handler: proc (self: Compiler, pragma: Pragma, name: Name)
CompileError* = ref object of PeonException
compiler*: Compiler
node*: ASTNode
function*: Declaration
# Forward declarations
@ -268,8 +272,9 @@ proc peek(self: Compiler, distance: int = 0): ASTNode
proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Type {.discardable.}
proc varDecl(self: Compiler, node: VarDecl)
proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name
proc specialize(self: Compiler, typ: Type, args: seq[Expression]): Type {.discardable.}
proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable.}
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.}
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.}
proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discardable.}
proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable.}
proc infer(self: Compiler, node: LiteralExpr): Type
@ -319,6 +324,7 @@ proc newCompiler*(replMode: bool = false): Compiler =
result.forwarded = @[]
result.disabledWarnings = @[]
result.functions = @[]
result.stackIndex = 1
## Public getters for nicer error formatting
@ -330,8 +336,8 @@ proc getSource*(self: Compiler): string = self.source
## Utility functions
proc `$`*(self: Name): string = $self[]
#proc `$`(self: Type): string = $self[]
proc `$`*(self: Name): string = $(self[])
proc `$`(self: Type): string = $(self[])
proc hash(self: Name): Hash = self.ident.token.lexeme.hash()
@ -353,10 +359,10 @@ proc done(self: Compiler): bool {.inline.} =
result = self.current > self.ast.high()
proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [CompileError], inline.} =
proc error(self: Compiler, message: string, node: ASTNode = nil) {.inline.} =
## Raises a CompileError exception
let node = if node.isNil(): self.getCurrentNode() else: node
raise CompileError(msg: message, node: node, line: node.token.line, file: self.file, compiler: self)
raise CompileError(msg: message, node: node, line: node.token.line, file: node.file)
proc warning(self: Compiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
@ -427,6 +433,43 @@ proc emitBytes(self: Compiler, bytarr: openarray[OpCode | uint8], line: int) {.i
self.emitByte(b, line)
proc printRepl(self: Compiler, typ: Type, node: Expression) =
## Emits instruction to print
## peon types in REPL mode
case typ.kind:
of Int64:
self.emitByte(PrintInt64, node.token.line)
of UInt64:
self.emitByte(PrintUInt64, node.token.line)
of Int32:
self.emitByte(PrintInt32, node.token.line)
of UInt32:
self.emitByte(PrintInt32, node.token.line)
of Int16:
self.emitByte(PrintInt16, node.token.line)
of UInt16:
self.emitByte(PrintUInt16, node.token.line)
of Int8:
self.emitByte(PrintInt8, node.token.line)
of UInt8:
self.emitByte(PrintUInt8, node.token.line)
of Float64:
self.emitByte(PrintFloat64, node.token.line)
of Float32:
self.emitByte(PrintFloat32, node.token.line)
of Bool:
self.emitByte(PrintBool, node.token.line)
of Nan:
self.emitByte(PrintNan, node.token.line)
of Inf:
self.emitByte(PrintInf, node.token.line)
of String:
self.emitByte(PrintString, node.token.line)
else:
self.emitByte(PrintHex, node.token.line)
proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
## Adds a constant to the current chunk's constant table
## and returns its index as a 3-byte array of uint8s
@ -520,18 +563,17 @@ proc patchJump(self: Compiler, offset: int) =
## jump using emitJump
var jump: int = self.chunk.code.len() - self.jumps[offset].offset
if jump < 0:
self.error("invalid jump size (< 0), did the bytecode size change without fixJumps being called?")
self.error("jump size cannot be negative (This is an internal error and most likely a bug)")
if jump > 16777215:
# TODO: Emit consecutive jumps?
# TODO: Emit consecutive jumps using insertAt
self.error("cannot jump more than 16777215 instructions")
if jump > 0:
self.setJump(self.jumps[offset].offset, (jump - 4).toTriple())
self.jumps[offset].patched = true
else:
# TODO: Discard jump of size 0 and update
# bytecode metadata
# TODO: Discard jump of size 0
discard
proc emitJump(self: Compiler, opcode: OpCode, line: int): int =
## Emits a dummy jump offset to be patched later
@ -622,7 +664,9 @@ proc fixNames(self: Compiler, where, oldLen: int) =
proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8]): int =
## Inserts the given instruction into the
## chunk's code segment and updates internal
## metadata to reflect this change
## metadata to reflect this change. Returns
## the new location where the code was added
## plus one (useful for consecutive calls)
result = where
let oldLen = self.chunk.code.len()
self.chunk.code.insert(uint8(opcode), where)
@ -639,31 +683,6 @@ proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8]
self.fixFunctionOffsets(oldLen, where)
proc compileDecl(self: Compiler, name: Name) =
## Internal resolve() helper
# There's no reason to compile a declaration
# unless it is used at least once: this way
# not only do we save space if a name is declared
# but never used, it also makes it easier to
# implement generics and lets us emit warnings for
# unused names once they go out of scope. Yay!
if name.resolved:
return
name.resolved = true
if name.isGeneric:
# We typecheck generics at declaration time,
# so they're already compiled
return
# Now we just dispatch to one of our functions to
# compile the declaration
case name.kind:
of NameKind.Function:
self.funDecl(FunDecl(name.node), name)
else:
discard
proc resolve(self: Compiler, name: string): Name =
## Traverses all existing namespaces and returns
## the first object with the given name. Returns
@ -694,10 +713,11 @@ proc resolve(self: Compiler, name: string): Name =
# might not want to also have access to C's and D's
# names as they might clash with its own stuff)
continue
if obj.kind == Argument and obj.belongsTo != self.currentFunction:
continue
result = obj
result.resolved = true
break
if not result.isNil():
self.compileDecl(result)
proc resolve(self: Compiler, name: IdentExpr): Name =
@ -717,63 +737,6 @@ proc resolveOrError[T: IdentExpr | string](self: Compiler, name: T): Name =
self.error(&"reference to undefined name '{name}'")
proc getStackPos(self: Compiler, name: Name): int =
## Returns the predicted call stack position
## of a given name, relative to the current
## stack frame
var found = false
result = 2 # Locals start at frame offset 2
for variable in self.names:
# Only variables and arguments are actually located on
# the stack, so we skip everything else
if variable.kind notin [NameKind.Var, NameKind.Argument]:
continue
# Variable is in a scope above us, so not in frame. Skip it!
elif variable.depth < name.depth:
continue
# Arguments to builtin functions are optimized away to stack
# temporaries. There is no stack frame for builtins, so we skip
# these names too
elif variable.kind == Argument:
if variable.belongsTo.isBuiltin:
continue
elif not variable.belongsTo.resolved:
continue
# This variable isn't in declared in our module,
# but we may still have access to it
elif variable.owner != name.owner:
# Variable is private in its owner module
# or it wasn't exported to us explicitly,
# so we just move on
if variable.isPrivate or name.owner notin variable.exportedTo:
inc(result)
continue
# Note: this MUST be an if, NOT an elif!
if name.ident == variable.ident and variable.depth == name.depth and name.owner == variable.owner:
if not name.belongsTo.isNil() and not variable.belongsTo.isNil():
if name.belongsTo != variable.belongsTo and variable.belongsTo.depth > name.belongsTo.depth:
# Argument with the same name but in a different function: ignore it
dec(result)
continue
found = true
variable.resolved = true
break
inc(result)
if not found:
result = -1
proc getClosurePos(self: Compiler, name: Name): int =
## Returns the position of a name in a closure's
## environment
if not self.currentFunction.valueType.isClosure:
return -1
for i, e in self.closures:
if e == name:
return i
return -1
proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool =
## Compares type unions between each other
var
@ -958,26 +921,22 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
case node.kind:
of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'")
if len(size) notin 1..2:
self.error("invalid state: infer -> invalid size specifier (This is an internal error and most likely a bug!)")
if size.len() == 1:
return Type(kind: Int64)
let typ = size[1].toIntrinsic()
if not self.compare(typ, nil):
return typ
else:
self.error(&"invalid type specifier '{size[1]}' for int")
self.error(&"invalid type specifier '{size[1]}' for int", node)
of floatExpr:
let size = node.token.lexeme.split("'")
if len(size) notin 1..2:
self.error("invalid state: infer -> invalid size specifier (This is an internal error and most likely a bug!)")
if size.len() == 1 or size[1] == "f64":
if size.len() == 1:
return Type(kind: Float64)
let typ = size[1].toIntrinsic()
if not typ.isNil():
return typ
else:
self.error(&"invalid type specifier '{size[1]}' for float")
self.error(&"invalid type specifier '{size[1]}' for float", node)
of trueExpr:
return Type(kind: Bool)
of falseExpr:
@ -985,7 +944,7 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
of strExpr:
return Type(kind: String)
else:
discard # TODO
discard # Unreachable
proc infer(self: Compiler, node: Expression): Type =
@ -1104,12 +1063,17 @@ proc findInModule(self: Compiler, name: string, module: Name): seq[Name] =
## Looks for objects that have been already declared as
## public within the given module with the given name.
## Returns all objects that apply. If the name is an
## empty string, returns all objects within the given moule
for obj in reversed(self.names):
if name != "" and obj.ident.token.lexeme != name:
continue
if not obj.isPrivate and obj.owner == module:
result.add(obj)
## empty string, returns all objects within the given
## module, regardless of whether they are exported to
## the current one or not
if name == "":
for obj in reversed(self.names):
if not obj.isPrivate and obj.owner == module:
result.add(obj)
else:
for obj in self.findInModule("", module):
if obj.ident.token.lexeme == name and self.currentModule in obj.exportedTo:
result.add(obj)
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] =
@ -1207,12 +1171,11 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowF
msg &= " (compile with --showMismatches for more details)"
self.error(msg, node)
if impl[0].valueType.forwarded and not allowFwd:
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
result = impl[0]
for (a, b) in zip(result.valueType.args, kind.args):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
self.compileDecl(result)
proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line: int) =
@ -1260,13 +1223,25 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line
"GreaterThan": GreaterThan,
"LessOrEqual": LessOrEqual,
"GreaterOrEqual": GreaterOrEqual,
"SignedLessThan": SignedLessThan,
"SignedGreaterThan": SignedGreaterThan,
"SignedLessOrEqual": SignedLessOrEqual,
"SignedGreaterOrEqual": SignedGreaterOrEqual,
"Float32LessThan": Float32LessThan,
"Float32GreaterThan": Float32GreaterThan,
"Float32LessOrEqual": Float32LessOrEqual,
"Float32GreaterOrEqual": Float32GreaterOrEqual,
"Float64LessThan": Float64LessThan,
"Float64GreaterThan": Float64GreaterThan,
"Float64LessOrEqual": Float64LessOrEqual,
"Float64GreaterOrEqual": Float64GreaterOrEqual,
"PrintString": PrintString,
"SysClock64": SysClock64,
"LogicalNot": LogicalNot,
"NegInf": LoadNInf
}.to_table()
if fn.builtinOp == "print":
var typ = self.expression(args[0], compile=false)
let typ = self.inferOrError(args[0])
case typ.kind:
of Int64:
self.emitByte(PrintInt64, line)
@ -1297,9 +1272,20 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line
of Inf:
self.emitByte(PrintInf, line)
of Function:
self.emitByte(PrintHex, line)
self.emitByte(LoadString, line)
var loc: string = typ.location.toHex()
while loc[0] == '0' and loc.len() > 1:
loc = loc[1..^1]
var str: string
if typ.isLambda:
str = &"anonymous function at 0x{loc}"
else:
str = &"function '{FunDecl(typ.fun).name.token.lexeme}' at 0x{loc}"
self.emitBytes(str.len().toTriple(), line)
self.emitBytes(self.chunk.writeConstant(str.toBytes()), line)
self.emitByte(PrintString, line)
else:
self.error("invalid type for built-in 'print'", args[0])
self.error(&"invalid type {self.stringify(typ)} for built-in 'print'", args[0])
return
if fn.builtinOp in codes:
self.emitByte(codes[fn.builtinOp], line)
@ -1327,16 +1313,6 @@ proc beginScope(self: Compiler) =
inc(self.depth)
# Flattens our weird function tree into a linear
# list
proc flattenImpl(self: Type, to: var seq[Type]) =
to.add(self)
for child in self.children:
flattenImpl(child, to)
proc flatten(self: Type): seq[Type] = flattenImpl(self, result)
proc patchForwardDeclarations(self: Compiler) =
## Patches forward declarations and looks
@ -1386,7 +1362,7 @@ proc endScope(self: Compiler) =
# be referenced anymore, of course)
if name.kind notin [NameKind.Var, NameKind.Argument]:
continue
elif name.kind == NameKind.Argument:
elif name.kind == NameKind.Argument and not name.belongsTo.isNil():
if name.belongsTo.isBuiltin:
# Arguments to builtin functions become temporaries on the
# stack and are popped automatically
@ -1397,6 +1373,7 @@ proc endScope(self: Compiler) =
# (it may need them later)
names.delete(names.high())
continue
inc(popCount)
if not name.resolved:
case name.kind:
of NameKind.Var:
@ -1404,7 +1381,7 @@ proc endScope(self: Compiler) =
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
of NameKind.Argument:
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
if not name.belongsTo.isBuiltin and name.belongsTo.isReal:
if not name.belongsTo.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal:
# Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for
# example as a result of generic specialization) because such objects do
@ -1412,7 +1389,7 @@ proc endScope(self: Compiler) =
self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
else:
discard
inc(popCount)
dec(self.stackIndex, popCount)
if popCount > 1:
# If we're popping more than one variable,
# we emit a bunch of PopN instructions until
@ -1529,7 +1506,6 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
returnType: nil, # We check it later
args: @[],
fun: node,
children: @[],
forwarded: node.body.isNil()),
ident: node.name,
node: node,
@ -1578,6 +1554,7 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
)
)
n = self.names[^1]
declaredName = node.name.token.lexeme
if node.value.isNil():
discard # TODO: Fields
else:
@ -1603,18 +1580,20 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
if name == n:
continue
# We don't check for name clashes with functions because self.match() does that
elif name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
if name.owner != self.currentModule:
if name.isPrivate:
continue
elif self.currentModule notin name.exportedTo:
continue
elif name.depth == 0:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})")
elif name.depth == n.depth:
self.error(&"re-declaration of {declaredName} is not allowed (previously declared in {name.owner.ident.token.lexeme}:{name.ident.token.line}:{name.ident.token.relPos.start})")
elif name.depth < self.depth:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope")
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum] and name.depth == n.depth and name.owner == n.owner:
self.error(&"re-declaration of {declaredName} is not allowed (previously declared in {name.owner.ident.token.lexeme}:{name.ident.token.line}:{name.ident.token.relPos.start})")
for name in self.names:
if name == n:
break
if name.ident.token.lexeme != declaredName:
continue
if name.owner != n.owner and (name.isPrivate or n.owner notin name.exportedTo):
continue
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
if name.depth < n.depth:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
elif name.owner != n.owner:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
return n
@ -1907,7 +1886,10 @@ proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discar
returnType: Type(kind: Any),
args: @[("", self.inferOrError(node.a), default)])
let impl = self.match(node.token.lexeme, fn, node)
result = impl.valueType.returnType
result = impl.valueType
if impl.isGeneric:
result = self.specialize(result, @[node.a])
result = result.returnType
if compile:
self.generateCall(impl, @[node.a], impl.line)
@ -1926,16 +1908,15 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
## Compiles access to identifiers
var s = name
if s.isNil():
s = self.resolveOrError(node)
var t = self.findByType(s.ident.token.lexeme, Type(kind: All))
s = t[0] # Shadowing!
s = self.resolveOrError(node)
result = s.valueType
if not compile:
return
var node = s.ident
if s.isConst:
# Constants are always emitted as Load* instructions
# no matter the scope depth
self.emitConstant(node, self.infer(node))
self.emitConstant(VarDecl(s.node).value, self.infer(node))
elif s.kind == NameKind.Function:
# Functions have no runtime representation, they're just
# a location to jump to, but we pretend they aren't and
@ -1953,45 +1934,14 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
self.emitByte(LoadInf, node.token.line)
else:
discard # Unreachable
elif s.depth > 0 and not s.belongsTo.isNil() and s.belongsTo != self.currentFunction and self.currentFunction.valueType in s.belongsTo.valueType.children:
# Loads a closure variable from a closure environment
if not s.isClosedOver:
var fn = self.currentFunction.valueType
while true:
fn.isClosure = true
fn.envLen += 1
if fn.parent.isNil():
break
fn = fn.parent
s.isClosedOver = true
self.closures.add(s)
# The best place to save this name for later use into
# the closure is right when the name is declared: the
# problem with this approach is that we have no way of
# knowing which names are closed over at the time they
# are first declared. What we do, then, is this: once we
# do know that a given name is closed over, we modify the
# code segment so that it stores the name's value into the
# topmost closure environment once it is declared
var location: int
if s.kind == Argument:
location = self.insertAt(s.belongsTo.codePos, LoadVar, self.getStackPos(s).toTriple())
else:
location = self.insertAt(s.codePos, LoadTos, [])
discard self.insertAt(location, AddClosure, [])
let pos = self.getClosurePos(s)
if pos == -1:
self.error(&"cannot compute closure offset for '{s.ident.token.lexeme}'", s.ident)
self.emitByte(LoadClosure, s.ident.token.line)
self.emitBytes(pos.toTriple(), s.ident.token.line)
else:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(self.getStackPos(s).toTriple(), s.ident.token.line)
self.emitBytes(s.position.toTriple(), s.ident.token.line)
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.discardable.} =
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
## Compiles assignment expressions
case node.kind:
of assignExpr:
@ -2004,14 +1954,14 @@ proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.dis
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
self.check(node.value, r.valueType)
self.expression(node.value)
var position = r.position
if r.depth < self.depth:
self.warning(WarningKind.MutateOuterScope, &"mutation of '{r.ident.token.lexeme}' declared in outer scope ({r.owner.file}.pn:{r.ident.token.line}:{r.ident.token.relPos.start})", nil, node)
result = r.valueType
if not compile:
return
if not r.isClosedOver:
self.emitByte(StoreVar, node.token.line)
self.emitBytes(self.getStackPos(r).toTriple(), node.token.line)
else:
self.emitByte(StoreClosure, node.token.line)
self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line)
self.emitByte(StoreVar, node.token.line)
self.emitBytes(position.toTriple(), node.token.line)
of setItemExpr:
let node = SetItemExpr(node)
let name = IdentExpr(node.name)
@ -2086,13 +2036,8 @@ proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) {.
# Creates a new call frame and jumps
# to the function's first instruction
# in the code
if not fn.isClosure:
self.emitByte(Call, line)
else:
self.emitByte(CallClosure,line)
self.emitByte(Call, line)
self.emitBytes(args.len().toTriple(), line)
if fn.isClosure:
self.emitBytes(fn.envLen.toTriple(), line)
self.patchReturnAddress(pos)
@ -2119,12 +2064,15 @@ proc prepareFunction(self: Compiler, fn: Name) =
constraints = @[]
# We now declare and typecheck the function's
# arguments
let idx = self.stackIndex
self.stackIndex = 1
var default: Expression
var i = 0
var node = FunDecl(fn.node)
for argument in node.arguments:
if self.names.high() > 16777215:
self.error("cannot declare more than 16777215 variables at a time")
inc(self.stackIndex)
self.names.add(Name(depth: fn.depth + 1,
isPrivate: true,
owner: fn.owner,
@ -2137,7 +2085,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
line: argument.name.token.line,
belongsTo: fn,
kind: NameKind.Argument,
node: argument.name
node: argument.name,
position: self.stackIndex
))
if node.arguments.high() - node.defaults.high() <= node.arguments.high():
# There's a default argument!
@ -2149,6 +2098,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
# The function needs a return type too!
if not FunDecl(fn.node).returnType.isNil():
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType)
fn.position = self.stackIndex
self.stackIndex = idx
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
@ -2176,13 +2127,8 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
# Creates a new call frame and jumps
# to the function's first instruction
# in the code
if not fn.valueType.isClosure:
self.emitByte(Call, line)
else:
self.emitByte(CallClosure, line)
self.emitByte(Call, line)
self.emitBytes(args.len().toTriple(), line)
if fn.valueType.isClosure:
self.emitBytes(fn.valueType.envLen.toTriple(), line)
self.patchReturnAddress(pos)
@ -2224,7 +2170,7 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message
if kind.isNil():
if argument.kind == NodeKind.identExpr:
self.error(&"reference to undeclared name '{argument.token.lexeme}'", argument)
self.error(&"reference to undefined name '{argument.token.lexeme}'", argument)
self.error(&"positional argument {i + 1} in function call has no type", argument)
args.add(("", kind, default))
argExpr.add(argument)
@ -2232,12 +2178,12 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
kind = self.infer(argument.value)
if kind.isNil():
if argument.value.kind == NodeKind.identExpr:
self.error(&"reference to undeclared name '{argument.value.token.lexeme}'", argument.value)
self.error(&"reference to undefined name '{argument.value.token.lexeme}'", argument.value)
self.error(&"keyword argument '{argument.name.token.lexeme}' in function call has no type", argument.value)
args.add((argument.name.token.lexeme, kind, default))
argExpr.add(argument.value)
case node.callee.kind:
of identExpr:
of NodeKind.identExpr:
# Calls like hi()
let impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node)
result = impl.valueType
@ -2261,11 +2207,19 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
# one and work our way to the innermost call
for exp in all:
result = self.call(exp, compile)
#echo result
#result = result.returnType
if compile and result.kind == Function:
self.generateCall(result, argExpr, node.token.line)
result = result.returnType
of NodeKind.getItemExpr:
var node = GetItemExpr(node.callee)
let impl = self.match(node.name.token.lexeme,
self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))), node)
result = impl.valueType
if impl.isGeneric:
result = self.specialize(result, argExpr)
result = result.returnType
if compile:
self.generateCall(impl, argExpr, node.token.line)
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
else:
let typ = self.infer(node)
@ -2275,7 +2229,7 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} =
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} =
## Compiles accessing to fields of a type or
## module namespace. If the compile flag is set
## to false, no code is generated for resolving
@ -2286,12 +2240,22 @@ proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type
let name = self.resolveOrError(IdentExpr(node.obj))
case name.kind:
of NameKind.Module:
let value = self.findInModule(node.name.token.lexeme, name)
if len(value) == 0 or self.currentModule notin value[0].exportedTo:
var values = self.findInModule(node.name.token.lexeme, name)
if len(values) == 0:
self.error(&"reference to undefined name '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
elif len(values) > 1 and matching.isNil():
self.error(&"ambiguous reference for '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
if not matching.isNil():
for name in values:
if self.compare(name.valueType, matching):
result = name.valueType
return
if len(values) == 1:
result = values[0].valueType
else:
self.error(&"ambiguous reference for '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
if compile:
self.identifier(nil, value[0])
result = value[0].valueType
self.identifier(nil, values[0])
else:
self.error("invalid syntax", node.obj)
else:
@ -2300,7 +2264,7 @@ proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type
proc lambdaExpr(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} =
## Compiles lambda functions as expressions
result = Type(kind: Function, isLambda: true, fun: node)
result = Type(kind: Function, isLambda: true, fun: node, location: self.chunk.code.high())
self.beginScope()
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
for gen in node.generics:
@ -2345,10 +2309,10 @@ proc lambdaExpr(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.
# The function needs a return type too!
if not node.returnType.isNil():
result.returnType = self.inferOrError(node.returnType)
self.endScope()
if not compile:
return
# TODO
self.endScope()
proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} =
@ -2365,7 +2329,7 @@ proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.
# would be lost in the call anyway. The differentiation
# happens in self.assignment()
of NodeKind.setItemExpr, NodeKind.assignExpr:
return self.assignment(node, compile).valueType
return self.assignment(node, compile)
of NodeKind.identExpr:
return self.identifier(IdentExpr(node), compile=compile)
of NodeKind.unaryExpr:
@ -2501,42 +2465,6 @@ proc exportStmt(self: Compiler, node: ExportStmt) =
discard
proc printRepl(self: Compiler, typ: Type, node: Expression) =
## Emits instruction to print
## peon types in REPL mode
case typ.kind:
of Int64:
self.emitByte(PrintInt64, node.token.line)
of UInt64:
self.emitByte(PrintUInt64, node.token.line)
of Int32:
self.emitByte(PrintInt32, node.token.line)
of UInt32:
self.emitByte(PrintInt32, node.token.line)
of Int16:
self.emitByte(PrintInt16, node.token.line)
of UInt16:
self.emitByte(PrintUInt16, node.token.line)
of Int8:
self.emitByte(PrintInt8, node.token.line)
of UInt8:
self.emitByte(PrintUInt8, node.token.line)
of Float64:
self.emitByte(PrintFloat64, node.token.line)
of Float32:
self.emitByte(PrintFloat32, node.token.line)
of Bool:
self.emitByte(PrintBool, node.token.line)
of Nan:
self.emitByte(PrintNan, node.token.line)
of Inf:
self.emitByte(PrintInf, node.token.line)
of String:
self.emitByte(PrintString, node.token.line)
else:
self.emitByte(PrintHex, node.token.line)
proc statement(self: Compiler, node: Statement) =
## Compiles all statements
case node.kind:
@ -2619,8 +2547,9 @@ proc varDecl(self: Compiler, node: VarDecl) =
self.emitByte(AddVar, node.token.line)
self.declare(node)
var name = self.names[^1]
inc(self.stackIndex)
name.position = self.stackIndex
name.valueType = typ
proc funDecl(self: Compiler, node: FunDecl, name: Name) =
@ -2630,32 +2559,27 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
var node = node
var jmp: int
# We store the current function
var function = self.currentFunction
let function = self.currentFunction
if node.body.isNil():
# We ignore forward declarations
self.forwarded.add((name, 0))
name.valueType.forwarded = true
self.currentFunction = function
return
if not function.isNil() and self.depth > 1:
# Calling a function immediately compiles it, so if
# we didn't check for the scope depth we'd mistakenly
# mark functions as being nested when they actually
# aren't
function.valueType.children.add(name.valueType)
name.valueType.parent = function.valueType
self.currentFunction = name
if self.currentFunction.isBuiltin:
self.currentFunction = function
return
let stackIdx = self.stackIndex
self.stackIndex = name.position
# A function's code is just compiled linearly
# and then jumped over
jmp = self.emitJump(JumpForwards, node.token.line)
name.codePos = self.chunk.code.len()
name.valueType.location = name.codePos
# We let our debugger know this function's boundaries
self.chunk.functions.add(self.chunk.code.high().toTriple())
self.functions.add((start: self.chunk.code.high(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
self.chunk.functions.add(self.chunk.code.len().toTriple())
self.functions.add((start: self.chunk.code.len(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
var offset = self.functions[^1]
let idx = self.chunk.functions.len()
self.chunk.functions.add(0.toTriple()) # Patched it later
@ -2728,19 +2652,17 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
self.currentFunction = function
self.stackIndex = stackIdx
proc declaration(self: Compiler, node: Declaration) =
## Handles all declarations. They are not compiled
## right away, but rather only when they're referenced
## the first time
## Compiles declarations, statements and expressions
## recursively
case node.kind:
of NodeKind.funDecl:
var name = self.declare(node)
self.funDecl(FunDecl(node), name)
if name.isGeneric:
# We typecheck generics immediately
name.resolved = true
self.funDecl(FunDecl(node), name)
# After we're done compiling a generic
# function, we pull a magic trick: since,
# from here on, the user will be able to
@ -2757,7 +2679,7 @@ proc declaration(self: Compiler, node: Declaration) =
continue
else:
argument.kind.asUnion = true
if not name.valueType.returnType.isNil() and name.valueType.returnType.isNil():
if not name.valueType.returnType.isNil() and name.valueType.returnType.kind == Generic:
name.valueType.returnType.asUnion = true
of NodeKind.typeDecl:
self.declare(node)
@ -2790,6 +2712,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
self.disabledWarnings = disabledWarnings
self.showMismatches = showMismatches
self.mode = mode
self.stackIndex = 1
if not incremental:
self.jumps = @[]
let pos = self.beginProgram()

View File

@ -93,6 +93,7 @@ type
# This is not shown when the node is printed, but makes it a heck of a lot easier to report
# errors accurately even deep in the compilation pipeline
token*: Token
file*: string
# This weird inheritance chain is needed for the parser to
# work properly
Declaration* = ref object of ASTNode

View File

@ -122,6 +122,18 @@ type
LessThan,
GreaterOrEqual,
LessOrEqual,
SignedGreaterThan,
SignedLessThan,
SignedGreaterOrEqual,
SignedLessOrEqual,
Float64GreaterThan,
Float64LessThan,
Float64GreaterOrEqual,
Float64LessOrEqual,
Float32GreaterThan,
Float32LessThan,
Float32GreaterOrEqual,
Float32LessOrEqual,
LogicalNot,
## Print opcodes
PrintInt64,
@ -148,9 +160,6 @@ type
LoadVar, # Pushes the object at position x in the stack onto the stack
StoreVar, # Stores the value of b at position a in the stack
AddVar, # An optimization for StoreVar (used when the variable is first declared)
LoadClosure, # Pushes the object position x in the closure array onto the stack
StoreClosure, # Stores the value of b at position a in the closure array
AddClosure, # This is the same optimization of AddVar, but applied to StoreClosure instead
## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode
JumpForwards, # Relative, unconditional, positive jump in the bytecode
@ -161,7 +170,6 @@ type
JumpIfFalseOrPop, # Jumps to an absolute index in the bytecode if x is false and pops otherwise (used for logical and)
## Functions
Call, # Calls a function and initiates a new stack frame
CallClosure, # Calls a closure
Return, # Terminates the current function
SetResult, # Sets the result of the current function
## Exception handling
@ -178,7 +186,7 @@ type
PopC, # Pop off the call stack onto the operand stack
PushC, # Pop off the operand stack onto the call stack
SysClock64, # Pushes the output of a monotonic clock on the stack
LoadTos # Pushes the top of the call stack onto the operand stack
LoadTOS # Pushes the top of the call stack onto the operand stack
# We group instructions by their operation/operand types for easier handling when debugging
@ -244,8 +252,19 @@ const simpleInstructions* = {Return, LoadNil,
PrintString,
LogicalNot,
AddVar,
AddClosure,
LoadTos
LoadTOS,
SignedGreaterThan,
SignedLessThan,
SignedGreaterOrEqual,
SignedLessOrEqual,
Float64GreaterThan,
Float64LessThan,
Float64GreaterOrEqual,
Float64LessOrEqual,
Float32GreaterThan,
Float32LessThan,
Float32GreaterOrEqual,
Float32LessOrEqual,
}
# Constant instructions are instructions that operate on the bytecode constant table
@ -258,7 +277,7 @@ const constantInstructions* = {LoadInt64, LoadUInt64,
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, }
const stackTripleInstructions* = {StoreVar, LoadVar, }
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers

View File

@ -38,7 +38,7 @@ type
Yield, Defer, Try, Except,
Finally, Type, Operator, Case,
Enum, From, Ptr, Ref, Object,
Export,
Export, Block
# Literal types
Integer, Float, String, Identifier,
@ -52,12 +52,12 @@ type
LeftBracket, RightBracket, # []
Dot, Semicolon, Comma, # . ; ,
# Miscellaneous
# Miscellaneous
EndOfFile, # Marks the end of the token stream
NoMatch, # Used internally by the symbol table
Comment, # Useful for documentation comments, pragmas, etc.
Symbol, # A generic symbol
NoMatch, # Used internally by the symbol table
Comment, # Useful for documentation comments, pragmas, etc.
Symbol, # A generic symbol
Pragma,
Token* = ref object

View File

@ -315,17 +315,23 @@ proc primary(self: Parser): Expression =
case self.peek().kind:
of True:
result = newTrueExpr(self.step())
result.file = self.file
of False:
result = newFalseExpr(self.step())
result.file = self.file
of Float:
result = newFloatExpr(self.step())
result.file = self.file
of Integer:
result = newIntExpr(self.step())
result.file = self.file
of Identifier:
result = newIdentExpr(self.step(), self.scopeDepth)
result.file = self.file
of LeftParen:
let tok = self.step()
result = newGroupingExpr(self.expression(), tok)
result.file = self.file
self.expect(RightParen, "unterminated parenthesized expression")
of Yield:
let tok = self.step()
@ -340,6 +346,7 @@ proc primary(self: Parser): Expression =
else:
# Empty yield
result = newYieldExpr(nil, tok)
result.file = self.file
of Await:
let tok = self.step()
if self.currentFunction.isNil():
@ -347,6 +354,7 @@ proc primary(self: Parser): Expression =
if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines", tok)
result = newAwaitExpr(self.expression(), tok)
result.file = self.file
of RightParen, RightBracket, RightBrace:
# This is *technically* unnecessary: the parser would
# throw an error regardless, but it's a little bit nicer
@ -354,27 +362,36 @@ proc primary(self: Parser): Expression =
self.error(&"unmatched '{self.peek().lexeme}'")
of Hex:
result = newHexExpr(self.step())
result.file = self.file
of Octal:
result = newOctExpr(self.step())
result.file = self.file
of Binary:
result = newBinExpr(self.step())
result.file = self.file
of String:
result = newStrExpr(self.step())
result.file = self.file
of Function:
discard self.step()
result = Expression(self.funDecl(isLambda=true))
result.file = self.file
of Coroutine:
discard self.step()
result = Expression(self.funDecl(isAsync=true, isLambda=true))
result.file = self.file
of Generator:
discard self.step()
result = Expression(self.funDecl(isGenerator=true, isLambda=true))
result.file = self.file
of TokenType.Ref:
discard self.step()
result = newRefExpr(self.expression(), self.peek(-1))
result.file = self.file
of TokenType.Ptr:
discard self.step()
result = newPtrExpr(self.expression(), self.peek(-1))
result.file = self.file
else:
self.error("invalid syntax")
@ -409,6 +426,7 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
argCount += 1
self.expect(RightParen)
result = newCallExpr(callee, arguments, tok)
result.file = self.file
result.closeParen = self.peek(-1)
@ -428,6 +446,7 @@ proc call(self: Parser): Expression =
elif self.match(Dot):
self.expect(Identifier, "expecting attribute name after '.'")
result = newGetItemExpr(result, newIdentExpr(self.peek(-1), self.scopeDepth), self.peek(-1))
result.file = self.file
elif self.match(LeftBracket):
self.parseGenericArgs() # TODO
result = self.makeCall(result)
@ -440,6 +459,7 @@ proc unary(self: Parser): Expression =
## Parses unary expressions
if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens:
result = newUnaryExpr(self.step(), self.unary())
result.file = self.file
else:
result = self.call()
@ -453,6 +473,7 @@ proc parsePow(self: Parser): Expression =
operator = self.step()
right = self.unary()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseMul(self: Parser): Expression =
@ -465,6 +486,7 @@ proc parseMul(self: Parser): Expression =
operator = self.step()
right = self.parsePow()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAdd(self: Parser): Expression =
@ -477,6 +499,7 @@ proc parseAdd(self: Parser): Expression =
operator = self.step()
right = self.parseMul()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseBitwise(self: Parser): Expression =
@ -488,6 +511,7 @@ proc parseBitwise(self: Parser): Expression =
operator = self.step()
right = self.parseAdd()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseCmp(self: Parser): Expression =
@ -499,6 +523,7 @@ proc parseCmp(self: Parser): Expression =
operator = self.step()
right = self.parseAdd()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAnd(self: Parser): Expression =
@ -510,6 +535,7 @@ proc parseAnd(self: Parser): Expression =
operator = self.step()
right = self.parseCmp()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseOr(self: Parser): Expression =
@ -521,6 +547,7 @@ proc parseOr(self: Parser): Expression =
operator = self.step()
right = self.parseAnd()
result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAssign(self: Parser): Expression =
@ -532,8 +559,10 @@ proc parseAssign(self: Parser): Expression =
case result.kind:
of identExpr, sliceExpr:
result = newAssignExpr(result, value, tok)
result.file = self.file
of getItemExpr:
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
result.file = self.file
else:
self.error("invalid assignment target", tok)
@ -547,6 +576,7 @@ proc parseArrow(self: Parser): Expression =
operator = self.step()
right = self.parseAssign()
result = newBinaryExpr(result, operator, right)
result.file = self.file
## End of operator parsing handlers
@ -560,6 +590,7 @@ proc assertStmt(self: Parser): Statement =
var expression = self.expression()
endOfLine("missing semicolon after 'assert'")
result = newAssertStmt(expression, tok)
result.file = self.file
proc beginScope(self: Parser) =
@ -585,6 +616,7 @@ proc blockStmt(self: Parser): Statement =
code.delete(code.high())
self.expect(RightBrace, "expecting '}'")
result = newBlockStmt(code, tok)
result.file = self.file
self.endScope()
@ -595,6 +627,7 @@ proc breakStmt(self: Parser): Statement =
self.error("'break' cannot be used outside loops")
endOfLine("missing semicolon after 'break'")
result = newBreakStmt(tok)
result.file = self.file
proc deferStmt(self: Parser): Statement =
@ -604,6 +637,7 @@ proc deferStmt(self: Parser): Statement =
self.error("'defer' cannot be used outside functions")
endOfLine("missing semicolon after 'defer'")
result = newDeferStmt(self.expression(), tok)
result.file = self.file
proc continueStmt(self: Parser): Statement =
@ -613,6 +647,7 @@ proc continueStmt(self: Parser): Statement =
self.error("'continue' cannot be used outside loops")
endOfLine("missing semicolon after 'continue'")
result = newContinueStmt(tok)
result.file = self.file
proc returnStmt(self: Parser): Statement =
@ -628,6 +663,7 @@ proc returnStmt(self: Parser): Statement =
value = self.expression()
endOfLine("missing semicolon after 'return'")
result = newReturnStmt(value, tok)
result.file = self.file
case self.currentFunction.kind:
of NodeKind.funDecl:
FunDecl(self.currentFunction).hasExplicitReturn = true
@ -646,6 +682,7 @@ proc yieldStmt(self: Parser): Statement =
result = newYieldStmt(self.expression(), tok)
else:
result = newYieldStmt(nil, tok)
result.file = self.file
endOfLine("missing semicolon after 'yield'")
@ -658,6 +695,7 @@ proc awaitStmt(self: Parser): Statement =
self.error("'await' can only be used inside coroutines")
endOfLine("missing semicolon after 'await'")
result = newAwaitStmt(self.expression(), tok)
result.file = self.file
proc raiseStmt(self: Parser): Statement =
@ -670,6 +708,7 @@ proc raiseStmt(self: Parser): Statement =
exception = self.expression()
endOfLine("missing semicolon after 'raise'")
result = newRaiseStmt(exception, tok)
result.file = self.file
proc forEachStmt(self: Parser): Statement =
@ -683,6 +722,7 @@ proc forEachStmt(self: Parser): Statement =
let expression = self.expression()
self.expect(LeftBrace)
result = newForEachStmt(identifier, expression, self.blockStmt(), tok)
result.file = self.file
self.currentLoop = enclosingLoop
@ -719,6 +759,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
pos: (tok.pos.stop + 1, (tok.pos.stop + 1) + len(moduleName)),
relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName))),
self.scopeDepth), tok)
result.file = self.file
moduleName &= ".pn"
var lexer = newLexer()
lexer.fillSymbolTable()
@ -778,6 +819,7 @@ proc tryStmt(self: Parser): Statement =
if handler.exc.isNil() and i != handlers.high():
self.error("catch-all exception handler with bare 'except' must come last in try statement", handler.exc.token)
result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
result.file = self.file
proc whileStmt(self: Parser): Statement =
@ -789,6 +831,7 @@ proc whileStmt(self: Parser): Statement =
self.expect(LeftBrace)
self.currentLoop = Loop
result = newWhileStmt(condition, self.blockStmt(), tok)
result.file = self.file
self.currentLoop = enclosingLoop
self.endScope()
@ -807,6 +850,7 @@ proc ifStmt(self: Parser): Statement =
self.expect(LeftBrace, "expecting 'if' or block statement")
elseBranch = self.blockStmt()
result = newIfStmt(condition, thenBranch, elseBranch, tok)
result.file = self.file
proc exportStmt(self: Parser): Statement =
@ -818,6 +862,7 @@ proc exportStmt(self: Parser): Statement =
exported = newIdentExpr(self.peek(-1))
endOfLine("missing semicolon after 'raise'")
result = newExportStmt(exported, tok)
result.file = self.file
template checkDecl(self: Parser, isPrivate: bool) =
@ -841,6 +886,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
self.error("duplicate pragmas are not allowed")
names.add(self.peek(-1).lexeme)
name = newIdentExpr(self.peek(-1), self.scopeDepth)
name.file = self.file
if not self.match(":"):
if self.match("]"):
result.add(newPragma(name, @[]))
@ -862,6 +908,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
self.error("pragma arguments can only be literals", exp.token)
args.add(LiteralExpr(exp))
result.add(newPragma(name, args))
result[^1].file = self.file
if self.match(","):
continue
@ -913,6 +960,7 @@ proc varDecl(self: Parser, isLet: bool = false,
if not hasInit and VarDecl(result).valueType.isNil():
self.error("expecting initializer or explicit type annotation, but neither was found", result.token)
result.pragmas = pragmas
result.file = self.file
proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr, valueType: Expression]],
@ -924,6 +972,7 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
self.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
self.expect(Identifier, "expecting parameter name")
parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth)
parameter.name.file = self.file
if self.match(":"):
parameter.valueType = self.expression()
for i in countdown(arguments.high(), 0):
@ -966,6 +1015,7 @@ proc parseFunExpr(self: Parser): LambdaExpr =
result.returnType = self.expression()
result.arguments = arguments
result.defaults = defaults
result.file = self.file
proc parseGenericConstraint(self: Parser): Expression =
@ -976,8 +1026,10 @@ proc parseGenericConstraint(self: Parser): Expression =
case self.peek().lexeme:
of "|":
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
result.file = self.file
of "~":
result = newUnaryExpr(self.step(), result)
result.file = self.file
of ",":
discard # Comma is handled in parseGenerics()
else:
@ -990,6 +1042,7 @@ proc parseGenerics(self: Parser, decl: Declaration) =
while not self.check(RightBracket) and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
self.expect(":", "expecting type constraint after generic name")
gen.cond = self.parseGenericConstraint()
decl.generics.add(gen)
@ -1101,11 +1154,13 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
self.error(&"missing type declaration for '{argument.name.token.lexeme}' in function declaration")
self.currentFunction = enclosingFunction
result.pragmas = pragmas
result.file = self.file
proc expression(self: Parser): Expression =
## Parses expressions
result = self.parseArrow() # Highest-level expression
result.file = self.file
proc expressionStatement(self: Parser): Statement =
@ -1114,6 +1169,7 @@ proc expressionStatement(self: Parser): Statement =
var expression = self.expression()
endOfLine("missing semicolon at end of expression", expression.token)
result = Statement(newExprStmt(expression, expression.token))
result.file = self.file
proc statement(self: Parser): Statement =
@ -1171,6 +1227,7 @@ proc statement(self: Parser): Statement =
result = self.tryStmt()
else:
result = self.expressionStatement()
result.file = self.file
proc typeDecl(self: Parser): TypeDecl =
@ -1241,6 +1298,7 @@ proc typeDecl(self: Parser): TypeDecl =
if not self.check(RightBrace):
self.expect(",", "expecting comma after enum field declaration")
result.pragmas = pragmas
result.file = self.file
proc declaration(self: Parser): Declaration =

View File

@ -154,7 +154,7 @@ proc repl =
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false,
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug) =
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, run: bool = true) =
var
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
@ -237,7 +237,8 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
vm.run(serialized.chunk, breakpoints)
if run:
vm.run(serialized.chunk, breakpoints)
except LexingError:
print(LexingError(getCurrentException()))
except ParseError:
@ -264,6 +265,7 @@ when isMainModule:
var dis: bool = false
var mismatches: bool = false
var mode: CompileMode = CompileMode.Debug
var run: bool = true
for kind, key, value in optParser.getopt():
case kind:
of cmdArgument:
@ -323,6 +325,8 @@ when isMainModule:
quit()
of "disassemble":
dis = true
of "compile":
run = false
else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'")
quit()
@ -349,6 +353,8 @@ when isMainModule:
except ValueError:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
quit()
of "c":
run = false
of "d":
dis = true
else:
@ -360,7 +366,7 @@ when isMainModule:
if file == "":
repl()
else:
runFile(file, fromString, dump, breaks, dis, warnings, mismatches, mode)
runFile(file, fromString, dump, breaks, dis, warnings, mismatches, mode, run)

View File

@ -2,12 +2,12 @@
import values;
operator `>`*[T: Number](a, b: T): bool {
operator `>`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "GreaterThan", pure]
}
operator `<`*[T: Number](a, b: T): bool {
operator `<`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "LessThan", pure]
}
@ -21,11 +21,72 @@ operator `!=`*[T: Number](a, b: T): bool {
}
operator `>=`*[T: Number](a, b: T): bool {
operator `>=`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "GreaterOrEqual", pure]
}
operator `<=`*[T: Number](a, b: T): bool {
operator `<=`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "LessOrEqual", pure]
}
operator `>`*[T: SignedInteger](a, b: T): bool {
#pragma[magic: "SignedGreaterThan", pure]
}
operator `<`*[T: SignedInteger](a, b: T): bool {
#pragma[magic: "SignedLessThan", pure]
}
operator `>=`*[T: SignedInteger](a, b: T): bool {
#pragma[magic: "SignedGreaterOrEqual", pure]
}
operator `<=`*[T: SignedInteger](a, b: T): bool {
#pragma[magic: "SignedLessOrEqual", pure]
}
operator `>`*(a, b: float64): bool {
#pragma[magic: "Float64GreaterThan", pure]
}
operator `<`*(a, b: float64): bool {
#pragma[magic: "Float64LessThan", pure]
}
operator `>=`*(a, b: float64): bool {
#pragma[magic: "Float64GreaterOrEqual", pure]
}
operator `<=`*(a, b: float64): bool {
#pragma[magic: "Float64LessOrEqual", pure]
}
operator `>`*(a, b: float32): bool {
#pragma[magic: "Float32GreaterThan", pure]
}
operator `<`*(a, b: float32): bool {
#pragma[magic: "Float32LessThan", pure]
}
operator `>=`*(a, b: float32): bool {
#pragma[magic: "Float32GreaterOrEqual", pure]
}
operator `<=`*(a, b: float32): bool {
#pragma[magic: "Float32LessOrEqual", pure]
}

View File

@ -2,8 +2,6 @@
import values;
# Some useful builtins
fn clock*: float {
#pragma[magic: "SysClock64"]
}

View File

@ -7,10 +7,31 @@ const e* = 2.718281828459045;
const tau* = 6.283185307179586;
fn abs*[T: int64 | int32 | int16 | int8](n: T): T {
## Returns the absolute value of the given number
fn abs*[T: SignedInteger](n: T): T {
## Returns the absolute value of
## the given number
if n < 0 {
return -n;
}
return n;
}
}
fn abs*(f: float64): float64 {
## Returns the absolute value of
## the given number
if f < 0.0 {
return -f;
}
return f;
}
fn abs*(f: float32): float32 {
## Returns the absolute value of
## the given number
if f < 0.0'f32 {
return -f;
}
return f;
}

View File

@ -14,7 +14,6 @@ export logical;
export misc;
export comparisons;
var version* = 1;
var _private = 5; # Invisible outside the module (underscore is to silence warning)
var test* = 0x60;

View File

@ -132,15 +132,6 @@ proc argumentTripleInstruction(self: Debugger, instruction: OpCode) {.used.} =
self.current += 4
proc storeClosureInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
var idx = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
var idx2 = [self.chunk.code[self.current + 4], self.chunk.code[self.current + 5], self.chunk.code[self.current + 6]].fromTriple()
printInstruction(instruction)
stdout.styledWriteLine(fgGreen, ", stores element at position ", fgYellow, $idx, fgGreen, " into position ", fgYellow, $idx2)
self.current += 7
proc callInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
@ -150,17 +141,6 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
self.current += 1
proc callClosureInstruction(self: Debugger, instruction: OpCode) =
## Debugs closure calls
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
self.current += 3
var envSize = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
self.current += 3
printInstruction(instruction)
styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen, " with environment of size ", fgYellow, $envSize, fgGreen
self.current += 1
proc constantInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on the constant table
var size: uint
@ -190,7 +170,7 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
inc(self.current)
for i in countup(orig, self.current + 1):
self.checkFunctionStart(i)
proc disassembleInstruction*(self: Debugger) =
## Takes one bytecode instruction and prints it
@ -210,18 +190,14 @@ proc disassembleInstruction*(self: Debugger) =
self.stackTripleInstruction(opcode)
of argumentDoubleInstructions:
self.argumentDoubleInstruction(opcode)
of StoreClosure:
self.storeClosureInstruction(opcode)
of Call:
self.callInstruction(opcode)
of CallClosure:
self.callClosureInstruction(opcode)
of jumpInstructions:
self.jumpInstruction(opcode)
else:
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
self.current += 1
proc parseFunctions(self: Debugger) =
## Parses function information in the chunk

View File

@ -44,8 +44,8 @@ proc print*(exc: CompileError) =
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
printError(file, exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.node.getRelativeBoundaries(), exc.compiler.getCurrentFunction(),
printError(file, readFile(file).splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.node.getRelativeBoundaries(), exc.function,
exc.msg)

7
tests/abs.pn Normal file
View File

@ -0,0 +1,7 @@
import std;
import math;
print(math.abs(-math.pi) == math.pi);
print(math.abs(5) == 5);
print(math.abs(-1) == 1);

View File

@ -1,16 +0,0 @@
# Tests closures
import std;
fn makeClosure(x: int): fn: int {
fn inner: int {
return x;
}
return inner;
}
print(makeClosure(38)() == 38); # true;
var closure = makeClosure(42);
print(closure);
#closure(); # TODO: Fix

View File

@ -1,12 +0,0 @@
import std;
fn makeAdder(x: int): fn (n: int): int {
fn adder(n: int): int {
return x + n;
}
return adder;
}
print(makeAdder(5)(2) == 7); # true

View File

@ -1,10 +0,0 @@
# Tests operator dispatching
operator `+`(a: int): int {
return a;
}
+1; # Works: defined for int64
# +1'i32; # Will not work

View File

@ -5,7 +5,7 @@ import std;
fn foo: int;
print(foo());
print(foo() == 42);
fn foo: int {return 42;} # Commenting this line will cause an error

View File

@ -1,4 +1,4 @@
# Another test for generic functions
# Another test for generic functions. This should fail to compile
import std;

View File

@ -1,12 +0,0 @@
import std;
var a = 5;
fn f {
a = 6;
}
print(a);
f();
#print(a);

View File

@ -1,6 +0,0 @@
import std;
import math;
print("Testing math module");
print(math.abs(-5) == 5);

View File

@ -1,19 +0,0 @@
# Tests var parameters. TODO: They don't actually exist yet, they're just checked statically
import std;
operator `+=`(a: var int, b: int) {
a = a + b;
}
fn plusOne(x: var int): int {
x += 1;
return x;
}
var x = 5;
print(plusOne(x));
# plusOne(38); # If you uncomment this, the compiler errors out!

View File

@ -7,8 +7,8 @@ var x = 5;
var x = 55;
{
var x = 22;
print(x);
print(x == 22);
}
print(x);
print(x == 55);
}
print(x);
print(x == 5);