diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 2d8574b..f9516cb 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -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..= 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()) diff --git a/src/config.nim b/src/config.nim index 04dcb28..03eb869 100644 --- a/src/config.nim +++ b/src/config.nim @@ -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) diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index e8a0ca7..013d1be 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -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() diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index a9d4422..3855ee6 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -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 diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index fc01fda..ad5f156 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -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 diff --git a/src/frontend/meta/token.nim b/src/frontend/meta/token.nim index e1c5568..75cf79d 100644 --- a/src/frontend/meta/token.nim +++ b/src/frontend/meta/token.nim @@ -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 diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 4666205..253d9f8 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -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 = diff --git a/src/main.nim b/src/main.nim index 50cb26c..f35409f 100644 --- a/src/main.nim +++ b/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) diff --git a/src/peon/stdlib/builtins/comparisons.pn b/src/peon/stdlib/builtins/comparisons.pn index 9489ad4..dca4fd7 100644 --- a/src/peon/stdlib/builtins/comparisons.pn +++ b/src/peon/stdlib/builtins/comparisons.pn @@ -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] +} + diff --git a/src/peon/stdlib/builtins/misc.pn b/src/peon/stdlib/builtins/misc.pn index 8b0fea5..01d0f9a 100644 --- a/src/peon/stdlib/builtins/misc.pn +++ b/src/peon/stdlib/builtins/misc.pn @@ -2,8 +2,6 @@ import values; -# Some useful builtins - fn clock*: float { #pragma[magic: "SysClock64"] } diff --git a/src/peon/stdlib/math.pn b/src/peon/stdlib/math.pn index 6c4a2a3..a486a86 100644 --- a/src/peon/stdlib/math.pn +++ b/src/peon/stdlib/math.pn @@ -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; -} \ No newline at end of file +} + + +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; +} diff --git a/src/peon/stdlib/std.pn b/src/peon/stdlib/std.pn index 7d96d68..c5bd37f 100644 --- a/src/peon/stdlib/std.pn +++ b/src/peon/stdlib/std.pn @@ -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; \ No newline at end of file diff --git a/src/util/debugger.nim b/src/util/debugger.nim index 99cb2d4..dbe9b63 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -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 diff --git a/src/util/fmterr.nim b/src/util/fmterr.nim index 0be908a..ae9c757 100644 --- a/src/util/fmterr.nim +++ b/src/util/fmterr.nim @@ -44,8 +44,8 @@ proc print*(exc: CompileError) = var file = exc.file if file notin ["", ""]: 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) diff --git a/tests/abs.pn b/tests/abs.pn new file mode 100644 index 0000000..3acaa95 --- /dev/null +++ b/tests/abs.pn @@ -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); diff --git a/tests/closures.pn b/tests/closures.pn deleted file mode 100644 index 705cc8c..0000000 --- a/tests/closures.pn +++ /dev/null @@ -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 \ No newline at end of file diff --git a/tests/closures2.pn b/tests/closures2.pn deleted file mode 100644 index 60867c6..0000000 --- a/tests/closures2.pn +++ /dev/null @@ -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 diff --git a/tests/dispatch.pn b/tests/dispatch.pn deleted file mode 100644 index 6b0ad64..0000000 --- a/tests/dispatch.pn +++ /dev/null @@ -1,10 +0,0 @@ -# Tests operator dispatching - - -operator `+`(a: int): int { - return a; -} - - -+1; # Works: defined for int64 -# +1'i32; # Will not work diff --git a/tests/fwd.pn b/tests/fwd.pn index 77cae6b..ae25745 100644 --- a/tests/fwd.pn +++ b/tests/fwd.pn @@ -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 diff --git a/tests/generics3.pn b/tests/generics3.pn index 10f4af6..49162a4 100644 --- a/tests/generics3.pn +++ b/tests/generics3.pn @@ -1,4 +1,4 @@ -# Another test for generic functions +# Another test for generic functions. This should fail to compile import std; diff --git a/tests/local.pn b/tests/local.pn deleted file mode 100644 index cd2ec87..0000000 --- a/tests/local.pn +++ /dev/null @@ -1,12 +0,0 @@ -import std; - - -var a = 5; -fn f { - a = 6; -} - - -print(a); -f(); -#print(a); diff --git a/tests/math_test.pn b/tests/math_test.pn deleted file mode 100644 index 27a9cd7..0000000 --- a/tests/math_test.pn +++ /dev/null @@ -1,6 +0,0 @@ -import std; -import math; - - -print("Testing math module"); -print(math.abs(-5) == 5); \ No newline at end of file diff --git a/tests/mutable.pn b/tests/mutable.pn deleted file mode 100644 index 795e1dd..0000000 --- a/tests/mutable.pn +++ /dev/null @@ -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! diff --git a/tests/scopes.pn b/tests/scopes.pn index f841ef0..3b7cde7 100644 --- a/tests/scopes.pn +++ b/tests/scopes.pn @@ -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); \ No newline at end of file +print(x == 5);