Removed broken closure implementation, changed local resolution mechanism, many bug fixes to namespaces and tests
This commit is contained in:
parent
142e575497
commit
21738b9382
|
@ -92,8 +92,6 @@ type
|
|||
operands: seq[uint64] # The operand stack
|
||||
cache: array[6, uint64] # The singletons cache
|
||||
frames: seq[uint64] # Stores the bottom of stack frames
|
||||
closures: seq[uint64] # Stores closure environment offsets
|
||||
envs: seq[uint64] # Stores closure variables
|
||||
results: seq[uint64] # Stores function return values
|
||||
gc: PeonGC # A reference to the VM's garbage collector
|
||||
breakpoints: seq[uint64] # Breakpoints where we call our debugger
|
||||
|
@ -231,9 +229,6 @@ proc markRoots(self: var PeonVM): seq[ptr HeapObject] =
|
|||
for obj in self.operands:
|
||||
if obj in self.gc.pointers:
|
||||
live.incl(obj)
|
||||
for obj in self.envs:
|
||||
if obj in self.gc.pointers:
|
||||
live.incl(obj)
|
||||
# We preallocate the space on the seq
|
||||
result = newSeqOfCap[ptr HeapObject](len(live))
|
||||
var obj: ptr HeapObject
|
||||
|
@ -368,7 +363,6 @@ proc newPeonVM*: PeonVM =
|
|||
result.frames = @[]
|
||||
result.operands = @[]
|
||||
result.results = @[]
|
||||
result.envs = @[]
|
||||
result.calls = @[]
|
||||
|
||||
|
||||
|
@ -470,30 +464,6 @@ proc setc(self: var PeonVM, idx: int, val: uint64) =
|
|||
self.calls[idx.uint + self.frames[^1]] = val
|
||||
|
||||
|
||||
proc getClosure(self: PeonVM, idx: int): uint64 =
|
||||
## Getter method that abstracts
|
||||
## indexing closure environments
|
||||
return self.envs[idx.uint + self.closures[^1]]
|
||||
|
||||
|
||||
proc setClosure(self: var PeonVM, idx: int, val: uint64) =
|
||||
## Setter method that abstracts
|
||||
## indexing closure environments
|
||||
if idx == self.envs.len():
|
||||
self.envs.add(val)
|
||||
else:
|
||||
self.envs[idx.uint + self.closures[^1]] = val
|
||||
|
||||
|
||||
proc popClosure(self: var PeonVM, idx: int): uint64 =
|
||||
## Pop method that abstracts
|
||||
## popping values off closure
|
||||
## environments
|
||||
var idx = idx.uint + self.closures[^1]
|
||||
result = self.envs[idx]
|
||||
self.envs.delete(idx)
|
||||
|
||||
|
||||
# Byte-level primitives to read and decode
|
||||
# bytecode
|
||||
|
||||
|
@ -700,20 +670,6 @@ when debugVM: # So nim shuts up
|
|||
if i < self.frames.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "cl", "closures":
|
||||
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "e", "env", "environments":
|
||||
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
||||
for i, e in self.envs:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.envs.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "r", "results":
|
||||
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
||||
for i, e in self.results:
|
||||
|
@ -769,9 +725,6 @@ proc dispatch*(self: var PeonVM) =
|
|||
of LoadString:
|
||||
# Loads the string's pointer onto the stack
|
||||
self.push(cast[uint64](self.constReadString(int(self.readLong()), int(self.readLong()))))
|
||||
# We cast instead of converting because, unlike with integers,
|
||||
# we don't want nim to touch any of the bits of the underlying
|
||||
# value!
|
||||
of LoadFloat32:
|
||||
self.push(cast[uint64](self.constReadFloat32(int(self.readLong()))))
|
||||
of LoadFloat64:
|
||||
|
@ -804,32 +757,6 @@ proc dispatch*(self: var PeonVM) =
|
|||
# not needed there anymore
|
||||
discard self.pop()
|
||||
discard self.pop()
|
||||
of CallClosure:
|
||||
# Calls a peon closure. The code here is
|
||||
# mostly identical to the one for Call,
|
||||
# but we also create a new environment
|
||||
# containing the function's closed-over variables
|
||||
let argc = self.readLong().int
|
||||
let offset = self.readLong().uint64
|
||||
let retAddr = self.peek(-argc - 1) # Return address
|
||||
let jmpAddr = self.peek(-argc - 2) # Function address
|
||||
self.ip = jmpAddr
|
||||
self.pushc(jmpAddr)
|
||||
self.pushc(retAddr)
|
||||
# Creates a new result slot for the
|
||||
# function's return value
|
||||
self.results.add(self.getNil())
|
||||
# Creates a new call frame
|
||||
self.frames.add(uint64(self.calls.len() - 2))
|
||||
self.closures.add(offset - 1)
|
||||
# Loads the arguments onto the stack
|
||||
for _ in 0..<argc:
|
||||
self.pushc(self.pop())
|
||||
# Pops the function and return address
|
||||
# off the operand stack since they're
|
||||
# not needed there anymore
|
||||
discard self.pop()
|
||||
discard self.pop()
|
||||
of Return:
|
||||
# Returns from a function.
|
||||
# Every peon program is wrapped
|
||||
|
@ -890,25 +817,6 @@ proc dispatch*(self: var PeonVM) =
|
|||
# condition in the VM's bytecode dispatch loop (which is
|
||||
# not a great idea)
|
||||
self.pushc(self.pop())
|
||||
of LoadClosure:
|
||||
# Loads a closed-over variable from the current
|
||||
# environment onto the operand stack
|
||||
self.push(self.getClosure(self.readLong().int))
|
||||
of StoreClosure:
|
||||
# Updates the value of a closed-over
|
||||
# variable
|
||||
let item = self.getc(self.readLong().int)
|
||||
self.setClosure(self.readLong().int, item)
|
||||
of LoadTos:
|
||||
# Copies the top of the call stack
|
||||
# (TOS: Top of Stack) onto the
|
||||
# operand stack
|
||||
self.push(self.peekc())
|
||||
of AddClosure:
|
||||
# Stores the value at the top of the
|
||||
# operand stack in the topmost closure
|
||||
# environment
|
||||
self.envs.add(self.pop())
|
||||
of LoadVar:
|
||||
# Pushes a variable from the call stack
|
||||
# onto the operand stack
|
||||
|
@ -987,7 +895,7 @@ proc dispatch*(self: var PeonVM) =
|
|||
# types, we don't need specialized instructions
|
||||
# to operate on them
|
||||
of Negate:
|
||||
self.push(cast[uint64](-int64(self.pop())))
|
||||
self.push(cast[uint64](-(cast[int64](self.pop()))))
|
||||
of NegateFloat64:
|
||||
self.push(cast[uint64](-cast[float](self.pop())))
|
||||
of NegateFloat32:
|
||||
|
@ -1001,7 +909,7 @@ proc dispatch*(self: var PeonVM) =
|
|||
of Divide:
|
||||
self.push(self.pop() div self.pop())
|
||||
of SignedDivide:
|
||||
self.push(uint64(int64(self.pop()) div int64(self.pop())))
|
||||
self.push(uint64(cast[int64](self.pop()) div cast[int64](self.pop())))
|
||||
of AddFloat64:
|
||||
self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop())))
|
||||
of SubtractFloat64:
|
||||
|
@ -1021,7 +929,7 @@ proc dispatch*(self: var PeonVM) =
|
|||
of Pow:
|
||||
self.push(uint64(self.pop() ^ self.pop()))
|
||||
of SignedPow:
|
||||
self.push(uint64(int64(self.pop()) ^ int64(self.pop())))
|
||||
self.push(uint64(cast[int64](self.pop()) ^ cast[int64](self.pop())))
|
||||
of PowFloat64:
|
||||
self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
|
||||
of PowFloat32:
|
||||
|
@ -1029,7 +937,7 @@ proc dispatch*(self: var PeonVM) =
|
|||
of Mod:
|
||||
self.push(uint64(self.pop() mod self.pop()))
|
||||
of SignedMod:
|
||||
self.push(uint64(int64(self.pop()) mod int64(self.pop())))
|
||||
self.push(uint64(cast[int64](self.pop()) mod cast[int64](self.pop())))
|
||||
of ModFloat64:
|
||||
self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
|
||||
of ModFloat32:
|
||||
|
@ -1056,7 +964,31 @@ proc dispatch*(self: var PeonVM) =
|
|||
of GreaterOrEqual:
|
||||
self.push(self.getBool(self.pop() !>= self.pop()))
|
||||
of LessOrEqual:
|
||||
self.push(self.getBool(self.pop() <= self.pop()))
|
||||
self.push(self.getBool(cast[int64](self.pop()) <= cast[int64](self.pop())))
|
||||
of SignedGreaterThan:
|
||||
self.push(self.getBool(cast[int64](self.pop()) !> cast[int64](self.pop())))
|
||||
of SignedLessThan:
|
||||
self.push(self.getBool(cast[int64](self.pop()) < cast[int64](self.pop())))
|
||||
of SignedGreaterOrEqual:
|
||||
self.push(self.getBool(cast[int64](self.pop()) !>= cast[int64](self.pop())))
|
||||
of SignedLessOrEqual:
|
||||
self.push(self.getBool(cast[int64](self.pop()) <= cast[int64](self.pop())))
|
||||
of Float64GreaterThan:
|
||||
self.push(self.getBool(cast[float64](self.pop()) !> cast[float64](self.pop())))
|
||||
of Float64LessThan:
|
||||
self.push(self.getBool(cast[float64](self.pop()) < cast[float64](self.pop())))
|
||||
of Float64GreaterOrEqual:
|
||||
self.push(self.getBool(cast[float64](self.pop()) !>= cast[float64](self.pop())))
|
||||
of Float64LessOrEqual:
|
||||
self.push(self.getBool(cast[float64](self.pop()) <= cast[float64](self.pop())))
|
||||
of Float32GreaterThan:
|
||||
self.push(self.getBool(cast[float32](self.pop()) !> cast[float32](self.pop())))
|
||||
of Float32LessThan:
|
||||
self.push(self.getBool(cast[float32](self.pop()) < cast[float32](self.pop())))
|
||||
of Float32GreaterOrEqual:
|
||||
self.push(self.getBool(cast[float32](self.pop()) !>= cast[float32](self.pop())))
|
||||
of Float32LessOrEqual:
|
||||
self.push(self.getBool(cast[float32](self.pop()) <= cast[float32](self.pop())))
|
||||
# Print opcodes
|
||||
of PrintInt64:
|
||||
echo cast[int64](self.pop())
|
||||
|
|
|
@ -62,6 +62,7 @@ Options
|
|||
-d, --disassemble Disassemble the given bytecode file instead of executing it
|
||||
-m, --mode Set the compilation mode. Acceptable values are 'debug' and
|
||||
'release'
|
||||
-c, --compile Compile the code, but do not execute it
|
||||
--warnings Turn warnings on/off (default: on). Acceptable values are
|
||||
yes/on and no/off
|
||||
--noWarn Disable a specific warning (for example, --noWarn unusedVariable)
|
||||
|
|
|
@ -59,10 +59,6 @@ type
|
|||
returnType: Type
|
||||
builtinOp: string
|
||||
fun: Declaration
|
||||
isClosure: bool
|
||||
envLen: int
|
||||
children: seq[Type]
|
||||
parent: Type
|
||||
retJumps: seq[int]
|
||||
forwarded: bool
|
||||
location: int
|
||||
|
@ -90,7 +86,8 @@ export bytecode
|
|||
type
|
||||
WarningKind* {.pure.} = enum
|
||||
## A warning enumeration type
|
||||
UnreachableCode, UnusedName, ShadowOuterScope
|
||||
UnreachableCode, UnusedName, ShadowOuterScope,
|
||||
MutateOuterScope
|
||||
CompileMode* {.pure.} = enum
|
||||
## A compilation mode enumeration
|
||||
Debug, Release
|
||||
|
@ -134,8 +131,6 @@ type
|
|||
belongsTo: Name
|
||||
# Where is this node declared in its file?
|
||||
line: int
|
||||
# Has this name been closed over?
|
||||
isClosedOver: bool
|
||||
# Has this name been referenced at least once?
|
||||
resolved: bool
|
||||
# The AST node associated with this node. This
|
||||
|
@ -151,6 +146,12 @@ type
|
|||
isReal: bool
|
||||
# Is this name a builtin?
|
||||
isBuiltin: bool
|
||||
# The location of this name on the stack.
|
||||
# Only makes sense for names that actually
|
||||
# materialize on the call stack at runtime
|
||||
# (except for functions, where we use it to
|
||||
# signal where the function's frame starts)
|
||||
position: int
|
||||
|
||||
Loop = object
|
||||
## A "loop object" used
|
||||
|
@ -243,6 +244,9 @@ type
|
|||
showMismatches: bool
|
||||
# Are we compiling in debug mode?
|
||||
mode: CompileMode
|
||||
# The topmost occupied stack slot
|
||||
# in the current frame (0-indexed)
|
||||
stackIndex: int
|
||||
PragmaKind = enum
|
||||
## An enumeration of pragma types
|
||||
Immediate,
|
||||
|
@ -253,8 +257,8 @@ type
|
|||
kind: PragmaKind
|
||||
handler: proc (self: Compiler, pragma: Pragma, name: Name)
|
||||
CompileError* = ref object of PeonException
|
||||
compiler*: Compiler
|
||||
node*: ASTNode
|
||||
function*: Declaration
|
||||
|
||||
|
||||
# Forward declarations
|
||||
|
@ -268,8 +272,9 @@ proc peek(self: Compiler, distance: int = 0): ASTNode
|
|||
proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Type {.discardable.}
|
||||
proc varDecl(self: Compiler, node: VarDecl)
|
||||
proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name
|
||||
proc specialize(self: Compiler, typ: Type, args: seq[Expression]): Type {.discardable.}
|
||||
proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable.}
|
||||
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.}
|
||||
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.}
|
||||
proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discardable.}
|
||||
proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable.}
|
||||
proc infer(self: Compiler, node: LiteralExpr): Type
|
||||
|
@ -319,6 +324,7 @@ proc newCompiler*(replMode: bool = false): Compiler =
|
|||
result.forwarded = @[]
|
||||
result.disabledWarnings = @[]
|
||||
result.functions = @[]
|
||||
result.stackIndex = 1
|
||||
|
||||
|
||||
## Public getters for nicer error formatting
|
||||
|
@ -330,8 +336,8 @@ proc getSource*(self: Compiler): string = self.source
|
|||
|
||||
## Utility functions
|
||||
|
||||
proc `$`*(self: Name): string = $self[]
|
||||
#proc `$`(self: Type): string = $self[]
|
||||
proc `$`*(self: Name): string = $(self[])
|
||||
proc `$`(self: Type): string = $(self[])
|
||||
proc hash(self: Name): Hash = self.ident.token.lexeme.hash()
|
||||
|
||||
|
||||
|
@ -353,10 +359,10 @@ proc done(self: Compiler): bool {.inline.} =
|
|||
result = self.current > self.ast.high()
|
||||
|
||||
|
||||
proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [CompileError], inline.} =
|
||||
proc error(self: Compiler, message: string, node: ASTNode = nil) {.inline.} =
|
||||
## Raises a CompileError exception
|
||||
let node = if node.isNil(): self.getCurrentNode() else: node
|
||||
raise CompileError(msg: message, node: node, line: node.token.line, file: self.file, compiler: self)
|
||||
raise CompileError(msg: message, node: node, line: node.token.line, file: node.file)
|
||||
|
||||
|
||||
proc warning(self: Compiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
|
||||
|
@ -427,6 +433,43 @@ proc emitBytes(self: Compiler, bytarr: openarray[OpCode | uint8], line: int) {.i
|
|||
self.emitByte(b, line)
|
||||
|
||||
|
||||
|
||||
proc printRepl(self: Compiler, typ: Type, node: Expression) =
|
||||
## Emits instruction to print
|
||||
## peon types in REPL mode
|
||||
case typ.kind:
|
||||
of Int64:
|
||||
self.emitByte(PrintInt64, node.token.line)
|
||||
of UInt64:
|
||||
self.emitByte(PrintUInt64, node.token.line)
|
||||
of Int32:
|
||||
self.emitByte(PrintInt32, node.token.line)
|
||||
of UInt32:
|
||||
self.emitByte(PrintInt32, node.token.line)
|
||||
of Int16:
|
||||
self.emitByte(PrintInt16, node.token.line)
|
||||
of UInt16:
|
||||
self.emitByte(PrintUInt16, node.token.line)
|
||||
of Int8:
|
||||
self.emitByte(PrintInt8, node.token.line)
|
||||
of UInt8:
|
||||
self.emitByte(PrintUInt8, node.token.line)
|
||||
of Float64:
|
||||
self.emitByte(PrintFloat64, node.token.line)
|
||||
of Float32:
|
||||
self.emitByte(PrintFloat32, node.token.line)
|
||||
of Bool:
|
||||
self.emitByte(PrintBool, node.token.line)
|
||||
of Nan:
|
||||
self.emitByte(PrintNan, node.token.line)
|
||||
of Inf:
|
||||
self.emitByte(PrintInf, node.token.line)
|
||||
of String:
|
||||
self.emitByte(PrintString, node.token.line)
|
||||
else:
|
||||
self.emitByte(PrintHex, node.token.line)
|
||||
|
||||
|
||||
proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
|
||||
## Adds a constant to the current chunk's constant table
|
||||
## and returns its index as a 3-byte array of uint8s
|
||||
|
@ -520,18 +563,17 @@ proc patchJump(self: Compiler, offset: int) =
|
|||
## jump using emitJump
|
||||
var jump: int = self.chunk.code.len() - self.jumps[offset].offset
|
||||
if jump < 0:
|
||||
self.error("invalid jump size (< 0), did the bytecode size change without fixJumps being called?")
|
||||
self.error("jump size cannot be negative (This is an internal error and most likely a bug)")
|
||||
if jump > 16777215:
|
||||
# TODO: Emit consecutive jumps?
|
||||
# TODO: Emit consecutive jumps using insertAt
|
||||
self.error("cannot jump more than 16777215 instructions")
|
||||
if jump > 0:
|
||||
self.setJump(self.jumps[offset].offset, (jump - 4).toTriple())
|
||||
self.jumps[offset].patched = true
|
||||
else:
|
||||
# TODO: Discard jump of size 0 and update
|
||||
# bytecode metadata
|
||||
# TODO: Discard jump of size 0
|
||||
discard
|
||||
|
||||
|
||||
|
||||
proc emitJump(self: Compiler, opcode: OpCode, line: int): int =
|
||||
## Emits a dummy jump offset to be patched later
|
||||
|
@ -622,7 +664,9 @@ proc fixNames(self: Compiler, where, oldLen: int) =
|
|||
proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8]): int =
|
||||
## Inserts the given instruction into the
|
||||
## chunk's code segment and updates internal
|
||||
## metadata to reflect this change
|
||||
## metadata to reflect this change. Returns
|
||||
## the new location where the code was added
|
||||
## plus one (useful for consecutive calls)
|
||||
result = where
|
||||
let oldLen = self.chunk.code.len()
|
||||
self.chunk.code.insert(uint8(opcode), where)
|
||||
|
@ -639,31 +683,6 @@ proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8]
|
|||
self.fixFunctionOffsets(oldLen, where)
|
||||
|
||||
|
||||
proc compileDecl(self: Compiler, name: Name) =
|
||||
## Internal resolve() helper
|
||||
|
||||
# There's no reason to compile a declaration
|
||||
# unless it is used at least once: this way
|
||||
# not only do we save space if a name is declared
|
||||
# but never used, it also makes it easier to
|
||||
# implement generics and lets us emit warnings for
|
||||
# unused names once they go out of scope. Yay!
|
||||
if name.resolved:
|
||||
return
|
||||
name.resolved = true
|
||||
if name.isGeneric:
|
||||
# We typecheck generics at declaration time,
|
||||
# so they're already compiled
|
||||
return
|
||||
# Now we just dispatch to one of our functions to
|
||||
# compile the declaration
|
||||
case name.kind:
|
||||
of NameKind.Function:
|
||||
self.funDecl(FunDecl(name.node), name)
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc resolve(self: Compiler, name: string): Name =
|
||||
## Traverses all existing namespaces and returns
|
||||
## the first object with the given name. Returns
|
||||
|
@ -694,10 +713,11 @@ proc resolve(self: Compiler, name: string): Name =
|
|||
# might not want to also have access to C's and D's
|
||||
# names as they might clash with its own stuff)
|
||||
continue
|
||||
if obj.kind == Argument and obj.belongsTo != self.currentFunction:
|
||||
continue
|
||||
result = obj
|
||||
result.resolved = true
|
||||
break
|
||||
if not result.isNil():
|
||||
self.compileDecl(result)
|
||||
|
||||
|
||||
proc resolve(self: Compiler, name: IdentExpr): Name =
|
||||
|
@ -717,63 +737,6 @@ proc resolveOrError[T: IdentExpr | string](self: Compiler, name: T): Name =
|
|||
self.error(&"reference to undefined name '{name}'")
|
||||
|
||||
|
||||
proc getStackPos(self: Compiler, name: Name): int =
|
||||
## Returns the predicted call stack position
|
||||
## of a given name, relative to the current
|
||||
## stack frame
|
||||
var found = false
|
||||
result = 2 # Locals start at frame offset 2
|
||||
for variable in self.names:
|
||||
# Only variables and arguments are actually located on
|
||||
# the stack, so we skip everything else
|
||||
if variable.kind notin [NameKind.Var, NameKind.Argument]:
|
||||
continue
|
||||
# Variable is in a scope above us, so not in frame. Skip it!
|
||||
elif variable.depth < name.depth:
|
||||
continue
|
||||
# Arguments to builtin functions are optimized away to stack
|
||||
# temporaries. There is no stack frame for builtins, so we skip
|
||||
# these names too
|
||||
elif variable.kind == Argument:
|
||||
if variable.belongsTo.isBuiltin:
|
||||
continue
|
||||
elif not variable.belongsTo.resolved:
|
||||
continue
|
||||
# This variable isn't in declared in our module,
|
||||
# but we may still have access to it
|
||||
elif variable.owner != name.owner:
|
||||
# Variable is private in its owner module
|
||||
# or it wasn't exported to us explicitly,
|
||||
# so we just move on
|
||||
if variable.isPrivate or name.owner notin variable.exportedTo:
|
||||
inc(result)
|
||||
continue
|
||||
# Note: this MUST be an if, NOT an elif!
|
||||
if name.ident == variable.ident and variable.depth == name.depth and name.owner == variable.owner:
|
||||
if not name.belongsTo.isNil() and not variable.belongsTo.isNil():
|
||||
if name.belongsTo != variable.belongsTo and variable.belongsTo.depth > name.belongsTo.depth:
|
||||
# Argument with the same name but in a different function: ignore it
|
||||
dec(result)
|
||||
continue
|
||||
found = true
|
||||
variable.resolved = true
|
||||
break
|
||||
inc(result)
|
||||
if not found:
|
||||
result = -1
|
||||
|
||||
|
||||
proc getClosurePos(self: Compiler, name: Name): int =
|
||||
## Returns the position of a name in a closure's
|
||||
## environment
|
||||
if not self.currentFunction.valueType.isClosure:
|
||||
return -1
|
||||
for i, e in self.closures:
|
||||
if e == name:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool =
|
||||
## Compares type unions between each other
|
||||
var
|
||||
|
@ -958,26 +921,22 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
|
|||
case node.kind:
|
||||
of intExpr, binExpr, octExpr, hexExpr:
|
||||
let size = node.token.lexeme.split("'")
|
||||
if len(size) notin 1..2:
|
||||
self.error("invalid state: infer -> invalid size specifier (This is an internal error and most likely a bug!)")
|
||||
if size.len() == 1:
|
||||
return Type(kind: Int64)
|
||||
let typ = size[1].toIntrinsic()
|
||||
if not self.compare(typ, nil):
|
||||
return typ
|
||||
else:
|
||||
self.error(&"invalid type specifier '{size[1]}' for int")
|
||||
self.error(&"invalid type specifier '{size[1]}' for int", node)
|
||||
of floatExpr:
|
||||
let size = node.token.lexeme.split("'")
|
||||
if len(size) notin 1..2:
|
||||
self.error("invalid state: infer -> invalid size specifier (This is an internal error and most likely a bug!)")
|
||||
if size.len() == 1 or size[1] == "f64":
|
||||
if size.len() == 1:
|
||||
return Type(kind: Float64)
|
||||
let typ = size[1].toIntrinsic()
|
||||
if not typ.isNil():
|
||||
return typ
|
||||
else:
|
||||
self.error(&"invalid type specifier '{size[1]}' for float")
|
||||
self.error(&"invalid type specifier '{size[1]}' for float", node)
|
||||
of trueExpr:
|
||||
return Type(kind: Bool)
|
||||
of falseExpr:
|
||||
|
@ -985,7 +944,7 @@ proc infer(self: Compiler, node: LiteralExpr): Type =
|
|||
of strExpr:
|
||||
return Type(kind: String)
|
||||
else:
|
||||
discard # TODO
|
||||
discard # Unreachable
|
||||
|
||||
|
||||
proc infer(self: Compiler, node: Expression): Type =
|
||||
|
@ -1104,12 +1063,17 @@ proc findInModule(self: Compiler, name: string, module: Name): seq[Name] =
|
|||
## Looks for objects that have been already declared as
|
||||
## public within the given module with the given name.
|
||||
## Returns all objects that apply. If the name is an
|
||||
## empty string, returns all objects within the given moule
|
||||
for obj in reversed(self.names):
|
||||
if name != "" and obj.ident.token.lexeme != name:
|
||||
continue
|
||||
if not obj.isPrivate and obj.owner == module:
|
||||
result.add(obj)
|
||||
## empty string, returns all objects within the given
|
||||
## module, regardless of whether they are exported to
|
||||
## the current one or not
|
||||
if name == "":
|
||||
for obj in reversed(self.names):
|
||||
if not obj.isPrivate and obj.owner == module:
|
||||
result.add(obj)
|
||||
else:
|
||||
for obj in self.findInModule("", module):
|
||||
if obj.ident.token.lexeme == name and self.currentModule in obj.exportedTo:
|
||||
result.add(obj)
|
||||
|
||||
|
||||
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] =
|
||||
|
@ -1207,12 +1171,11 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowF
|
|||
msg &= " (compile with --showMismatches for more details)"
|
||||
self.error(msg, node)
|
||||
if impl[0].valueType.forwarded and not allowFwd:
|
||||
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
|
||||
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
|
||||
result = impl[0]
|
||||
for (a, b) in zip(result.valueType.args, kind.args):
|
||||
if not a.kind.isAny() and b.kind.isAny():
|
||||
self.error("any is not a valid type in this context", node)
|
||||
self.compileDecl(result)
|
||||
|
||||
|
||||
proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line: int) =
|
||||
|
@ -1260,13 +1223,25 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line
|
|||
"GreaterThan": GreaterThan,
|
||||
"LessOrEqual": LessOrEqual,
|
||||
"GreaterOrEqual": GreaterOrEqual,
|
||||
"SignedLessThan": SignedLessThan,
|
||||
"SignedGreaterThan": SignedGreaterThan,
|
||||
"SignedLessOrEqual": SignedLessOrEqual,
|
||||
"SignedGreaterOrEqual": SignedGreaterOrEqual,
|
||||
"Float32LessThan": Float32LessThan,
|
||||
"Float32GreaterThan": Float32GreaterThan,
|
||||
"Float32LessOrEqual": Float32LessOrEqual,
|
||||
"Float32GreaterOrEqual": Float32GreaterOrEqual,
|
||||
"Float64LessThan": Float64LessThan,
|
||||
"Float64GreaterThan": Float64GreaterThan,
|
||||
"Float64LessOrEqual": Float64LessOrEqual,
|
||||
"Float64GreaterOrEqual": Float64GreaterOrEqual,
|
||||
"PrintString": PrintString,
|
||||
"SysClock64": SysClock64,
|
||||
"LogicalNot": LogicalNot,
|
||||
"NegInf": LoadNInf
|
||||
}.to_table()
|
||||
if fn.builtinOp == "print":
|
||||
var typ = self.expression(args[0], compile=false)
|
||||
let typ = self.inferOrError(args[0])
|
||||
case typ.kind:
|
||||
of Int64:
|
||||
self.emitByte(PrintInt64, line)
|
||||
|
@ -1297,9 +1272,20 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line
|
|||
of Inf:
|
||||
self.emitByte(PrintInf, line)
|
||||
of Function:
|
||||
self.emitByte(PrintHex, line)
|
||||
self.emitByte(LoadString, line)
|
||||
var loc: string = typ.location.toHex()
|
||||
while loc[0] == '0' and loc.len() > 1:
|
||||
loc = loc[1..^1]
|
||||
var str: string
|
||||
if typ.isLambda:
|
||||
str = &"anonymous function at 0x{loc}"
|
||||
else:
|
||||
str = &"function '{FunDecl(typ.fun).name.token.lexeme}' at 0x{loc}"
|
||||
self.emitBytes(str.len().toTriple(), line)
|
||||
self.emitBytes(self.chunk.writeConstant(str.toBytes()), line)
|
||||
self.emitByte(PrintString, line)
|
||||
else:
|
||||
self.error("invalid type for built-in 'print'", args[0])
|
||||
self.error(&"invalid type {self.stringify(typ)} for built-in 'print'", args[0])
|
||||
return
|
||||
if fn.builtinOp in codes:
|
||||
self.emitByte(codes[fn.builtinOp], line)
|
||||
|
@ -1327,16 +1313,6 @@ proc beginScope(self: Compiler) =
|
|||
inc(self.depth)
|
||||
|
||||
|
||||
# Flattens our weird function tree into a linear
|
||||
# list
|
||||
proc flattenImpl(self: Type, to: var seq[Type]) =
|
||||
to.add(self)
|
||||
for child in self.children:
|
||||
flattenImpl(child, to)
|
||||
|
||||
|
||||
proc flatten(self: Type): seq[Type] = flattenImpl(self, result)
|
||||
|
||||
|
||||
proc patchForwardDeclarations(self: Compiler) =
|
||||
## Patches forward declarations and looks
|
||||
|
@ -1386,7 +1362,7 @@ proc endScope(self: Compiler) =
|
|||
# be referenced anymore, of course)
|
||||
if name.kind notin [NameKind.Var, NameKind.Argument]:
|
||||
continue
|
||||
elif name.kind == NameKind.Argument:
|
||||
elif name.kind == NameKind.Argument and not name.belongsTo.isNil():
|
||||
if name.belongsTo.isBuiltin:
|
||||
# Arguments to builtin functions become temporaries on the
|
||||
# stack and are popped automatically
|
||||
|
@ -1397,6 +1373,7 @@ proc endScope(self: Compiler) =
|
|||
# (it may need them later)
|
||||
names.delete(names.high())
|
||||
continue
|
||||
inc(popCount)
|
||||
if not name.resolved:
|
||||
case name.kind:
|
||||
of NameKind.Var:
|
||||
|
@ -1404,7 +1381,7 @@ proc endScope(self: Compiler) =
|
|||
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
|
||||
of NameKind.Argument:
|
||||
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
|
||||
if not name.belongsTo.isBuiltin and name.belongsTo.isReal:
|
||||
if not name.belongsTo.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal:
|
||||
# Builtin functions never use their arguments. We also don't emit this
|
||||
# warning if the function was generated internally by the compiler (for
|
||||
# example as a result of generic specialization) because such objects do
|
||||
|
@ -1412,7 +1389,7 @@ proc endScope(self: Compiler) =
|
|||
self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
|
||||
else:
|
||||
discard
|
||||
inc(popCount)
|
||||
dec(self.stackIndex, popCount)
|
||||
if popCount > 1:
|
||||
# If we're popping more than one variable,
|
||||
# we emit a bunch of PopN instructions until
|
||||
|
@ -1529,7 +1506,6 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
|
|||
returnType: nil, # We check it later
|
||||
args: @[],
|
||||
fun: node,
|
||||
children: @[],
|
||||
forwarded: node.body.isNil()),
|
||||
ident: node.name,
|
||||
node: node,
|
||||
|
@ -1578,6 +1554,7 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
|
|||
)
|
||||
)
|
||||
n = self.names[^1]
|
||||
declaredName = node.name.token.lexeme
|
||||
if node.value.isNil():
|
||||
discard # TODO: Fields
|
||||
else:
|
||||
|
@ -1603,18 +1580,20 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
|
|||
if name == n:
|
||||
continue
|
||||
# We don't check for name clashes with functions because self.match() does that
|
||||
elif name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
|
||||
if name.owner != self.currentModule:
|
||||
if name.isPrivate:
|
||||
continue
|
||||
elif self.currentModule notin name.exportedTo:
|
||||
continue
|
||||
elif name.depth == 0:
|
||||
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})")
|
||||
elif name.depth == n.depth:
|
||||
self.error(&"re-declaration of {declaredName} is not allowed (previously declared in {name.owner.ident.token.lexeme}:{name.ident.token.line}:{name.ident.token.relPos.start})")
|
||||
elif name.depth < self.depth:
|
||||
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope")
|
||||
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum] and name.depth == n.depth and name.owner == n.owner:
|
||||
self.error(&"re-declaration of {declaredName} is not allowed (previously declared in {name.owner.ident.token.lexeme}:{name.ident.token.line}:{name.ident.token.relPos.start})")
|
||||
for name in self.names:
|
||||
if name == n:
|
||||
break
|
||||
if name.ident.token.lexeme != declaredName:
|
||||
continue
|
||||
if name.owner != n.owner and (name.isPrivate or n.owner notin name.exportedTo):
|
||||
continue
|
||||
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
|
||||
if name.depth < n.depth:
|
||||
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
|
||||
elif name.owner != n.owner:
|
||||
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
|
||||
return n
|
||||
|
||||
|
||||
|
@ -1907,7 +1886,10 @@ proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discar
|
|||
returnType: Type(kind: Any),
|
||||
args: @[("", self.inferOrError(node.a), default)])
|
||||
let impl = self.match(node.token.lexeme, fn, node)
|
||||
result = impl.valueType.returnType
|
||||
result = impl.valueType
|
||||
if impl.isGeneric:
|
||||
result = self.specialize(result, @[node.a])
|
||||
result = result.returnType
|
||||
if compile:
|
||||
self.generateCall(impl, @[node.a], impl.line)
|
||||
|
||||
|
@ -1926,16 +1908,15 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
|
|||
## Compiles access to identifiers
|
||||
var s = name
|
||||
if s.isNil():
|
||||
s = self.resolveOrError(node)
|
||||
var t = self.findByType(s.ident.token.lexeme, Type(kind: All))
|
||||
s = t[0] # Shadowing!
|
||||
s = self.resolveOrError(node)
|
||||
result = s.valueType
|
||||
if not compile:
|
||||
return
|
||||
var node = s.ident
|
||||
if s.isConst:
|
||||
# Constants are always emitted as Load* instructions
|
||||
# no matter the scope depth
|
||||
self.emitConstant(node, self.infer(node))
|
||||
self.emitConstant(VarDecl(s.node).value, self.infer(node))
|
||||
elif s.kind == NameKind.Function:
|
||||
# Functions have no runtime representation, they're just
|
||||
# a location to jump to, but we pretend they aren't and
|
||||
|
@ -1953,45 +1934,14 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
|
|||
self.emitByte(LoadInf, node.token.line)
|
||||
else:
|
||||
discard # Unreachable
|
||||
elif s.depth > 0 and not s.belongsTo.isNil() and s.belongsTo != self.currentFunction and self.currentFunction.valueType in s.belongsTo.valueType.children:
|
||||
# Loads a closure variable from a closure environment
|
||||
if not s.isClosedOver:
|
||||
var fn = self.currentFunction.valueType
|
||||
while true:
|
||||
fn.isClosure = true
|
||||
fn.envLen += 1
|
||||
if fn.parent.isNil():
|
||||
break
|
||||
fn = fn.parent
|
||||
s.isClosedOver = true
|
||||
self.closures.add(s)
|
||||
# The best place to save this name for later use into
|
||||
# the closure is right when the name is declared: the
|
||||
# problem with this approach is that we have no way of
|
||||
# knowing which names are closed over at the time they
|
||||
# are first declared. What we do, then, is this: once we
|
||||
# do know that a given name is closed over, we modify the
|
||||
# code segment so that it stores the name's value into the
|
||||
# topmost closure environment once it is declared
|
||||
var location: int
|
||||
if s.kind == Argument:
|
||||
location = self.insertAt(s.belongsTo.codePos, LoadVar, self.getStackPos(s).toTriple())
|
||||
else:
|
||||
location = self.insertAt(s.codePos, LoadTos, [])
|
||||
discard self.insertAt(location, AddClosure, [])
|
||||
let pos = self.getClosurePos(s)
|
||||
if pos == -1:
|
||||
self.error(&"cannot compute closure offset for '{s.ident.token.lexeme}'", s.ident)
|
||||
self.emitByte(LoadClosure, s.ident.token.line)
|
||||
self.emitBytes(pos.toTriple(), s.ident.token.line)
|
||||
else:
|
||||
# Loads a regular variable from the current frame
|
||||
self.emitByte(LoadVar, s.ident.token.line)
|
||||
# No need to check for -1 here: we already did a nil check above!
|
||||
self.emitBytes(self.getStackPos(s).toTriple(), s.ident.token.line)
|
||||
self.emitBytes(s.position.toTriple(), s.ident.token.line)
|
||||
|
||||
|
||||
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.discardable.} =
|
||||
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
|
||||
## Compiles assignment expressions
|
||||
case node.kind:
|
||||
of assignExpr:
|
||||
|
@ -2004,14 +1954,14 @@ proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.dis
|
|||
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
|
||||
self.check(node.value, r.valueType)
|
||||
self.expression(node.value)
|
||||
var position = r.position
|
||||
if r.depth < self.depth:
|
||||
self.warning(WarningKind.MutateOuterScope, &"mutation of '{r.ident.token.lexeme}' declared in outer scope ({r.owner.file}.pn:{r.ident.token.line}:{r.ident.token.relPos.start})", nil, node)
|
||||
result = r.valueType
|
||||
if not compile:
|
||||
return
|
||||
if not r.isClosedOver:
|
||||
self.emitByte(StoreVar, node.token.line)
|
||||
self.emitBytes(self.getStackPos(r).toTriple(), node.token.line)
|
||||
else:
|
||||
self.emitByte(StoreClosure, node.token.line)
|
||||
self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line)
|
||||
self.emitByte(StoreVar, node.token.line)
|
||||
self.emitBytes(position.toTriple(), node.token.line)
|
||||
of setItemExpr:
|
||||
let node = SetItemExpr(node)
|
||||
let name = IdentExpr(node.name)
|
||||
|
@ -2086,13 +2036,8 @@ proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) {.
|
|||
# Creates a new call frame and jumps
|
||||
# to the function's first instruction
|
||||
# in the code
|
||||
if not fn.isClosure:
|
||||
self.emitByte(Call, line)
|
||||
else:
|
||||
self.emitByte(CallClosure,line)
|
||||
self.emitByte(Call, line)
|
||||
self.emitBytes(args.len().toTriple(), line)
|
||||
if fn.isClosure:
|
||||
self.emitBytes(fn.envLen.toTriple(), line)
|
||||
self.patchReturnAddress(pos)
|
||||
|
||||
|
||||
|
@ -2119,12 +2064,15 @@ proc prepareFunction(self: Compiler, fn: Name) =
|
|||
constraints = @[]
|
||||
# We now declare and typecheck the function's
|
||||
# arguments
|
||||
let idx = self.stackIndex
|
||||
self.stackIndex = 1
|
||||
var default: Expression
|
||||
var i = 0
|
||||
var node = FunDecl(fn.node)
|
||||
for argument in node.arguments:
|
||||
if self.names.high() > 16777215:
|
||||
self.error("cannot declare more than 16777215 variables at a time")
|
||||
inc(self.stackIndex)
|
||||
self.names.add(Name(depth: fn.depth + 1,
|
||||
isPrivate: true,
|
||||
owner: fn.owner,
|
||||
|
@ -2137,7 +2085,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
|
|||
line: argument.name.token.line,
|
||||
belongsTo: fn,
|
||||
kind: NameKind.Argument,
|
||||
node: argument.name
|
||||
node: argument.name,
|
||||
position: self.stackIndex
|
||||
))
|
||||
if node.arguments.high() - node.defaults.high() <= node.arguments.high():
|
||||
# There's a default argument!
|
||||
|
@ -2149,6 +2098,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
|
|||
# The function needs a return type too!
|
||||
if not FunDecl(fn.node).returnType.isNil():
|
||||
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType)
|
||||
fn.position = self.stackIndex
|
||||
self.stackIndex = idx
|
||||
|
||||
|
||||
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
|
||||
|
@ -2176,13 +2127,8 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
|
|||
# Creates a new call frame and jumps
|
||||
# to the function's first instruction
|
||||
# in the code
|
||||
if not fn.valueType.isClosure:
|
||||
self.emitByte(Call, line)
|
||||
else:
|
||||
self.emitByte(CallClosure, line)
|
||||
self.emitByte(Call, line)
|
||||
self.emitBytes(args.len().toTriple(), line)
|
||||
if fn.valueType.isClosure:
|
||||
self.emitBytes(fn.valueType.envLen.toTriple(), line)
|
||||
self.patchReturnAddress(pos)
|
||||
|
||||
|
||||
|
@ -2224,7 +2170,7 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
|
|||
kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message
|
||||
if kind.isNil():
|
||||
if argument.kind == NodeKind.identExpr:
|
||||
self.error(&"reference to undeclared name '{argument.token.lexeme}'", argument)
|
||||
self.error(&"reference to undefined name '{argument.token.lexeme}'", argument)
|
||||
self.error(&"positional argument {i + 1} in function call has no type", argument)
|
||||
args.add(("", kind, default))
|
||||
argExpr.add(argument)
|
||||
|
@ -2232,12 +2178,12 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
|
|||
kind = self.infer(argument.value)
|
||||
if kind.isNil():
|
||||
if argument.value.kind == NodeKind.identExpr:
|
||||
self.error(&"reference to undeclared name '{argument.value.token.lexeme}'", argument.value)
|
||||
self.error(&"reference to undefined name '{argument.value.token.lexeme}'", argument.value)
|
||||
self.error(&"keyword argument '{argument.name.token.lexeme}' in function call has no type", argument.value)
|
||||
args.add((argument.name.token.lexeme, kind, default))
|
||||
argExpr.add(argument.value)
|
||||
case node.callee.kind:
|
||||
of identExpr:
|
||||
of NodeKind.identExpr:
|
||||
# Calls like hi()
|
||||
let impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node)
|
||||
result = impl.valueType
|
||||
|
@ -2261,11 +2207,19 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
|
|||
# one and work our way to the innermost call
|
||||
for exp in all:
|
||||
result = self.call(exp, compile)
|
||||
#echo result
|
||||
#result = result.returnType
|
||||
if compile and result.kind == Function:
|
||||
self.generateCall(result, argExpr, node.token.line)
|
||||
result = result.returnType
|
||||
of NodeKind.getItemExpr:
|
||||
var node = GetItemExpr(node.callee)
|
||||
let impl = self.match(node.name.token.lexeme,
|
||||
self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))), node)
|
||||
result = impl.valueType
|
||||
if impl.isGeneric:
|
||||
result = self.specialize(result, argExpr)
|
||||
result = result.returnType
|
||||
if compile:
|
||||
self.generateCall(impl, argExpr, node.token.line)
|
||||
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
||||
else:
|
||||
let typ = self.infer(node)
|
||||
|
@ -2275,7 +2229,7 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
|
|||
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
|
||||
|
||||
|
||||
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} =
|
||||
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} =
|
||||
## Compiles accessing to fields of a type or
|
||||
## module namespace. If the compile flag is set
|
||||
## to false, no code is generated for resolving
|
||||
|
@ -2286,12 +2240,22 @@ proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type
|
|||
let name = self.resolveOrError(IdentExpr(node.obj))
|
||||
case name.kind:
|
||||
of NameKind.Module:
|
||||
let value = self.findInModule(node.name.token.lexeme, name)
|
||||
if len(value) == 0 or self.currentModule notin value[0].exportedTo:
|
||||
var values = self.findInModule(node.name.token.lexeme, name)
|
||||
if len(values) == 0:
|
||||
self.error(&"reference to undefined name '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
|
||||
elif len(values) > 1 and matching.isNil():
|
||||
self.error(&"ambiguous reference for '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
|
||||
if not matching.isNil():
|
||||
for name in values:
|
||||
if self.compare(name.valueType, matching):
|
||||
result = name.valueType
|
||||
return
|
||||
if len(values) == 1:
|
||||
result = values[0].valueType
|
||||
else:
|
||||
self.error(&"ambiguous reference for '{node.name.token.lexeme}' in module '{name.ident.token.lexeme}'")
|
||||
if compile:
|
||||
self.identifier(nil, value[0])
|
||||
result = value[0].valueType
|
||||
self.identifier(nil, values[0])
|
||||
else:
|
||||
self.error("invalid syntax", node.obj)
|
||||
else:
|
||||
|
@ -2300,7 +2264,7 @@ proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type
|
|||
|
||||
proc lambdaExpr(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} =
|
||||
## Compiles lambda functions as expressions
|
||||
result = Type(kind: Function, isLambda: true, fun: node)
|
||||
result = Type(kind: Function, isLambda: true, fun: node, location: self.chunk.code.high())
|
||||
self.beginScope()
|
||||
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
|
||||
for gen in node.generics:
|
||||
|
@ -2345,10 +2309,10 @@ proc lambdaExpr(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.
|
|||
# The function needs a return type too!
|
||||
if not node.returnType.isNil():
|
||||
result.returnType = self.inferOrError(node.returnType)
|
||||
self.endScope()
|
||||
if not compile:
|
||||
return
|
||||
# TODO
|
||||
self.endScope()
|
||||
|
||||
|
||||
proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} =
|
||||
|
@ -2365,7 +2329,7 @@ proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.
|
|||
# would be lost in the call anyway. The differentiation
|
||||
# happens in self.assignment()
|
||||
of NodeKind.setItemExpr, NodeKind.assignExpr:
|
||||
return self.assignment(node, compile).valueType
|
||||
return self.assignment(node, compile)
|
||||
of NodeKind.identExpr:
|
||||
return self.identifier(IdentExpr(node), compile=compile)
|
||||
of NodeKind.unaryExpr:
|
||||
|
@ -2501,42 +2465,6 @@ proc exportStmt(self: Compiler, node: ExportStmt) =
|
|||
discard
|
||||
|
||||
|
||||
proc printRepl(self: Compiler, typ: Type, node: Expression) =
|
||||
## Emits instruction to print
|
||||
## peon types in REPL mode
|
||||
case typ.kind:
|
||||
of Int64:
|
||||
self.emitByte(PrintInt64, node.token.line)
|
||||
of UInt64:
|
||||
self.emitByte(PrintUInt64, node.token.line)
|
||||
of Int32:
|
||||
self.emitByte(PrintInt32, node.token.line)
|
||||
of UInt32:
|
||||
self.emitByte(PrintInt32, node.token.line)
|
||||
of Int16:
|
||||
self.emitByte(PrintInt16, node.token.line)
|
||||
of UInt16:
|
||||
self.emitByte(PrintUInt16, node.token.line)
|
||||
of Int8:
|
||||
self.emitByte(PrintInt8, node.token.line)
|
||||
of UInt8:
|
||||
self.emitByte(PrintUInt8, node.token.line)
|
||||
of Float64:
|
||||
self.emitByte(PrintFloat64, node.token.line)
|
||||
of Float32:
|
||||
self.emitByte(PrintFloat32, node.token.line)
|
||||
of Bool:
|
||||
self.emitByte(PrintBool, node.token.line)
|
||||
of Nan:
|
||||
self.emitByte(PrintNan, node.token.line)
|
||||
of Inf:
|
||||
self.emitByte(PrintInf, node.token.line)
|
||||
of String:
|
||||
self.emitByte(PrintString, node.token.line)
|
||||
else:
|
||||
self.emitByte(PrintHex, node.token.line)
|
||||
|
||||
|
||||
proc statement(self: Compiler, node: Statement) =
|
||||
## Compiles all statements
|
||||
case node.kind:
|
||||
|
@ -2619,8 +2547,9 @@ proc varDecl(self: Compiler, node: VarDecl) =
|
|||
self.emitByte(AddVar, node.token.line)
|
||||
self.declare(node)
|
||||
var name = self.names[^1]
|
||||
inc(self.stackIndex)
|
||||
name.position = self.stackIndex
|
||||
name.valueType = typ
|
||||
|
||||
|
||||
|
||||
proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
||||
|
@ -2630,32 +2559,27 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
var node = node
|
||||
var jmp: int
|
||||
# We store the current function
|
||||
var function = self.currentFunction
|
||||
let function = self.currentFunction
|
||||
if node.body.isNil():
|
||||
# We ignore forward declarations
|
||||
self.forwarded.add((name, 0))
|
||||
name.valueType.forwarded = true
|
||||
self.currentFunction = function
|
||||
return
|
||||
if not function.isNil() and self.depth > 1:
|
||||
# Calling a function immediately compiles it, so if
|
||||
# we didn't check for the scope depth we'd mistakenly
|
||||
# mark functions as being nested when they actually
|
||||
# aren't
|
||||
function.valueType.children.add(name.valueType)
|
||||
name.valueType.parent = function.valueType
|
||||
self.currentFunction = name
|
||||
if self.currentFunction.isBuiltin:
|
||||
self.currentFunction = function
|
||||
return
|
||||
let stackIdx = self.stackIndex
|
||||
self.stackIndex = name.position
|
||||
# A function's code is just compiled linearly
|
||||
# and then jumped over
|
||||
jmp = self.emitJump(JumpForwards, node.token.line)
|
||||
name.codePos = self.chunk.code.len()
|
||||
name.valueType.location = name.codePos
|
||||
# We let our debugger know this function's boundaries
|
||||
self.chunk.functions.add(self.chunk.code.high().toTriple())
|
||||
self.functions.add((start: self.chunk.code.high(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
|
||||
self.chunk.functions.add(self.chunk.code.len().toTriple())
|
||||
self.functions.add((start: self.chunk.code.len(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
|
||||
var offset = self.functions[^1]
|
||||
let idx = self.chunk.functions.len()
|
||||
self.chunk.functions.add(0.toTriple()) # Patched it later
|
||||
|
@ -2728,19 +2652,17 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
# Restores the enclosing function (if any).
|
||||
# Makes nested calls work (including recursion)
|
||||
self.currentFunction = function
|
||||
self.stackIndex = stackIdx
|
||||
|
||||
|
||||
proc declaration(self: Compiler, node: Declaration) =
|
||||
## Handles all declarations. They are not compiled
|
||||
## right away, but rather only when they're referenced
|
||||
## the first time
|
||||
## Compiles declarations, statements and expressions
|
||||
## recursively
|
||||
case node.kind:
|
||||
of NodeKind.funDecl:
|
||||
var name = self.declare(node)
|
||||
self.funDecl(FunDecl(node), name)
|
||||
if name.isGeneric:
|
||||
# We typecheck generics immediately
|
||||
name.resolved = true
|
||||
self.funDecl(FunDecl(node), name)
|
||||
# After we're done compiling a generic
|
||||
# function, we pull a magic trick: since,
|
||||
# from here on, the user will be able to
|
||||
|
@ -2757,7 +2679,7 @@ proc declaration(self: Compiler, node: Declaration) =
|
|||
continue
|
||||
else:
|
||||
argument.kind.asUnion = true
|
||||
if not name.valueType.returnType.isNil() and name.valueType.returnType.isNil():
|
||||
if not name.valueType.returnType.isNil() and name.valueType.returnType.kind == Generic:
|
||||
name.valueType.returnType.asUnion = true
|
||||
of NodeKind.typeDecl:
|
||||
self.declare(node)
|
||||
|
@ -2790,6 +2712,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
|
|||
self.disabledWarnings = disabledWarnings
|
||||
self.showMismatches = showMismatches
|
||||
self.mode = mode
|
||||
self.stackIndex = 1
|
||||
if not incremental:
|
||||
self.jumps = @[]
|
||||
let pos = self.beginProgram()
|
||||
|
|
|
@ -93,6 +93,7 @@ type
|
|||
# This is not shown when the node is printed, but makes it a heck of a lot easier to report
|
||||
# errors accurately even deep in the compilation pipeline
|
||||
token*: Token
|
||||
file*: string
|
||||
# This weird inheritance chain is needed for the parser to
|
||||
# work properly
|
||||
Declaration* = ref object of ASTNode
|
||||
|
|
|
@ -122,6 +122,18 @@ type
|
|||
LessThan,
|
||||
GreaterOrEqual,
|
||||
LessOrEqual,
|
||||
SignedGreaterThan,
|
||||
SignedLessThan,
|
||||
SignedGreaterOrEqual,
|
||||
SignedLessOrEqual,
|
||||
Float64GreaterThan,
|
||||
Float64LessThan,
|
||||
Float64GreaterOrEqual,
|
||||
Float64LessOrEqual,
|
||||
Float32GreaterThan,
|
||||
Float32LessThan,
|
||||
Float32GreaterOrEqual,
|
||||
Float32LessOrEqual,
|
||||
LogicalNot,
|
||||
## Print opcodes
|
||||
PrintInt64,
|
||||
|
@ -148,9 +160,6 @@ type
|
|||
LoadVar, # Pushes the object at position x in the stack onto the stack
|
||||
StoreVar, # Stores the value of b at position a in the stack
|
||||
AddVar, # An optimization for StoreVar (used when the variable is first declared)
|
||||
LoadClosure, # Pushes the object position x in the closure array onto the stack
|
||||
StoreClosure, # Stores the value of b at position a in the closure array
|
||||
AddClosure, # This is the same optimization of AddVar, but applied to StoreClosure instead
|
||||
## Looping and jumping
|
||||
Jump, # Absolute, unconditional jump into the bytecode
|
||||
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
||||
|
@ -161,7 +170,6 @@ type
|
|||
JumpIfFalseOrPop, # Jumps to an absolute index in the bytecode if x is false and pops otherwise (used for logical and)
|
||||
## Functions
|
||||
Call, # Calls a function and initiates a new stack frame
|
||||
CallClosure, # Calls a closure
|
||||
Return, # Terminates the current function
|
||||
SetResult, # Sets the result of the current function
|
||||
## Exception handling
|
||||
|
@ -178,7 +186,7 @@ type
|
|||
PopC, # Pop off the call stack onto the operand stack
|
||||
PushC, # Pop off the operand stack onto the call stack
|
||||
SysClock64, # Pushes the output of a monotonic clock on the stack
|
||||
LoadTos # Pushes the top of the call stack onto the operand stack
|
||||
LoadTOS # Pushes the top of the call stack onto the operand stack
|
||||
|
||||
|
||||
# We group instructions by their operation/operand types for easier handling when debugging
|
||||
|
@ -244,8 +252,19 @@ const simpleInstructions* = {Return, LoadNil,
|
|||
PrintString,
|
||||
LogicalNot,
|
||||
AddVar,
|
||||
AddClosure,
|
||||
LoadTos
|
||||
LoadTOS,
|
||||
SignedGreaterThan,
|
||||
SignedLessThan,
|
||||
SignedGreaterOrEqual,
|
||||
SignedLessOrEqual,
|
||||
Float64GreaterThan,
|
||||
Float64LessThan,
|
||||
Float64GreaterOrEqual,
|
||||
Float64LessOrEqual,
|
||||
Float32GreaterThan,
|
||||
Float32LessThan,
|
||||
Float32GreaterOrEqual,
|
||||
Float32LessOrEqual,
|
||||
}
|
||||
|
||||
# Constant instructions are instructions that operate on the bytecode constant table
|
||||
|
@ -258,7 +277,7 @@ const constantInstructions* = {LoadInt64, LoadUInt64,
|
|||
|
||||
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
|
||||
# of 24 bit integers
|
||||
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, }
|
||||
const stackTripleInstructions* = {StoreVar, LoadVar, }
|
||||
|
||||
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
|
||||
# of 16 bit integers
|
||||
|
|
|
@ -38,7 +38,7 @@ type
|
|||
Yield, Defer, Try, Except,
|
||||
Finally, Type, Operator, Case,
|
||||
Enum, From, Ptr, Ref, Object,
|
||||
Export,
|
||||
Export, Block
|
||||
|
||||
# Literal types
|
||||
Integer, Float, String, Identifier,
|
||||
|
@ -52,12 +52,12 @@ type
|
|||
LeftBracket, RightBracket, # []
|
||||
Dot, Semicolon, Comma, # . ; ,
|
||||
|
||||
# Miscellaneous
|
||||
# Miscellaneous
|
||||
|
||||
EndOfFile, # Marks the end of the token stream
|
||||
NoMatch, # Used internally by the symbol table
|
||||
Comment, # Useful for documentation comments, pragmas, etc.
|
||||
Symbol, # A generic symbol
|
||||
NoMatch, # Used internally by the symbol table
|
||||
Comment, # Useful for documentation comments, pragmas, etc.
|
||||
Symbol, # A generic symbol
|
||||
Pragma,
|
||||
|
||||
Token* = ref object
|
||||
|
|
|
@ -315,17 +315,23 @@ proc primary(self: Parser): Expression =
|
|||
case self.peek().kind:
|
||||
of True:
|
||||
result = newTrueExpr(self.step())
|
||||
result.file = self.file
|
||||
of False:
|
||||
result = newFalseExpr(self.step())
|
||||
result.file = self.file
|
||||
of Float:
|
||||
result = newFloatExpr(self.step())
|
||||
result.file = self.file
|
||||
of Integer:
|
||||
result = newIntExpr(self.step())
|
||||
result.file = self.file
|
||||
of Identifier:
|
||||
result = newIdentExpr(self.step(), self.scopeDepth)
|
||||
result.file = self.file
|
||||
of LeftParen:
|
||||
let tok = self.step()
|
||||
result = newGroupingExpr(self.expression(), tok)
|
||||
result.file = self.file
|
||||
self.expect(RightParen, "unterminated parenthesized expression")
|
||||
of Yield:
|
||||
let tok = self.step()
|
||||
|
@ -340,6 +346,7 @@ proc primary(self: Parser): Expression =
|
|||
else:
|
||||
# Empty yield
|
||||
result = newYieldExpr(nil, tok)
|
||||
result.file = self.file
|
||||
of Await:
|
||||
let tok = self.step()
|
||||
if self.currentFunction.isNil():
|
||||
|
@ -347,6 +354,7 @@ proc primary(self: Parser): Expression =
|
|||
if self.currentFunction.token.kind != Coroutine:
|
||||
self.error("'await' can only be used inside coroutines", tok)
|
||||
result = newAwaitExpr(self.expression(), tok)
|
||||
result.file = self.file
|
||||
of RightParen, RightBracket, RightBrace:
|
||||
# This is *technically* unnecessary: the parser would
|
||||
# throw an error regardless, but it's a little bit nicer
|
||||
|
@ -354,27 +362,36 @@ proc primary(self: Parser): Expression =
|
|||
self.error(&"unmatched '{self.peek().lexeme}'")
|
||||
of Hex:
|
||||
result = newHexExpr(self.step())
|
||||
result.file = self.file
|
||||
of Octal:
|
||||
result = newOctExpr(self.step())
|
||||
result.file = self.file
|
||||
of Binary:
|
||||
result = newBinExpr(self.step())
|
||||
result.file = self.file
|
||||
of String:
|
||||
result = newStrExpr(self.step())
|
||||
result.file = self.file
|
||||
of Function:
|
||||
discard self.step()
|
||||
result = Expression(self.funDecl(isLambda=true))
|
||||
result.file = self.file
|
||||
of Coroutine:
|
||||
discard self.step()
|
||||
result = Expression(self.funDecl(isAsync=true, isLambda=true))
|
||||
result.file = self.file
|
||||
of Generator:
|
||||
discard self.step()
|
||||
result = Expression(self.funDecl(isGenerator=true, isLambda=true))
|
||||
result.file = self.file
|
||||
of TokenType.Ref:
|
||||
discard self.step()
|
||||
result = newRefExpr(self.expression(), self.peek(-1))
|
||||
result.file = self.file
|
||||
of TokenType.Ptr:
|
||||
discard self.step()
|
||||
result = newPtrExpr(self.expression(), self.peek(-1))
|
||||
result.file = self.file
|
||||
else:
|
||||
self.error("invalid syntax")
|
||||
|
||||
|
@ -409,6 +426,7 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
|
|||
argCount += 1
|
||||
self.expect(RightParen)
|
||||
result = newCallExpr(callee, arguments, tok)
|
||||
result.file = self.file
|
||||
result.closeParen = self.peek(-1)
|
||||
|
||||
|
||||
|
@ -428,6 +446,7 @@ proc call(self: Parser): Expression =
|
|||
elif self.match(Dot):
|
||||
self.expect(Identifier, "expecting attribute name after '.'")
|
||||
result = newGetItemExpr(result, newIdentExpr(self.peek(-1), self.scopeDepth), self.peek(-1))
|
||||
result.file = self.file
|
||||
elif self.match(LeftBracket):
|
||||
self.parseGenericArgs() # TODO
|
||||
result = self.makeCall(result)
|
||||
|
@ -440,6 +459,7 @@ proc unary(self: Parser): Expression =
|
|||
## Parses unary expressions
|
||||
if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens:
|
||||
result = newUnaryExpr(self.step(), self.unary())
|
||||
result.file = self.file
|
||||
else:
|
||||
result = self.call()
|
||||
|
||||
|
@ -453,6 +473,7 @@ proc parsePow(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.unary()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseMul(self: Parser): Expression =
|
||||
|
@ -465,6 +486,7 @@ proc parseMul(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parsePow()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseAdd(self: Parser): Expression =
|
||||
|
@ -477,6 +499,7 @@ proc parseAdd(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseMul()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseBitwise(self: Parser): Expression =
|
||||
|
@ -488,6 +511,7 @@ proc parseBitwise(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseAdd()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseCmp(self: Parser): Expression =
|
||||
|
@ -499,6 +523,7 @@ proc parseCmp(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseAdd()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseAnd(self: Parser): Expression =
|
||||
|
@ -510,6 +535,7 @@ proc parseAnd(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseCmp()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseOr(self: Parser): Expression =
|
||||
|
@ -521,6 +547,7 @@ proc parseOr(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseAnd()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseAssign(self: Parser): Expression =
|
||||
|
@ -532,8 +559,10 @@ proc parseAssign(self: Parser): Expression =
|
|||
case result.kind:
|
||||
of identExpr, sliceExpr:
|
||||
result = newAssignExpr(result, value, tok)
|
||||
result.file = self.file
|
||||
of getItemExpr:
|
||||
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
|
||||
result.file = self.file
|
||||
else:
|
||||
self.error("invalid assignment target", tok)
|
||||
|
||||
|
@ -547,6 +576,7 @@ proc parseArrow(self: Parser): Expression =
|
|||
operator = self.step()
|
||||
right = self.parseAssign()
|
||||
result = newBinaryExpr(result, operator, right)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
## End of operator parsing handlers
|
||||
|
@ -560,6 +590,7 @@ proc assertStmt(self: Parser): Statement =
|
|||
var expression = self.expression()
|
||||
endOfLine("missing semicolon after 'assert'")
|
||||
result = newAssertStmt(expression, tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc beginScope(self: Parser) =
|
||||
|
@ -585,6 +616,7 @@ proc blockStmt(self: Parser): Statement =
|
|||
code.delete(code.high())
|
||||
self.expect(RightBrace, "expecting '}'")
|
||||
result = newBlockStmt(code, tok)
|
||||
result.file = self.file
|
||||
self.endScope()
|
||||
|
||||
|
||||
|
@ -595,6 +627,7 @@ proc breakStmt(self: Parser): Statement =
|
|||
self.error("'break' cannot be used outside loops")
|
||||
endOfLine("missing semicolon after 'break'")
|
||||
result = newBreakStmt(tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc deferStmt(self: Parser): Statement =
|
||||
|
@ -604,6 +637,7 @@ proc deferStmt(self: Parser): Statement =
|
|||
self.error("'defer' cannot be used outside functions")
|
||||
endOfLine("missing semicolon after 'defer'")
|
||||
result = newDeferStmt(self.expression(), tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc continueStmt(self: Parser): Statement =
|
||||
|
@ -613,6 +647,7 @@ proc continueStmt(self: Parser): Statement =
|
|||
self.error("'continue' cannot be used outside loops")
|
||||
endOfLine("missing semicolon after 'continue'")
|
||||
result = newContinueStmt(tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc returnStmt(self: Parser): Statement =
|
||||
|
@ -628,6 +663,7 @@ proc returnStmt(self: Parser): Statement =
|
|||
value = self.expression()
|
||||
endOfLine("missing semicolon after 'return'")
|
||||
result = newReturnStmt(value, tok)
|
||||
result.file = self.file
|
||||
case self.currentFunction.kind:
|
||||
of NodeKind.funDecl:
|
||||
FunDecl(self.currentFunction).hasExplicitReturn = true
|
||||
|
@ -646,6 +682,7 @@ proc yieldStmt(self: Parser): Statement =
|
|||
result = newYieldStmt(self.expression(), tok)
|
||||
else:
|
||||
result = newYieldStmt(nil, tok)
|
||||
result.file = self.file
|
||||
endOfLine("missing semicolon after 'yield'")
|
||||
|
||||
|
||||
|
@ -658,6 +695,7 @@ proc awaitStmt(self: Parser): Statement =
|
|||
self.error("'await' can only be used inside coroutines")
|
||||
endOfLine("missing semicolon after 'await'")
|
||||
result = newAwaitStmt(self.expression(), tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc raiseStmt(self: Parser): Statement =
|
||||
|
@ -670,6 +708,7 @@ proc raiseStmt(self: Parser): Statement =
|
|||
exception = self.expression()
|
||||
endOfLine("missing semicolon after 'raise'")
|
||||
result = newRaiseStmt(exception, tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc forEachStmt(self: Parser): Statement =
|
||||
|
@ -683,6 +722,7 @@ proc forEachStmt(self: Parser): Statement =
|
|||
let expression = self.expression()
|
||||
self.expect(LeftBrace)
|
||||
result = newForEachStmt(identifier, expression, self.blockStmt(), tok)
|
||||
result.file = self.file
|
||||
self.currentLoop = enclosingLoop
|
||||
|
||||
|
||||
|
@ -719,6 +759,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
|
|||
pos: (tok.pos.stop + 1, (tok.pos.stop + 1) + len(moduleName)),
|
||||
relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName))),
|
||||
self.scopeDepth), tok)
|
||||
result.file = self.file
|
||||
moduleName &= ".pn"
|
||||
var lexer = newLexer()
|
||||
lexer.fillSymbolTable()
|
||||
|
@ -778,6 +819,7 @@ proc tryStmt(self: Parser): Statement =
|
|||
if handler.exc.isNil() and i != handlers.high():
|
||||
self.error("catch-all exception handler with bare 'except' must come last in try statement", handler.exc.token)
|
||||
result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc whileStmt(self: Parser): Statement =
|
||||
|
@ -789,6 +831,7 @@ proc whileStmt(self: Parser): Statement =
|
|||
self.expect(LeftBrace)
|
||||
self.currentLoop = Loop
|
||||
result = newWhileStmt(condition, self.blockStmt(), tok)
|
||||
result.file = self.file
|
||||
self.currentLoop = enclosingLoop
|
||||
self.endScope()
|
||||
|
||||
|
@ -807,6 +850,7 @@ proc ifStmt(self: Parser): Statement =
|
|||
self.expect(LeftBrace, "expecting 'if' or block statement")
|
||||
elseBranch = self.blockStmt()
|
||||
result = newIfStmt(condition, thenBranch, elseBranch, tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc exportStmt(self: Parser): Statement =
|
||||
|
@ -818,6 +862,7 @@ proc exportStmt(self: Parser): Statement =
|
|||
exported = newIdentExpr(self.peek(-1))
|
||||
endOfLine("missing semicolon after 'raise'")
|
||||
result = newExportStmt(exported, tok)
|
||||
result.file = self.file
|
||||
|
||||
|
||||
template checkDecl(self: Parser, isPrivate: bool) =
|
||||
|
@ -841,6 +886,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
|
|||
self.error("duplicate pragmas are not allowed")
|
||||
names.add(self.peek(-1).lexeme)
|
||||
name = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
name.file = self.file
|
||||
if not self.match(":"):
|
||||
if self.match("]"):
|
||||
result.add(newPragma(name, @[]))
|
||||
|
@ -862,6 +908,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
|
|||
self.error("pragma arguments can only be literals", exp.token)
|
||||
args.add(LiteralExpr(exp))
|
||||
result.add(newPragma(name, args))
|
||||
result[^1].file = self.file
|
||||
if self.match(","):
|
||||
continue
|
||||
|
||||
|
@ -913,6 +960,7 @@ proc varDecl(self: Parser, isLet: bool = false,
|
|||
if not hasInit and VarDecl(result).valueType.isNil():
|
||||
self.error("expecting initializer or explicit type annotation, but neither was found", result.token)
|
||||
result.pragmas = pragmas
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr, valueType: Expression]],
|
||||
|
@ -924,6 +972,7 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
|
|||
self.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
|
||||
self.expect(Identifier, "expecting parameter name")
|
||||
parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
parameter.name.file = self.file
|
||||
if self.match(":"):
|
||||
parameter.valueType = self.expression()
|
||||
for i in countdown(arguments.high(), 0):
|
||||
|
@ -966,6 +1015,7 @@ proc parseFunExpr(self: Parser): LambdaExpr =
|
|||
result.returnType = self.expression()
|
||||
result.arguments = arguments
|
||||
result.defaults = defaults
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc parseGenericConstraint(self: Parser): Expression =
|
||||
|
@ -976,8 +1026,10 @@ proc parseGenericConstraint(self: Parser): Expression =
|
|||
case self.peek().lexeme:
|
||||
of "|":
|
||||
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
|
||||
result.file = self.file
|
||||
of "~":
|
||||
result = newUnaryExpr(self.step(), result)
|
||||
result.file = self.file
|
||||
of ",":
|
||||
discard # Comma is handled in parseGenerics()
|
||||
else:
|
||||
|
@ -990,6 +1042,7 @@ proc parseGenerics(self: Parser, decl: Declaration) =
|
|||
while not self.check(RightBracket) and not self.done():
|
||||
self.expect(Identifier, "expecting generic type name")
|
||||
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
gen.name.file = self.file
|
||||
self.expect(":", "expecting type constraint after generic name")
|
||||
gen.cond = self.parseGenericConstraint()
|
||||
decl.generics.add(gen)
|
||||
|
@ -1101,11 +1154,13 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
|
|||
self.error(&"missing type declaration for '{argument.name.token.lexeme}' in function declaration")
|
||||
self.currentFunction = enclosingFunction
|
||||
result.pragmas = pragmas
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc expression(self: Parser): Expression =
|
||||
## Parses expressions
|
||||
result = self.parseArrow() # Highest-level expression
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc expressionStatement(self: Parser): Statement =
|
||||
|
@ -1114,6 +1169,7 @@ proc expressionStatement(self: Parser): Statement =
|
|||
var expression = self.expression()
|
||||
endOfLine("missing semicolon at end of expression", expression.token)
|
||||
result = Statement(newExprStmt(expression, expression.token))
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc statement(self: Parser): Statement =
|
||||
|
@ -1171,6 +1227,7 @@ proc statement(self: Parser): Statement =
|
|||
result = self.tryStmt()
|
||||
else:
|
||||
result = self.expressionStatement()
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc typeDecl(self: Parser): TypeDecl =
|
||||
|
@ -1241,6 +1298,7 @@ proc typeDecl(self: Parser): TypeDecl =
|
|||
if not self.check(RightBrace):
|
||||
self.expect(",", "expecting comma after enum field declaration")
|
||||
result.pragmas = pragmas
|
||||
result.file = self.file
|
||||
|
||||
|
||||
proc declaration(self: Parser): Declaration =
|
||||
|
|
12
src/main.nim
12
src/main.nim
|
@ -154,7 +154,7 @@ proc repl =
|
|||
|
||||
|
||||
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false,
|
||||
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug) =
|
||||
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, run: bool = true) =
|
||||
var
|
||||
tokens: seq[Token] = @[]
|
||||
tree: seq[Declaration] = @[]
|
||||
|
@ -237,7 +237,8 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
|
|||
styledEcho fgGreen, "OK"
|
||||
else:
|
||||
styledEcho fgRed, "Corrupted"
|
||||
vm.run(serialized.chunk, breakpoints)
|
||||
if run:
|
||||
vm.run(serialized.chunk, breakpoints)
|
||||
except LexingError:
|
||||
print(LexingError(getCurrentException()))
|
||||
except ParseError:
|
||||
|
@ -264,6 +265,7 @@ when isMainModule:
|
|||
var dis: bool = false
|
||||
var mismatches: bool = false
|
||||
var mode: CompileMode = CompileMode.Debug
|
||||
var run: bool = true
|
||||
for kind, key, value in optParser.getopt():
|
||||
case kind:
|
||||
of cmdArgument:
|
||||
|
@ -323,6 +325,8 @@ when isMainModule:
|
|||
quit()
|
||||
of "disassemble":
|
||||
dis = true
|
||||
of "compile":
|
||||
run = false
|
||||
else:
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'")
|
||||
quit()
|
||||
|
@ -349,6 +353,8 @@ when isMainModule:
|
|||
except ValueError:
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
|
||||
quit()
|
||||
of "c":
|
||||
run = false
|
||||
of "d":
|
||||
dis = true
|
||||
else:
|
||||
|
@ -360,7 +366,7 @@ when isMainModule:
|
|||
if file == "":
|
||||
repl()
|
||||
else:
|
||||
runFile(file, fromString, dump, breaks, dis, warnings, mismatches, mode)
|
||||
runFile(file, fromString, dump, breaks, dis, warnings, mismatches, mode, run)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import values;
|
||||
|
||||
|
||||
operator `>`*[T: Number](a, b: T): bool {
|
||||
operator `>`*[T: UnsignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "GreaterThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<`*[T: Number](a, b: T): bool {
|
||||
operator `<`*[T: UnsignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "LessThan", pure]
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,72 @@ operator `!=`*[T: Number](a, b: T): bool {
|
|||
}
|
||||
|
||||
|
||||
operator `>=`*[T: Number](a, b: T): bool {
|
||||
operator `>=`*[T: UnsignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "GreaterOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<=`*[T: Number](a, b: T): bool {
|
||||
operator `<=`*[T: UnsignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "LessOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>`*[T: SignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "SignedGreaterThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<`*[T: SignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "SignedLessThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>=`*[T: SignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "SignedGreaterOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<=`*[T: SignedInteger](a, b: T): bool {
|
||||
#pragma[magic: "SignedLessOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>`*(a, b: float64): bool {
|
||||
#pragma[magic: "Float64GreaterThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<`*(a, b: float64): bool {
|
||||
#pragma[magic: "Float64LessThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>=`*(a, b: float64): bool {
|
||||
#pragma[magic: "Float64GreaterOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<=`*(a, b: float64): bool {
|
||||
#pragma[magic: "Float64LessOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>`*(a, b: float32): bool {
|
||||
#pragma[magic: "Float32GreaterThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<`*(a, b: float32): bool {
|
||||
#pragma[magic: "Float32LessThan", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `>=`*(a, b: float32): bool {
|
||||
#pragma[magic: "Float32GreaterOrEqual", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `<=`*(a, b: float32): bool {
|
||||
#pragma[magic: "Float32LessOrEqual", pure]
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
import values;
|
||||
|
||||
|
||||
# Some useful builtins
|
||||
|
||||
fn clock*: float {
|
||||
#pragma[magic: "SysClock64"]
|
||||
}
|
||||
|
|
|
@ -7,10 +7,31 @@ const e* = 2.718281828459045;
|
|||
const tau* = 6.283185307179586;
|
||||
|
||||
|
||||
fn abs*[T: int64 | int32 | int16 | int8](n: T): T {
|
||||
## Returns the absolute value of the given number
|
||||
fn abs*[T: SignedInteger](n: T): T {
|
||||
## Returns the absolute value of
|
||||
## the given number
|
||||
if n < 0 {
|
||||
return -n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn abs*(f: float64): float64 {
|
||||
## Returns the absolute value of
|
||||
## the given number
|
||||
if f < 0.0 {
|
||||
return -f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
fn abs*(f: float32): float32 {
|
||||
## Returns the absolute value of
|
||||
## the given number
|
||||
if f < 0.0'f32 {
|
||||
return -f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ export logical;
|
|||
export misc;
|
||||
export comparisons;
|
||||
|
||||
|
||||
var version* = 1;
|
||||
var _private = 5; # Invisible outside the module (underscore is to silence warning)
|
||||
var test* = 0x60;
|
|
@ -132,15 +132,6 @@ proc argumentTripleInstruction(self: Debugger, instruction: OpCode) {.used.} =
|
|||
self.current += 4
|
||||
|
||||
|
||||
proc storeClosureInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
|
||||
var idx = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
var idx2 = [self.chunk.code[self.current + 4], self.chunk.code[self.current + 5], self.chunk.code[self.current + 6]].fromTriple()
|
||||
printInstruction(instruction)
|
||||
stdout.styledWriteLine(fgGreen, ", stores element at position ", fgYellow, $idx, fgGreen, " into position ", fgYellow, $idx2)
|
||||
self.current += 7
|
||||
|
||||
|
||||
proc callInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs function calls
|
||||
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
|
@ -150,17 +141,6 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
|
|||
self.current += 1
|
||||
|
||||
|
||||
proc callClosureInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs closure calls
|
||||
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
self.current += 3
|
||||
var envSize = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
self.current += 3
|
||||
printInstruction(instruction)
|
||||
styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen, " with environment of size ", fgYellow, $envSize, fgGreen
|
||||
self.current += 1
|
||||
|
||||
|
||||
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs instructions that operate on the constant table
|
||||
var size: uint
|
||||
|
@ -190,7 +170,7 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
|
|||
inc(self.current)
|
||||
for i in countup(orig, self.current + 1):
|
||||
self.checkFunctionStart(i)
|
||||
|
||||
|
||||
|
||||
proc disassembleInstruction*(self: Debugger) =
|
||||
## Takes one bytecode instruction and prints it
|
||||
|
@ -210,18 +190,14 @@ proc disassembleInstruction*(self: Debugger) =
|
|||
self.stackTripleInstruction(opcode)
|
||||
of argumentDoubleInstructions:
|
||||
self.argumentDoubleInstruction(opcode)
|
||||
of StoreClosure:
|
||||
self.storeClosureInstruction(opcode)
|
||||
of Call:
|
||||
self.callInstruction(opcode)
|
||||
of CallClosure:
|
||||
self.callClosureInstruction(opcode)
|
||||
of jumpInstructions:
|
||||
self.jumpInstruction(opcode)
|
||||
else:
|
||||
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
|
||||
self.current += 1
|
||||
|
||||
|
||||
|
||||
proc parseFunctions(self: Debugger) =
|
||||
## Parses function information in the chunk
|
||||
|
|
|
@ -44,8 +44,8 @@ proc print*(exc: CompileError) =
|
|||
var file = exc.file
|
||||
if file notin ["<string>", ""]:
|
||||
file = relativePath(exc.file, getCurrentDir())
|
||||
printError(file, exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
|
||||
exc.line, exc.node.getRelativeBoundaries(), exc.compiler.getCurrentFunction(),
|
||||
printError(file, readFile(file).splitLines()[exc.line - 1].strip(chars={'\n'}),
|
||||
exc.line, exc.node.getRelativeBoundaries(), exc.function,
|
||||
exc.msg)
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -1,10 +0,0 @@
|
|||
# Tests operator dispatching
|
||||
|
||||
|
||||
operator `+`(a: int): int {
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
+1; # Works: defined for int64
|
||||
# +1'i32; # Will not work
|
|
@ -5,7 +5,7 @@ import std;
|
|||
fn foo: int;
|
||||
|
||||
|
||||
print(foo());
|
||||
print(foo() == 42);
|
||||
|
||||
|
||||
fn foo: int {return 42;} # Commenting this line will cause an error
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Another test for generic functions
|
||||
# Another test for generic functions. This should fail to compile
|
||||
import std;
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import std;
|
||||
|
||||
|
||||
var a = 5;
|
||||
fn f {
|
||||
a = 6;
|
||||
}
|
||||
|
||||
|
||||
print(a);
|
||||
f();
|
||||
#print(a);
|
|
@ -1,6 +0,0 @@
|
|||
import std;
|
||||
import math;
|
||||
|
||||
|
||||
print("Testing math module");
|
||||
print(math.abs(-5) == 5);
|
|
@ -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!
|
|
@ -7,8 +7,8 @@ var x = 5;
|
|||
var x = 55;
|
||||
{
|
||||
var x = 22;
|
||||
print(x);
|
||||
print(x == 22);
|
||||
}
|
||||
print(x);
|
||||
print(x == 55);
|
||||
}
|
||||
print(x);
|
||||
print(x == 5);
|
||||
|
|
Loading…
Reference in New Issue