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 operands: seq[uint64] # The operand stack
cache: array[6, uint64] # The singletons cache cache: array[6, uint64] # The singletons cache
frames: seq[uint64] # Stores the bottom of stack frames 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 results: seq[uint64] # Stores function return values
gc: PeonGC # A reference to the VM's garbage collector gc: PeonGC # A reference to the VM's garbage collector
breakpoints: seq[uint64] # Breakpoints where we call our debugger 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: for obj in self.operands:
if obj in self.gc.pointers: if obj in self.gc.pointers:
live.incl(obj) live.incl(obj)
for obj in self.envs:
if obj in self.gc.pointers:
live.incl(obj)
# We preallocate the space on the seq # We preallocate the space on the seq
result = newSeqOfCap[ptr HeapObject](len(live)) result = newSeqOfCap[ptr HeapObject](len(live))
var obj: ptr HeapObject var obj: ptr HeapObject
@ -368,7 +363,6 @@ proc newPeonVM*: PeonVM =
result.frames = @[] result.frames = @[]
result.operands = @[] result.operands = @[]
result.results = @[] result.results = @[]
result.envs = @[]
result.calls = @[] result.calls = @[]
@ -470,30 +464,6 @@ proc setc(self: var PeonVM, idx: int, val: uint64) =
self.calls[idx.uint + self.frames[^1]] = val 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 # Byte-level primitives to read and decode
# bytecode # bytecode
@ -700,20 +670,6 @@ when debugVM: # So nim shuts up
if i < self.frames.high(): if i < self.frames.high():
stdout.styledWrite(fgYellow, ", ") stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]" 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": of "r", "results":
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[") stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
for i, e in self.results: for i, e in self.results:
@ -769,9 +725,6 @@ proc dispatch*(self: var PeonVM) =
of LoadString: of LoadString:
# Loads the string's pointer onto the stack # Loads the string's pointer onto the stack
self.push(cast[uint64](self.constReadString(int(self.readLong()), int(self.readLong())))) 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: of LoadFloat32:
self.push(cast[uint64](self.constReadFloat32(int(self.readLong())))) self.push(cast[uint64](self.constReadFloat32(int(self.readLong()))))
of LoadFloat64: of LoadFloat64:
@ -804,32 +757,6 @@ proc dispatch*(self: var PeonVM) =
# not needed there anymore # not needed there anymore
discard self.pop() discard self.pop()
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: of Return:
# Returns from a function. # Returns from a function.
# Every peon program is wrapped # Every peon program is wrapped
@ -890,25 +817,6 @@ proc dispatch*(self: var PeonVM) =
# condition in the VM's bytecode dispatch loop (which is # condition in the VM's bytecode dispatch loop (which is
# not a great idea) # not a great idea)
self.pushc(self.pop()) 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: of LoadVar:
# Pushes a variable from the call stack # Pushes a variable from the call stack
# onto the operand stack # onto the operand stack
@ -987,7 +895,7 @@ proc dispatch*(self: var PeonVM) =
# types, we don't need specialized instructions # types, we don't need specialized instructions
# to operate on them # to operate on them
of Negate: of Negate:
self.push(cast[uint64](-int64(self.pop()))) self.push(cast[uint64](-(cast[int64](self.pop()))))
of NegateFloat64: of NegateFloat64:
self.push(cast[uint64](-cast[float](self.pop()))) self.push(cast[uint64](-cast[float](self.pop())))
of NegateFloat32: of NegateFloat32:
@ -1001,7 +909,7 @@ proc dispatch*(self: var PeonVM) =
of Divide: of Divide:
self.push(self.pop() div self.pop()) self.push(self.pop() div self.pop())
of SignedDivide: 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: of AddFloat64:
self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop()))) self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop())))
of SubtractFloat64: of SubtractFloat64:
@ -1021,7 +929,7 @@ proc dispatch*(self: var PeonVM) =
of Pow: of Pow:
self.push(uint64(self.pop() ^ self.pop())) self.push(uint64(self.pop() ^ self.pop()))
of SignedPow: of SignedPow:
self.push(uint64(int64(self.pop()) ^ int64(self.pop()))) self.push(uint64(cast[int64](self.pop()) ^ cast[int64](self.pop())))
of PowFloat64: of PowFloat64:
self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop())))) self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of PowFloat32: of PowFloat32:
@ -1029,7 +937,7 @@ proc dispatch*(self: var PeonVM) =
of Mod: of Mod:
self.push(uint64(self.pop() mod self.pop())) self.push(uint64(self.pop() mod self.pop()))
of SignedMod: 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: of ModFloat64:
self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop())))) self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of ModFloat32: of ModFloat32:
@ -1056,7 +964,31 @@ proc dispatch*(self: var PeonVM) =
of GreaterOrEqual: of GreaterOrEqual:
self.push(self.getBool(self.pop() !>= self.pop())) self.push(self.getBool(self.pop() !>= self.pop()))
of LessOrEqual: 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 # Print opcodes
of PrintInt64: of PrintInt64:
echo cast[int64](self.pop()) echo cast[int64](self.pop())

View File

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

View File

@ -59,10 +59,6 @@ type
returnType: Type returnType: Type
builtinOp: string builtinOp: string
fun: Declaration fun: Declaration
isClosure: bool
envLen: int
children: seq[Type]
parent: Type
retJumps: seq[int] retJumps: seq[int]
forwarded: bool forwarded: bool
location: int location: int
@ -90,7 +86,8 @@ export bytecode
type type
WarningKind* {.pure.} = enum WarningKind* {.pure.} = enum
## A warning enumeration type ## A warning enumeration type
UnreachableCode, UnusedName, ShadowOuterScope UnreachableCode, UnusedName, ShadowOuterScope,
MutateOuterScope
CompileMode* {.pure.} = enum CompileMode* {.pure.} = enum
## A compilation mode enumeration ## A compilation mode enumeration
Debug, Release Debug, Release
@ -134,8 +131,6 @@ type
belongsTo: Name belongsTo: Name
# Where is this node declared in its file? # Where is this node declared in its file?
line: int line: int
# Has this name been closed over?
isClosedOver: bool
# Has this name been referenced at least once? # Has this name been referenced at least once?
resolved: bool resolved: bool
# The AST node associated with this node. This # The AST node associated with this node. This
@ -151,6 +146,12 @@ type
isReal: bool isReal: bool
# Is this name a builtin? # Is this name a builtin?
isBuiltin: bool 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 Loop = object
## A "loop object" used ## A "loop object" used
@ -243,6 +244,9 @@ type
showMismatches: bool showMismatches: bool
# Are we compiling in debug mode? # Are we compiling in debug mode?
mode: CompileMode mode: CompileMode
# The topmost occupied stack slot
# in the current frame (0-indexed)
stackIndex: int
PragmaKind = enum PragmaKind = enum
## An enumeration of pragma types ## An enumeration of pragma types
Immediate, Immediate,
@ -253,8 +257,8 @@ type
kind: PragmaKind kind: PragmaKind
handler: proc (self: Compiler, pragma: Pragma, name: Name) handler: proc (self: Compiler, pragma: Pragma, name: Name)
CompileError* = ref object of PeonException CompileError* = ref object of PeonException
compiler*: Compiler
node*: ASTNode node*: ASTNode
function*: Declaration
# Forward declarations # 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 identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Type {.discardable.}
proc varDecl(self: Compiler, node: VarDecl) proc varDecl(self: Compiler, node: VarDecl)
proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name 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 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 unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discardable.}
proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable.}
proc infer(self: Compiler, node: LiteralExpr): Type proc infer(self: Compiler, node: LiteralExpr): Type
@ -319,6 +324,7 @@ proc newCompiler*(replMode: bool = false): Compiler =
result.forwarded = @[] result.forwarded = @[]
result.disabledWarnings = @[] result.disabledWarnings = @[]
result.functions = @[] result.functions = @[]
result.stackIndex = 1
## Public getters for nicer error formatting ## Public getters for nicer error formatting
@ -330,8 +336,8 @@ proc getSource*(self: Compiler): string = self.source
## Utility functions ## Utility functions
proc `$`*(self: Name): string = $self[] proc `$`*(self: Name): string = $(self[])
#proc `$`(self: Type): string = $self[] proc `$`(self: Type): string = $(self[])
proc hash(self: Name): Hash = self.ident.token.lexeme.hash() 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() 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 ## Raises a CompileError exception
let node = if node.isNil(): self.getCurrentNode() else: node 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) = 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) 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] = proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
## Adds a constant to the current chunk's constant table ## Adds a constant to the current chunk's constant table
## and returns its index as a 3-byte array of uint8s ## and returns its index as a 3-byte array of uint8s
@ -520,18 +563,17 @@ proc patchJump(self: Compiler, offset: int) =
## jump using emitJump ## jump using emitJump
var jump: int = self.chunk.code.len() - self.jumps[offset].offset var jump: int = self.chunk.code.len() - self.jumps[offset].offset
if jump < 0: 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: if jump > 16777215:
# TODO: Emit consecutive jumps? # TODO: Emit consecutive jumps using insertAt
self.error("cannot jump more than 16777215 instructions") self.error("cannot jump more than 16777215 instructions")
if jump > 0: if jump > 0:
self.setJump(self.jumps[offset].offset, (jump - 4).toTriple()) self.setJump(self.jumps[offset].offset, (jump - 4).toTriple())
self.jumps[offset].patched = true self.jumps[offset].patched = true
else: else:
# TODO: Discard jump of size 0 and update # TODO: Discard jump of size 0
# bytecode metadata
discard discard
proc emitJump(self: Compiler, opcode: OpCode, line: int): int = proc emitJump(self: Compiler, opcode: OpCode, line: int): int =
## Emits a dummy jump offset to be patched later ## 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 = proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8]): int =
## Inserts the given instruction into the ## Inserts the given instruction into the
## chunk's code segment and updates internal ## 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 result = where
let oldLen = self.chunk.code.len() let oldLen = self.chunk.code.len()
self.chunk.code.insert(uint8(opcode), where) 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) 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 = proc resolve(self: Compiler, name: string): Name =
## Traverses all existing namespaces and returns ## Traverses all existing namespaces and returns
## the first object with the given name. 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 # might not want to also have access to C's and D's
# names as they might clash with its own stuff) # names as they might clash with its own stuff)
continue continue
if obj.kind == Argument and obj.belongsTo != self.currentFunction:
continue
result = obj result = obj
result.resolved = true
break break
if not result.isNil():
self.compileDecl(result)
proc resolve(self: Compiler, name: IdentExpr): Name = 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}'") 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 = proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool =
## Compares type unions between each other ## Compares type unions between each other
var var
@ -958,26 +921,22 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
case node.kind: case node.kind:
of intExpr, binExpr, octExpr, hexExpr: of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'") 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: if size.len() == 1:
return Type(kind: Int64) return Type(kind: Int64)
let typ = size[1].toIntrinsic() let typ = size[1].toIntrinsic()
if not self.compare(typ, nil): if not self.compare(typ, nil):
return typ return typ
else: else:
self.error(&"invalid type specifier '{size[1]}' for int") self.error(&"invalid type specifier '{size[1]}' for int", node)
of floatExpr: of floatExpr:
let size = node.token.lexeme.split("'") let size = node.token.lexeme.split("'")
if len(size) notin 1..2: if size.len() == 1:
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":
return Type(kind: Float64) return Type(kind: Float64)
let typ = size[1].toIntrinsic() let typ = size[1].toIntrinsic()
if not typ.isNil(): if not typ.isNil():
return typ return typ
else: else:
self.error(&"invalid type specifier '{size[1]}' for float") self.error(&"invalid type specifier '{size[1]}' for float", node)
of trueExpr: of trueExpr:
return Type(kind: Bool) return Type(kind: Bool)
of falseExpr: of falseExpr:
@ -985,7 +944,7 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
of strExpr: of strExpr:
return Type(kind: String) return Type(kind: String)
else: else:
discard # TODO discard # Unreachable
proc infer(self: Compiler, node: Expression): Type = 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 ## Looks for objects that have been already declared as
## public within the given module with the given name. ## public within the given module with the given name.
## Returns all objects that apply. If the name is an ## Returns all objects that apply. If the name is an
## empty string, returns all objects within the given moule ## empty string, returns all objects within the given
for obj in reversed(self.names): ## module, regardless of whether they are exported to
if name != "" and obj.ident.token.lexeme != name: ## the current one or not
continue if name == "":
if not obj.isPrivate and obj.owner == module: for obj in reversed(self.names):
result.add(obj) 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] = 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)" msg &= " (compile with --showMismatches for more details)"
self.error(msg, node) self.error(msg, node)
if impl[0].valueType.forwarded and not allowFwd: 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] result = impl[0]
for (a, b) in zip(result.valueType.args, kind.args): for (a, b) in zip(result.valueType.args, kind.args):
if not a.kind.isAny() and b.kind.isAny(): if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node) 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) = 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, "GreaterThan": GreaterThan,
"LessOrEqual": LessOrEqual, "LessOrEqual": LessOrEqual,
"GreaterOrEqual": GreaterOrEqual, "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, "PrintString": PrintString,
"SysClock64": SysClock64, "SysClock64": SysClock64,
"LogicalNot": LogicalNot, "LogicalNot": LogicalNot,
"NegInf": LoadNInf "NegInf": LoadNInf
}.to_table() }.to_table()
if fn.builtinOp == "print": if fn.builtinOp == "print":
var typ = self.expression(args[0], compile=false) let typ = self.inferOrError(args[0])
case typ.kind: case typ.kind:
of Int64: of Int64:
self.emitByte(PrintInt64, line) self.emitByte(PrintInt64, line)
@ -1297,9 +1272,20 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line
of Inf: of Inf:
self.emitByte(PrintInf, line) self.emitByte(PrintInf, line)
of Function: 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: 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 return
if fn.builtinOp in codes: if fn.builtinOp in codes:
self.emitByte(codes[fn.builtinOp], line) self.emitByte(codes[fn.builtinOp], line)
@ -1327,16 +1313,6 @@ proc beginScope(self: Compiler) =
inc(self.depth) 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) = proc patchForwardDeclarations(self: Compiler) =
## Patches forward declarations and looks ## Patches forward declarations and looks
@ -1386,7 +1362,7 @@ proc endScope(self: Compiler) =
# be referenced anymore, of course) # be referenced anymore, of course)
if name.kind notin [NameKind.Var, NameKind.Argument]: if name.kind notin [NameKind.Var, NameKind.Argument]:
continue continue
elif name.kind == NameKind.Argument: elif name.kind == NameKind.Argument and not name.belongsTo.isNil():
if name.belongsTo.isBuiltin: if name.belongsTo.isBuiltin:
# Arguments to builtin functions become temporaries on the # Arguments to builtin functions become temporaries on the
# stack and are popped automatically # stack and are popped automatically
@ -1397,6 +1373,7 @@ proc endScope(self: Compiler) =
# (it may need them later) # (it may need them later)
names.delete(names.high()) names.delete(names.high())
continue continue
inc(popCount)
if not name.resolved: if not name.resolved:
case name.kind: case name.kind:
of NameKind.Var: 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) self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
of NameKind.Argument: of NameKind.Argument:
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate: 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 # Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for # warning if the function was generated internally by the compiler (for
# example as a result of generic specialization) because such objects do # 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) self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
else: else:
discard discard
inc(popCount) dec(self.stackIndex, popCount)
if popCount > 1: if popCount > 1:
# If we're popping more than one variable, # If we're popping more than one variable,
# we emit a bunch of PopN instructions until # 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 returnType: nil, # We check it later
args: @[], args: @[],
fun: node, fun: node,
children: @[],
forwarded: node.body.isNil()), forwarded: node.body.isNil()),
ident: node.name, ident: node.name,
node: node, node: node,
@ -1578,6 +1554,7 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
) )
) )
n = self.names[^1] n = self.names[^1]
declaredName = node.name.token.lexeme
if node.value.isNil(): if node.value.isNil():
discard # TODO: Fields discard # TODO: Fields
else: else:
@ -1603,18 +1580,20 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
if name == n: if name == n:
continue continue
# We don't check for name clashes with functions because self.match() does that # 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.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum] and name.depth == n.depth and name.owner == n.owner:
if name.owner != self.currentModule: 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})")
if name.isPrivate: for name in self.names:
continue if name == n:
elif self.currentModule notin name.exportedTo: break
continue if name.ident.token.lexeme != declaredName:
elif name.depth == 0: continue
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})") if name.owner != n.owner and (name.isPrivate or n.owner notin name.exportedTo):
elif name.depth == n.depth: continue
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})") if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
elif name.depth < self.depth: if name.depth < n.depth:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope") 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 return n
@ -1907,7 +1886,10 @@ proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discar
returnType: Type(kind: Any), returnType: Type(kind: Any),
args: @[("", self.inferOrError(node.a), default)]) args: @[("", self.inferOrError(node.a), default)])
let impl = self.match(node.token.lexeme, fn, node) 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: if compile:
self.generateCall(impl, @[node.a], impl.line) 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 ## Compiles access to identifiers
var s = name var s = name
if s.isNil(): if s.isNil():
s = self.resolveOrError(node) s = self.resolveOrError(node)
var t = self.findByType(s.ident.token.lexeme, Type(kind: All))
s = t[0] # Shadowing!
result = s.valueType result = s.valueType
if not compile: if not compile:
return return
var node = s.ident
if s.isConst: if s.isConst:
# Constants are always emitted as Load* instructions # Constants are always emitted as Load* instructions
# no matter the scope depth # 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: elif s.kind == NameKind.Function:
# Functions have no runtime representation, they're just # Functions have no runtime representation, they're just
# a location to jump to, but we pretend they aren't and # 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) self.emitByte(LoadInf, node.token.line)
else: else:
discard # Unreachable 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: else:
# Loads a regular variable from the current frame # Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line) self.emitByte(LoadVar, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above! # 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 ## Compiles assignment expressions
case node.kind: case node.kind:
of assignExpr: 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.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
self.check(node.value, r.valueType) self.check(node.value, r.valueType)
self.expression(node.value) 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: if not compile:
return return
if not r.isClosedOver: self.emitByte(StoreVar, node.token.line)
self.emitByte(StoreVar, node.token.line) self.emitBytes(position.toTriple(), 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)
of setItemExpr: of setItemExpr:
let node = SetItemExpr(node) let node = SetItemExpr(node)
let name = IdentExpr(node.name) 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 # Creates a new call frame and jumps
# to the function's first instruction # to the function's first instruction
# in the code # in the code
if not fn.isClosure: self.emitByte(Call, line)
self.emitByte(Call, line)
else:
self.emitByte(CallClosure,line)
self.emitBytes(args.len().toTriple(), line) self.emitBytes(args.len().toTriple(), line)
if fn.isClosure:
self.emitBytes(fn.envLen.toTriple(), line)
self.patchReturnAddress(pos) self.patchReturnAddress(pos)
@ -2119,12 +2064,15 @@ proc prepareFunction(self: Compiler, fn: Name) =
constraints = @[] constraints = @[]
# We now declare and typecheck the function's # We now declare and typecheck the function's
# arguments # arguments
let idx = self.stackIndex
self.stackIndex = 1
var default: Expression var default: Expression
var i = 0 var i = 0
var node = FunDecl(fn.node) var node = FunDecl(fn.node)
for argument in node.arguments: for argument in node.arguments:
if self.names.high() > 16777215: if self.names.high() > 16777215:
self.error("cannot declare more than 16777215 variables at a time") self.error("cannot declare more than 16777215 variables at a time")
inc(self.stackIndex)
self.names.add(Name(depth: fn.depth + 1, self.names.add(Name(depth: fn.depth + 1,
isPrivate: true, isPrivate: true,
owner: fn.owner, owner: fn.owner,
@ -2137,7 +2085,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
line: argument.name.token.line, line: argument.name.token.line,
belongsTo: fn, belongsTo: fn,
kind: NameKind.Argument, kind: NameKind.Argument,
node: argument.name node: argument.name,
position: self.stackIndex
)) ))
if node.arguments.high() - node.defaults.high() <= node.arguments.high(): if node.arguments.high() - node.defaults.high() <= node.arguments.high():
# There's a default argument! # There's a default argument!
@ -2149,6 +2098,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
# The function needs a return type too! # The function needs a return type too!
if not FunDecl(fn.node).returnType.isNil(): if not FunDecl(fn.node).returnType.isNil():
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType) 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) = 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 # Creates a new call frame and jumps
# to the function's first instruction # to the function's first instruction
# in the code # in the code
if not fn.valueType.isClosure: self.emitByte(Call, line)
self.emitByte(Call, line)
else:
self.emitByte(CallClosure, line)
self.emitBytes(args.len().toTriple(), line) self.emitBytes(args.len().toTriple(), line)
if fn.valueType.isClosure:
self.emitBytes(fn.valueType.envLen.toTriple(), line)
self.patchReturnAddress(pos) 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 kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message
if kind.isNil(): if kind.isNil():
if argument.kind == NodeKind.identExpr: 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) self.error(&"positional argument {i + 1} in function call has no type", argument)
args.add(("", kind, default)) args.add(("", kind, default))
argExpr.add(argument) argExpr.add(argument)
@ -2232,12 +2178,12 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
kind = self.infer(argument.value) kind = self.infer(argument.value)
if kind.isNil(): if kind.isNil():
if argument.value.kind == NodeKind.identExpr: 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) self.error(&"keyword argument '{argument.name.token.lexeme}' in function call has no type", argument.value)
args.add((argument.name.token.lexeme, kind, default)) args.add((argument.name.token.lexeme, kind, default))
argExpr.add(argument.value) argExpr.add(argument.value)
case node.callee.kind: case node.callee.kind:
of identExpr: of NodeKind.identExpr:
# Calls like hi() # Calls like hi()
let impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node) let impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node)
result = impl.valueType 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 # one and work our way to the innermost call
for exp in all: for exp in all:
result = self.call(exp, compile) result = self.call(exp, compile)
#echo result
#result = result.returnType
if compile and result.kind == Function: if compile and result.kind == Function:
self.generateCall(result, argExpr, node.token.line) self.generateCall(result, argExpr, node.token.line)
result = result.returnType 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) # TODO: Calling lambdas on-the-fly (i.e. on the same line)
else: else:
let typ = self.infer(node) 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) 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 ## Compiles accessing to fields of a type or
## module namespace. If the compile flag is set ## module namespace. If the compile flag is set
## to false, no code is generated for resolving ## 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)) let name = self.resolveOrError(IdentExpr(node.obj))
case name.kind: case name.kind:
of NameKind.Module: of NameKind.Module:
let value = self.findInModule(node.name.token.lexeme, name) var values = self.findInModule(node.name.token.lexeme, name)
if len(value) == 0 or self.currentModule notin value[0].exportedTo: if len(values) == 0:
self.error(&"reference to undefined name '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'") 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: if compile:
self.identifier(nil, value[0]) self.identifier(nil, values[0])
result = value[0].valueType
else: else:
self.error("invalid syntax", node.obj) self.error("invalid syntax", node.obj)
else: 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.} = proc lambdaExpr(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} =
## Compiles lambda functions as expressions ## 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() self.beginScope()
var constraints: seq[tuple[match: bool, kind: Type]] = @[] var constraints: seq[tuple[match: bool, kind: Type]] = @[]
for gen in node.generics: 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! # The function needs a return type too!
if not node.returnType.isNil(): if not node.returnType.isNil():
result.returnType = self.inferOrError(node.returnType) result.returnType = self.inferOrError(node.returnType)
self.endScope()
if not compile: if not compile:
return return
# TODO # TODO
self.endScope()
proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} = 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 # would be lost in the call anyway. The differentiation
# happens in self.assignment() # happens in self.assignment()
of NodeKind.setItemExpr, NodeKind.assignExpr: of NodeKind.setItemExpr, NodeKind.assignExpr:
return self.assignment(node, compile).valueType return self.assignment(node, compile)
of NodeKind.identExpr: of NodeKind.identExpr:
return self.identifier(IdentExpr(node), compile=compile) return self.identifier(IdentExpr(node), compile=compile)
of NodeKind.unaryExpr: of NodeKind.unaryExpr:
@ -2501,42 +2465,6 @@ proc exportStmt(self: Compiler, node: ExportStmt) =
discard 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) = proc statement(self: Compiler, node: Statement) =
## Compiles all statements ## Compiles all statements
case node.kind: case node.kind:
@ -2619,8 +2547,9 @@ proc varDecl(self: Compiler, node: VarDecl) =
self.emitByte(AddVar, node.token.line) self.emitByte(AddVar, node.token.line)
self.declare(node) self.declare(node)
var name = self.names[^1] var name = self.names[^1]
inc(self.stackIndex)
name.position = self.stackIndex
name.valueType = typ name.valueType = typ
proc funDecl(self: Compiler, node: FunDecl, name: Name) = proc funDecl(self: Compiler, node: FunDecl, name: Name) =
@ -2630,32 +2559,27 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
var node = node var node = node
var jmp: int var jmp: int
# We store the current function # We store the current function
var function = self.currentFunction let function = self.currentFunction
if node.body.isNil(): if node.body.isNil():
# We ignore forward declarations # We ignore forward declarations
self.forwarded.add((name, 0)) self.forwarded.add((name, 0))
name.valueType.forwarded = true name.valueType.forwarded = true
self.currentFunction = function self.currentFunction = function
return 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 self.currentFunction = name
if self.currentFunction.isBuiltin: if self.currentFunction.isBuiltin:
self.currentFunction = function self.currentFunction = function
return return
let stackIdx = self.stackIndex
self.stackIndex = name.position
# A function's code is just compiled linearly # A function's code is just compiled linearly
# and then jumped over # and then jumped over
jmp = self.emitJump(JumpForwards, node.token.line) jmp = self.emitJump(JumpForwards, node.token.line)
name.codePos = self.chunk.code.len() name.codePos = self.chunk.code.len()
name.valueType.location = name.codePos name.valueType.location = name.codePos
# We let our debugger know this function's boundaries # We let our debugger know this function's boundaries
self.chunk.functions.add(self.chunk.code.high().toTriple()) self.chunk.functions.add(self.chunk.code.len().toTriple())
self.functions.add((start: self.chunk.code.high(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name)) self.functions.add((start: self.chunk.code.len(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
var offset = self.functions[^1] var offset = self.functions[^1]
let idx = self.chunk.functions.len() let idx = self.chunk.functions.len()
self.chunk.functions.add(0.toTriple()) # Patched it later 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). # Restores the enclosing function (if any).
# Makes nested calls work (including recursion) # Makes nested calls work (including recursion)
self.currentFunction = function self.currentFunction = function
self.stackIndex = stackIdx
proc declaration(self: Compiler, node: Declaration) = proc declaration(self: Compiler, node: Declaration) =
## Handles all declarations. They are not compiled ## Compiles declarations, statements and expressions
## right away, but rather only when they're referenced ## recursively
## the first time
case node.kind: case node.kind:
of NodeKind.funDecl: of NodeKind.funDecl:
var name = self.declare(node) var name = self.declare(node)
self.funDecl(FunDecl(node), name)
if name.isGeneric: if name.isGeneric:
# We typecheck generics immediately
name.resolved = true
self.funDecl(FunDecl(node), name)
# After we're done compiling a generic # After we're done compiling a generic
# function, we pull a magic trick: since, # function, we pull a magic trick: since,
# from here on, the user will be able to # from here on, the user will be able to
@ -2757,7 +2679,7 @@ proc declaration(self: Compiler, node: Declaration) =
continue continue
else: else:
argument.kind.asUnion = true 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 name.valueType.returnType.asUnion = true
of NodeKind.typeDecl: of NodeKind.typeDecl:
self.declare(node) self.declare(node)
@ -2790,6 +2712,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
self.disabledWarnings = disabledWarnings self.disabledWarnings = disabledWarnings
self.showMismatches = showMismatches self.showMismatches = showMismatches
self.mode = mode self.mode = mode
self.stackIndex = 1
if not incremental: if not incremental:
self.jumps = @[] self.jumps = @[]
let pos = self.beginProgram() 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 # 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 # errors accurately even deep in the compilation pipeline
token*: Token token*: Token
file*: string
# This weird inheritance chain is needed for the parser to # This weird inheritance chain is needed for the parser to
# work properly # work properly
Declaration* = ref object of ASTNode Declaration* = ref object of ASTNode

View File

@ -122,6 +122,18 @@ type
LessThan, LessThan,
GreaterOrEqual, GreaterOrEqual,
LessOrEqual, LessOrEqual,
SignedGreaterThan,
SignedLessThan,
SignedGreaterOrEqual,
SignedLessOrEqual,
Float64GreaterThan,
Float64LessThan,
Float64GreaterOrEqual,
Float64LessOrEqual,
Float32GreaterThan,
Float32LessThan,
Float32GreaterOrEqual,
Float32LessOrEqual,
LogicalNot, LogicalNot,
## Print opcodes ## Print opcodes
PrintInt64, PrintInt64,
@ -148,9 +160,6 @@ type
LoadVar, # Pushes the object at position x in the stack onto the stack 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 StoreVar, # Stores the value of b at position a in the stack
AddVar, # An optimization for StoreVar (used when the variable is first declared) 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 ## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode Jump, # Absolute, unconditional jump into the bytecode
JumpForwards, # Relative, unconditional, positive jump in 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) JumpIfFalseOrPop, # Jumps to an absolute index in the bytecode if x is false and pops otherwise (used for logical and)
## Functions ## Functions
Call, # Calls a function and initiates a new stack frame Call, # Calls a function and initiates a new stack frame
CallClosure, # Calls a closure
Return, # Terminates the current function Return, # Terminates the current function
SetResult, # Sets the result of the current function SetResult, # Sets the result of the current function
## Exception handling ## Exception handling
@ -178,7 +186,7 @@ type
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 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 # We group instructions by their operation/operand types for easier handling when debugging
@ -244,8 +252,19 @@ const simpleInstructions* = {Return, LoadNil,
PrintString, PrintString,
LogicalNot, LogicalNot,
AddVar, 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 # 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 # Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers # 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 # Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers # of 16 bit integers

View File

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

View File

@ -315,17 +315,23 @@ proc primary(self: Parser): Expression =
case self.peek().kind: case self.peek().kind:
of True: of True:
result = newTrueExpr(self.step()) result = newTrueExpr(self.step())
result.file = self.file
of False: of False:
result = newFalseExpr(self.step()) result = newFalseExpr(self.step())
result.file = self.file
of Float: of Float:
result = newFloatExpr(self.step()) result = newFloatExpr(self.step())
result.file = self.file
of Integer: of Integer:
result = newIntExpr(self.step()) result = newIntExpr(self.step())
result.file = self.file
of Identifier: of Identifier:
result = newIdentExpr(self.step(), self.scopeDepth) result = newIdentExpr(self.step(), self.scopeDepth)
result.file = self.file
of LeftParen: of LeftParen:
let tok = self.step() let tok = self.step()
result = newGroupingExpr(self.expression(), tok) result = newGroupingExpr(self.expression(), tok)
result.file = self.file
self.expect(RightParen, "unterminated parenthesized expression") self.expect(RightParen, "unterminated parenthesized expression")
of Yield: of Yield:
let tok = self.step() let tok = self.step()
@ -340,6 +346,7 @@ proc primary(self: Parser): Expression =
else: else:
# Empty yield # Empty yield
result = newYieldExpr(nil, tok) result = newYieldExpr(nil, tok)
result.file = self.file
of Await: of Await:
let tok = self.step() let tok = self.step()
if self.currentFunction.isNil(): if self.currentFunction.isNil():
@ -347,6 +354,7 @@ proc primary(self: Parser): Expression =
if self.currentFunction.token.kind != Coroutine: if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines", tok) self.error("'await' can only be used inside coroutines", tok)
result = newAwaitExpr(self.expression(), tok) result = newAwaitExpr(self.expression(), tok)
result.file = self.file
of RightParen, RightBracket, RightBrace: of RightParen, RightBracket, RightBrace:
# This is *technically* unnecessary: the parser would # This is *technically* unnecessary: the parser would
# throw an error regardless, but it's a little bit nicer # 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}'") self.error(&"unmatched '{self.peek().lexeme}'")
of Hex: of Hex:
result = newHexExpr(self.step()) result = newHexExpr(self.step())
result.file = self.file
of Octal: of Octal:
result = newOctExpr(self.step()) result = newOctExpr(self.step())
result.file = self.file
of Binary: of Binary:
result = newBinExpr(self.step()) result = newBinExpr(self.step())
result.file = self.file
of String: of String:
result = newStrExpr(self.step()) result = newStrExpr(self.step())
result.file = self.file
of Function: of Function:
discard self.step() discard self.step()
result = Expression(self.funDecl(isLambda=true)) result = Expression(self.funDecl(isLambda=true))
result.file = self.file
of Coroutine: of Coroutine:
discard self.step() discard self.step()
result = Expression(self.funDecl(isAsync=true, isLambda=true)) result = Expression(self.funDecl(isAsync=true, isLambda=true))
result.file = self.file
of Generator: of Generator:
discard self.step() discard self.step()
result = Expression(self.funDecl(isGenerator=true, isLambda=true)) result = Expression(self.funDecl(isGenerator=true, isLambda=true))
result.file = self.file
of TokenType.Ref: of TokenType.Ref:
discard self.step() discard self.step()
result = newRefExpr(self.expression(), self.peek(-1)) result = newRefExpr(self.expression(), self.peek(-1))
result.file = self.file
of TokenType.Ptr: of TokenType.Ptr:
discard self.step() discard self.step()
result = newPtrExpr(self.expression(), self.peek(-1)) result = newPtrExpr(self.expression(), self.peek(-1))
result.file = self.file
else: else:
self.error("invalid syntax") self.error("invalid syntax")
@ -409,6 +426,7 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
argCount += 1 argCount += 1
self.expect(RightParen) self.expect(RightParen)
result = newCallExpr(callee, arguments, tok) result = newCallExpr(callee, arguments, tok)
result.file = self.file
result.closeParen = self.peek(-1) result.closeParen = self.peek(-1)
@ -428,6 +446,7 @@ proc call(self: Parser): Expression =
elif self.match(Dot): elif self.match(Dot):
self.expect(Identifier, "expecting attribute name after '.'") self.expect(Identifier, "expecting attribute name after '.'")
result = newGetItemExpr(result, newIdentExpr(self.peek(-1), self.scopeDepth), self.peek(-1)) result = newGetItemExpr(result, newIdentExpr(self.peek(-1), self.scopeDepth), self.peek(-1))
result.file = self.file
elif self.match(LeftBracket): elif self.match(LeftBracket):
self.parseGenericArgs() # TODO self.parseGenericArgs() # TODO
result = self.makeCall(result) result = self.makeCall(result)
@ -440,6 +459,7 @@ proc unary(self: Parser): Expression =
## Parses unary expressions ## Parses unary expressions
if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens: if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens:
result = newUnaryExpr(self.step(), self.unary()) result = newUnaryExpr(self.step(), self.unary())
result.file = self.file
else: else:
result = self.call() result = self.call()
@ -453,6 +473,7 @@ proc parsePow(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.unary() right = self.unary()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseMul(self: Parser): Expression = proc parseMul(self: Parser): Expression =
@ -465,6 +486,7 @@ proc parseMul(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parsePow() right = self.parsePow()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAdd(self: Parser): Expression = proc parseAdd(self: Parser): Expression =
@ -477,6 +499,7 @@ proc parseAdd(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseMul() right = self.parseMul()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseBitwise(self: Parser): Expression = proc parseBitwise(self: Parser): Expression =
@ -488,6 +511,7 @@ proc parseBitwise(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseAdd() right = self.parseAdd()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseCmp(self: Parser): Expression = proc parseCmp(self: Parser): Expression =
@ -499,6 +523,7 @@ proc parseCmp(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseAdd() right = self.parseAdd()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAnd(self: Parser): Expression = proc parseAnd(self: Parser): Expression =
@ -510,6 +535,7 @@ proc parseAnd(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseCmp() right = self.parseCmp()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseOr(self: Parser): Expression = proc parseOr(self: Parser): Expression =
@ -521,6 +547,7 @@ proc parseOr(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseAnd() right = self.parseAnd()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
proc parseAssign(self: Parser): Expression = proc parseAssign(self: Parser): Expression =
@ -532,8 +559,10 @@ proc parseAssign(self: Parser): Expression =
case result.kind: case result.kind:
of identExpr, sliceExpr: of identExpr, sliceExpr:
result = newAssignExpr(result, value, tok) result = newAssignExpr(result, value, tok)
result.file = self.file
of getItemExpr: of getItemExpr:
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok) result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
result.file = self.file
else: else:
self.error("invalid assignment target", tok) self.error("invalid assignment target", tok)
@ -547,6 +576,7 @@ proc parseArrow(self: Parser): Expression =
operator = self.step() operator = self.step()
right = self.parseAssign() right = self.parseAssign()
result = newBinaryExpr(result, operator, right) result = newBinaryExpr(result, operator, right)
result.file = self.file
## End of operator parsing handlers ## End of operator parsing handlers
@ -560,6 +590,7 @@ proc assertStmt(self: Parser): Statement =
var expression = self.expression() var expression = self.expression()
endOfLine("missing semicolon after 'assert'") endOfLine("missing semicolon after 'assert'")
result = newAssertStmt(expression, tok) result = newAssertStmt(expression, tok)
result.file = self.file
proc beginScope(self: Parser) = proc beginScope(self: Parser) =
@ -585,6 +616,7 @@ proc blockStmt(self: Parser): Statement =
code.delete(code.high()) code.delete(code.high())
self.expect(RightBrace, "expecting '}'") self.expect(RightBrace, "expecting '}'")
result = newBlockStmt(code, tok) result = newBlockStmt(code, tok)
result.file = self.file
self.endScope() self.endScope()
@ -595,6 +627,7 @@ proc breakStmt(self: Parser): Statement =
self.error("'break' cannot be used outside loops") self.error("'break' cannot be used outside loops")
endOfLine("missing semicolon after 'break'") endOfLine("missing semicolon after 'break'")
result = newBreakStmt(tok) result = newBreakStmt(tok)
result.file = self.file
proc deferStmt(self: Parser): Statement = proc deferStmt(self: Parser): Statement =
@ -604,6 +637,7 @@ proc deferStmt(self: Parser): Statement =
self.error("'defer' cannot be used outside functions") self.error("'defer' cannot be used outside functions")
endOfLine("missing semicolon after 'defer'") endOfLine("missing semicolon after 'defer'")
result = newDeferStmt(self.expression(), tok) result = newDeferStmt(self.expression(), tok)
result.file = self.file
proc continueStmt(self: Parser): Statement = proc continueStmt(self: Parser): Statement =
@ -613,6 +647,7 @@ proc continueStmt(self: Parser): Statement =
self.error("'continue' cannot be used outside loops") self.error("'continue' cannot be used outside loops")
endOfLine("missing semicolon after 'continue'") endOfLine("missing semicolon after 'continue'")
result = newContinueStmt(tok) result = newContinueStmt(tok)
result.file = self.file
proc returnStmt(self: Parser): Statement = proc returnStmt(self: Parser): Statement =
@ -628,6 +663,7 @@ proc returnStmt(self: Parser): Statement =
value = self.expression() value = self.expression()
endOfLine("missing semicolon after 'return'") endOfLine("missing semicolon after 'return'")
result = newReturnStmt(value, tok) result = newReturnStmt(value, tok)
result.file = self.file
case self.currentFunction.kind: case self.currentFunction.kind:
of NodeKind.funDecl: of NodeKind.funDecl:
FunDecl(self.currentFunction).hasExplicitReturn = true FunDecl(self.currentFunction).hasExplicitReturn = true
@ -646,6 +682,7 @@ proc yieldStmt(self: Parser): Statement =
result = newYieldStmt(self.expression(), tok) result = newYieldStmt(self.expression(), tok)
else: else:
result = newYieldStmt(nil, tok) result = newYieldStmt(nil, tok)
result.file = self.file
endOfLine("missing semicolon after 'yield'") endOfLine("missing semicolon after 'yield'")
@ -658,6 +695,7 @@ proc awaitStmt(self: Parser): Statement =
self.error("'await' can only be used inside coroutines") self.error("'await' can only be used inside coroutines")
endOfLine("missing semicolon after 'await'") endOfLine("missing semicolon after 'await'")
result = newAwaitStmt(self.expression(), tok) result = newAwaitStmt(self.expression(), tok)
result.file = self.file
proc raiseStmt(self: Parser): Statement = proc raiseStmt(self: Parser): Statement =
@ -670,6 +708,7 @@ proc raiseStmt(self: Parser): Statement =
exception = self.expression() exception = self.expression()
endOfLine("missing semicolon after 'raise'") endOfLine("missing semicolon after 'raise'")
result = newRaiseStmt(exception, tok) result = newRaiseStmt(exception, tok)
result.file = self.file
proc forEachStmt(self: Parser): Statement = proc forEachStmt(self: Parser): Statement =
@ -683,6 +722,7 @@ proc forEachStmt(self: Parser): Statement =
let expression = self.expression() let expression = self.expression()
self.expect(LeftBrace) self.expect(LeftBrace)
result = newForEachStmt(identifier, expression, self.blockStmt(), tok) result = newForEachStmt(identifier, expression, self.blockStmt(), tok)
result.file = self.file
self.currentLoop = enclosingLoop 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)), pos: (tok.pos.stop + 1, (tok.pos.stop + 1) + len(moduleName)),
relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName))), relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName))),
self.scopeDepth), tok) self.scopeDepth), tok)
result.file = self.file
moduleName &= ".pn" moduleName &= ".pn"
var lexer = newLexer() var lexer = newLexer()
lexer.fillSymbolTable() lexer.fillSymbolTable()
@ -778,6 +819,7 @@ proc tryStmt(self: Parser): Statement =
if handler.exc.isNil() and i != handlers.high(): 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) 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 = newTryStmt(body, handlers, finallyClause, elseClause, tok)
result.file = self.file
proc whileStmt(self: Parser): Statement = proc whileStmt(self: Parser): Statement =
@ -789,6 +831,7 @@ proc whileStmt(self: Parser): Statement =
self.expect(LeftBrace) self.expect(LeftBrace)
self.currentLoop = Loop self.currentLoop = Loop
result = newWhileStmt(condition, self.blockStmt(), tok) result = newWhileStmt(condition, self.blockStmt(), tok)
result.file = self.file
self.currentLoop = enclosingLoop self.currentLoop = enclosingLoop
self.endScope() self.endScope()
@ -807,6 +850,7 @@ proc ifStmt(self: Parser): Statement =
self.expect(LeftBrace, "expecting 'if' or block statement") self.expect(LeftBrace, "expecting 'if' or block statement")
elseBranch = self.blockStmt() elseBranch = self.blockStmt()
result = newIfStmt(condition, thenBranch, elseBranch, tok) result = newIfStmt(condition, thenBranch, elseBranch, tok)
result.file = self.file
proc exportStmt(self: Parser): Statement = proc exportStmt(self: Parser): Statement =
@ -818,6 +862,7 @@ proc exportStmt(self: Parser): Statement =
exported = newIdentExpr(self.peek(-1)) exported = newIdentExpr(self.peek(-1))
endOfLine("missing semicolon after 'raise'") endOfLine("missing semicolon after 'raise'")
result = newExportStmt(exported, tok) result = newExportStmt(exported, tok)
result.file = self.file
template checkDecl(self: Parser, isPrivate: bool) = template checkDecl(self: Parser, isPrivate: bool) =
@ -841,6 +886,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
self.error("duplicate pragmas are not allowed") self.error("duplicate pragmas are not allowed")
names.add(self.peek(-1).lexeme) names.add(self.peek(-1).lexeme)
name = newIdentExpr(self.peek(-1), self.scopeDepth) name = newIdentExpr(self.peek(-1), self.scopeDepth)
name.file = self.file
if not self.match(":"): if not self.match(":"):
if self.match("]"): if self.match("]"):
result.add(newPragma(name, @[])) result.add(newPragma(name, @[]))
@ -862,6 +908,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
self.error("pragma arguments can only be literals", exp.token) self.error("pragma arguments can only be literals", exp.token)
args.add(LiteralExpr(exp)) args.add(LiteralExpr(exp))
result.add(newPragma(name, args)) result.add(newPragma(name, args))
result[^1].file = self.file
if self.match(","): if self.match(","):
continue continue
@ -913,6 +960,7 @@ proc varDecl(self: Parser, isLet: bool = false,
if not hasInit and VarDecl(result).valueType.isNil(): if not hasInit and VarDecl(result).valueType.isNil():
self.error("expecting initializer or explicit type annotation, but neither was found", result.token) self.error("expecting initializer or explicit type annotation, but neither was found", result.token)
result.pragmas = pragmas result.pragmas = pragmas
result.file = self.file
proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr, valueType: Expression]], 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.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
self.expect(Identifier, "expecting parameter name") self.expect(Identifier, "expecting parameter name")
parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth) parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth)
parameter.name.file = self.file
if self.match(":"): if self.match(":"):
parameter.valueType = self.expression() parameter.valueType = self.expression()
for i in countdown(arguments.high(), 0): for i in countdown(arguments.high(), 0):
@ -966,6 +1015,7 @@ proc parseFunExpr(self: Parser): LambdaExpr =
result.returnType = self.expression() result.returnType = self.expression()
result.arguments = arguments result.arguments = arguments
result.defaults = defaults result.defaults = defaults
result.file = self.file
proc parseGenericConstraint(self: Parser): Expression = proc parseGenericConstraint(self: Parser): Expression =
@ -976,8 +1026,10 @@ proc parseGenericConstraint(self: Parser): Expression =
case self.peek().lexeme: case self.peek().lexeme:
of "|": of "|":
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint()) result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
result.file = self.file
of "~": of "~":
result = newUnaryExpr(self.step(), result) result = newUnaryExpr(self.step(), result)
result.file = self.file
of ",": of ",":
discard # Comma is handled in parseGenerics() discard # Comma is handled in parseGenerics()
else: else:
@ -990,6 +1042,7 @@ proc parseGenerics(self: Parser, decl: Declaration) =
while not self.check(RightBracket) and not self.done(): while not self.check(RightBracket) and not self.done():
self.expect(Identifier, "expecting generic type name") self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth) gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
self.expect(":", "expecting type constraint after generic name") self.expect(":", "expecting type constraint after generic name")
gen.cond = self.parseGenericConstraint() gen.cond = self.parseGenericConstraint()
decl.generics.add(gen) 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.error(&"missing type declaration for '{argument.name.token.lexeme}' in function declaration")
self.currentFunction = enclosingFunction self.currentFunction = enclosingFunction
result.pragmas = pragmas result.pragmas = pragmas
result.file = self.file
proc expression(self: Parser): Expression = proc expression(self: Parser): Expression =
## Parses expressions ## Parses expressions
result = self.parseArrow() # Highest-level expression result = self.parseArrow() # Highest-level expression
result.file = self.file
proc expressionStatement(self: Parser): Statement = proc expressionStatement(self: Parser): Statement =
@ -1114,6 +1169,7 @@ proc expressionStatement(self: Parser): Statement =
var expression = self.expression() var expression = self.expression()
endOfLine("missing semicolon at end of expression", expression.token) endOfLine("missing semicolon at end of expression", expression.token)
result = Statement(newExprStmt(expression, expression.token)) result = Statement(newExprStmt(expression, expression.token))
result.file = self.file
proc statement(self: Parser): Statement = proc statement(self: Parser): Statement =
@ -1171,6 +1227,7 @@ proc statement(self: Parser): Statement =
result = self.tryStmt() result = self.tryStmt()
else: else:
result = self.expressionStatement() result = self.expressionStatement()
result.file = self.file
proc typeDecl(self: Parser): TypeDecl = proc typeDecl(self: Parser): TypeDecl =
@ -1241,6 +1298,7 @@ proc typeDecl(self: Parser): TypeDecl =
if not self.check(RightBrace): if not self.check(RightBrace):
self.expect(",", "expecting comma after enum field declaration") self.expect(",", "expecting comma after enum field declaration")
result.pragmas = pragmas result.pragmas = pragmas
result.file = self.file
proc declaration(self: Parser): Declaration = 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, 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 var
tokens: seq[Token] = @[] tokens: seq[Token] = @[]
tree: seq[Declaration] = @[] tree: seq[Declaration] = @[]
@ -237,7 +237,8 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
styledEcho fgGreen, "OK" styledEcho fgGreen, "OK"
else: else:
styledEcho fgRed, "Corrupted" styledEcho fgRed, "Corrupted"
vm.run(serialized.chunk, breakpoints) if run:
vm.run(serialized.chunk, breakpoints)
except LexingError: except LexingError:
print(LexingError(getCurrentException())) print(LexingError(getCurrentException()))
except ParseError: except ParseError:
@ -264,6 +265,7 @@ when isMainModule:
var dis: bool = false var dis: bool = false
var mismatches: bool = false var mismatches: bool = false
var mode: CompileMode = CompileMode.Debug var mode: CompileMode = CompileMode.Debug
var run: bool = true
for kind, key, value in optParser.getopt(): for kind, key, value in optParser.getopt():
case kind: case kind:
of cmdArgument: of cmdArgument:
@ -323,6 +325,8 @@ when isMainModule:
quit() quit()
of "disassemble": of "disassemble":
dis = true dis = true
of "compile":
run = false
else: else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'") stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'")
quit() quit()
@ -349,6 +353,8 @@ when isMainModule:
except ValueError: except ValueError:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'") stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
quit() quit()
of "c":
run = false
of "d": of "d":
dis = true dis = true
else: else:
@ -360,7 +366,7 @@ when isMainModule:
if file == "": if file == "":
repl() repl()
else: 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; import values;
operator `>`*[T: Number](a, b: T): bool { operator `>`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "GreaterThan", pure] #pragma[magic: "GreaterThan", pure]
} }
operator `<`*[T: Number](a, b: T): bool { operator `<`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "LessThan", pure] #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] #pragma[magic: "GreaterOrEqual", pure]
} }
operator `<=`*[T: Number](a, b: T): bool { operator `<=`*[T: UnsignedInteger](a, b: T): bool {
#pragma[magic: "LessOrEqual", pure] #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; import values;
# Some useful builtins
fn clock*: float { fn clock*: float {
#pragma[magic: "SysClock64"] #pragma[magic: "SysClock64"]
} }

View File

@ -7,10 +7,31 @@ const e* = 2.718281828459045;
const tau* = 6.283185307179586; const tau* = 6.283185307179586;
fn abs*[T: int64 | int32 | int16 | int8](n: T): T { fn abs*[T: SignedInteger](n: T): T {
## Returns the absolute value of the given number ## Returns the absolute value of
## the given number
if n < 0 { if n < 0 {
return -n; return -n;
} }
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 misc;
export comparisons; export comparisons;
var version* = 1; var version* = 1;
var _private = 5; # Invisible outside the module (underscore is to silence warning) var _private = 5; # Invisible outside the module (underscore is to silence warning)
var test* = 0x60; var test* = 0x60;

View File

@ -132,15 +132,6 @@ proc argumentTripleInstruction(self: Debugger, instruction: OpCode) {.used.} =
self.current += 4 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) = proc callInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls ## Debugs function calls
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() 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 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) = proc constantInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on the constant table ## Debugs instructions that operate on the constant table
var size: uint var size: uint
@ -190,7 +170,7 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
inc(self.current) inc(self.current)
for i in countup(orig, self.current + 1): for i in countup(orig, self.current + 1):
self.checkFunctionStart(i) self.checkFunctionStart(i)
proc disassembleInstruction*(self: Debugger) = proc disassembleInstruction*(self: Debugger) =
## Takes one bytecode instruction and prints it ## Takes one bytecode instruction and prints it
@ -210,18 +190,14 @@ proc disassembleInstruction*(self: Debugger) =
self.stackTripleInstruction(opcode) self.stackTripleInstruction(opcode)
of argumentDoubleInstructions: of argumentDoubleInstructions:
self.argumentDoubleInstruction(opcode) self.argumentDoubleInstruction(opcode)
of StoreClosure:
self.storeClosureInstruction(opcode)
of Call: of Call:
self.callInstruction(opcode) self.callInstruction(opcode)
of CallClosure:
self.callClosureInstruction(opcode)
of jumpInstructions: of jumpInstructions:
self.jumpInstruction(opcode) self.jumpInstruction(opcode)
else: else:
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}" echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
self.current += 1 self.current += 1
proc parseFunctions(self: Debugger) = proc parseFunctions(self: Debugger) =
## Parses function information in the chunk ## Parses function information in the chunk

View File

@ -44,8 +44,8 @@ proc print*(exc: CompileError) =
var file = exc.file var file = exc.file
if file notin ["<string>", ""]: if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir()) file = relativePath(exc.file, getCurrentDir())
printError(file, exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}), printError(file, readFile(file).splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.node.getRelativeBoundaries(), exc.compiler.getCurrentFunction(), exc.line, exc.node.getRelativeBoundaries(), exc.function,
exc.msg) 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; fn foo: int;
print(foo()); print(foo() == 42);
fn foo: int {return 42;} # Commenting this line will cause an error 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; 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 = 55;
{ {
var x = 22; var x = 22;
print(x); print(x == 22);
} }
print(x); print(x == 55);
} }
print(x); print(x == 5);