diff --git a/src/backend/bytecode/vm.nim b/src/backend/bytecode/vm.nim index 3baf998..7f2dd01 100644 --- a/src/backend/bytecode/vm.nim +++ b/src/backend/bytecode/vm.nim @@ -25,13 +25,13 @@ when not defined(gcArc) and not defined(gcOrc): import std/math +import std/strformat import std/segfaults import std/strutils import std/sets import std/monotimes when debugVM or debugMem or debugGC or debugAlloc: - import std/strformat import std/sequtils import std/terminal @@ -1065,8 +1065,12 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl: self.lastDebugCommand = "" try: self.dispatch() + except Defect as e: + stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}") + except CatchableError as e: + stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}") except NilAccessDefect: - stderr.writeLine("Memory Access Violation: SIGSEGV") + stderr.writeLine(&"Memory Access Violation (bytecode offset {self.ip}): SIGSEGV") quit(1) if not repl: # We clean up after ourselves! diff --git a/src/frontend/compiler/compiler.nim b/src/frontend/compiler/compiler.nim index ab8e2f3..ed1bc84 100644 --- a/src/frontend/compiler/compiler.nim +++ b/src/frontend/compiler/compiler.nim @@ -64,6 +64,7 @@ type isBuiltin*: bool case kind*: TypeKind: of Function: + nameObj*: Name isLambda*: bool isGenerator*: bool isCoroutine*: bool @@ -99,7 +100,7 @@ type ## A name enumeration type None, Module, Argument, Var, Function, CustomType, Enum - Name* = ref object of RootObj + Name* = ref object ## A generic name object # Type of the identifier (NOT of the value!) @@ -345,6 +346,22 @@ proc step*(self: Compiler): ASTNode {.inline.} = self.current += 1 +proc wrap*(self: Type): Type = + ## Wraps a type in a typevar if it's not already + ## wrapped + if self.kind != Typevar: + return Type(kind: Typevar, wrapped: self) + return self + + +proc unwrap*(self: Type): Type = + ## Unwraps a typevar if it's not already + ## unwrapped + if self.kind == Typevar: + return self.wrapped + return self + + # Peon's type inference and name resolution system is very flexible # and can be reused across multiple compilation backends @@ -420,25 +437,27 @@ proc compareUnions*(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): proc compare*(self: Compiler, a, b: Type): bool = ## Compares two type objects ## for equality - result = false + # Note: 'All' is a type internal to the peon # compiler that cannot be generated from user # code in any way. It's used mostly for matching # function return types (at least until we don't # have return type inference) and it matches any - # type, including nil (nim's nil, not our nil) + # type, including nil (nim's nil, aka no type at all, + # not peon's nil which still has a type) if a.isNil(): return b.isNil() or b.kind == All - elif b.isNil(): + if b.isNil(): return a.isNil() or a.kind == All - elif a.kind == All or b.kind == All: + if a.kind == All or b.kind == All: return true if a.kind == b.kind: # Here we compare types with the same kind discriminant case a.kind: of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32, Float64, - Char, Byte, String, Nil, TypeKind.Nan, Bool, TypeKind.Inf, Any: + Char, Byte, String, Nil, TypeKind.Nan, Bool, + TypeKind.Inf, Any, Auto: return true of Typevar: return self.compare(a.wrapped, b.wrapped) @@ -481,17 +500,21 @@ proc compare*(self: Compiler, a, b: Type): bool = return true else: discard # TODO: Custom types, enums - elif a.kind == Union: + if a.kind == Typevar: + return self.compare(a.wrapped, b) + if b.kind == Typevar: + return self.compare(a, b.wrapped) + if a.kind == Union: for constraint in a.types: if self.compare(constraint.kind, b) and constraint.match: return true return false - elif b.kind == Union: + if b.kind == Union: for constraint in b.types: if self.compare(constraint.kind, a) and constraint.match: return true return false - elif a.kind == Generic: + if a.kind == Generic: if a.asUnion: for constraint in a.cond: if self.compare(constraint.kind, b) and constraint.match: @@ -502,7 +525,7 @@ proc compare*(self: Compiler, a, b: Type): bool = if not self.compare(constraint.kind, b) or not constraint.match: return false return true - elif b.kind == Generic: + if b.kind == Generic: if b.asUnion: for constraint in b.cond: if self.compare(constraint.kind, a) and constraint.match: @@ -513,7 +536,7 @@ proc compare*(self: Compiler, a, b: Type): bool = if not self.compare(constraint.kind, a) or not constraint.match: return false return true - elif a.kind == Any or b.kind == Any: + if a.kind == Any or b.kind == Any: # Here we already know that neither of # these types are nil, so we can always # just return true @@ -526,47 +549,47 @@ proc toIntrinsic*(name: string): Type = ## type if it is valid and returns nil ## otherwise if name == "any": - return Type(kind: Any) + return Type(kind: Any, isBuiltin: true) elif name == "all": - return Type(kind: All) + return Type(kind: All, isBuiltin: true) elif name == "auto": - return Type(kind: Auto) + return Type(kind: Auto, isBuiltin: true) elif name in ["int", "int64", "i64"]: - return Type(kind: Int64) + return Type(kind: Int64, isBuiltin: true) elif name in ["uint64", "u64", "uint"]: - return Type(kind: UInt64) + return Type(kind: UInt64, isBuiltin: true) elif name in ["int32", "i32"]: - return Type(kind: Int32) + return Type(kind: Int32, isBuiltin: true) elif name in ["uint32", "u32"]: - return Type(kind: UInt32) + return Type(kind: UInt32, isBuiltin: true) elif name in ["int16", "i16", "short"]: - return Type(kind: Int16) + return Type(kind: Int16, isBuiltin: true) elif name in ["uint16", "u16"]: - return Type(kind: UInt16) + return Type(kind: UInt16, isBuiltin: true) elif name in ["int8", "i8"]: - return Type(kind: Int8) + return Type(kind: Int8, isBuiltin: true) elif name in ["uint8", "u8"]: - return Type(kind: UInt8) + return Type(kind: UInt8, isBuiltin: true) elif name in ["f64", "float", "float64"]: - return Type(kind: Float64) + return Type(kind: Float64, isBuiltin: true) elif name in ["f32", "float32"]: - return Type(kind: Float32) + return Type(kind: Float32, isBuiltin: true) elif name in ["byte", "b"]: - return Type(kind: Byte) + return Type(kind: Byte, isBuiltin: true) elif name in ["char", "c"]: - return Type(kind: Char) + return Type(kind: Char, isBuiltin: true) elif name == "nan": - return Type(kind: TypeKind.Nan) + return Type(kind: TypeKind.Nan, isBuiltin: true) elif name == "nil": - return Type(kind: Nil) + return Type(kind: Nil, isBuiltin: true) elif name == "inf": - return Type(kind: TypeKind.Inf) + return Type(kind: TypeKind.Inf, isBuiltin: true) elif name == "bool": - return Type(kind: Bool) + return Type(kind: Bool, isBuiltin: true) elif name == "typevar": - return Type(kind: Typevar) + return Type(kind: Typevar, isBuiltin: true) elif name == "string": - return Type(kind: String) + return Type(kind: String, isBuiltin: true) proc infer*(self: Compiler, node: LiteralExpr): Type = @@ -577,7 +600,7 @@ proc infer*(self: Compiler, node: LiteralExpr): Type = of intExpr, binExpr, octExpr, hexExpr: let size = node.token.lexeme.split("'") if size.len() == 1: - return Type(kind: Int64) + return Type(kind: Int64, isBuiltin: true) let typ = size[1].toIntrinsic() if not self.compare(typ, nil): return typ @@ -586,18 +609,18 @@ proc infer*(self: Compiler, node: LiteralExpr): Type = of floatExpr: let size = node.token.lexeme.split("'") if size.len() == 1: - return Type(kind: Float64) + return Type(kind: Float64, isBuiltin: true) let typ = size[1].toIntrinsic() if not typ.isNil(): return typ else: self.error(&"invalid type specifier '{size[1]}' for float", node) of trueExpr: - return Type(kind: Bool) + return Type(kind: Bool, isBuiltin: true) of falseExpr: - return Type(kind: Bool) + return Type(kind: Bool, isBuiltin: true) of strExpr: - return Type(kind: String) + return Type(kind: String, isBuiltin: true) else: discard # Unreachable @@ -847,6 +870,10 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'") result = impl[0] result.resolved = true + if result.kind == NameKind.Var: + # We found a function bound to a variable, + # so we return the original function's name object + result = result.valueType.nameObj 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) @@ -947,6 +974,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = if node.generics.len() > 0: fn.isGeneric = true self.names.add(fn) + fn.valueType.nameObj = fn self.prepareFunction(fn) n = fn of NodeKind.importStmt: diff --git a/src/frontend/compiler/targets/bytecode/target.nim b/src/frontend/compiler/targets/bytecode/target.nim index a1ccee4..4be5bee 100644 --- a/src/frontend/compiler/targets/bytecode/target.nim +++ b/src/frontend/compiler/targets/bytecode/target.nim @@ -106,7 +106,6 @@ method dispatchDelayedPragmas(self: BytecodeCompiler, name: Name) proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name) proc compileModule(self: BytecodeCompiler, module: Name) proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) -method prepareFunction(self: BytecodeCompiler, fn: Name) # End of forward declarations @@ -466,7 +465,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio "Identity": Identity }.to_table() if fn.builtinOp == "print": - var typ = self.inferOrError(args[0]) + var typ = self.inferOrError(args[0]).unwrap() case typ.kind: of Int64: self.emitByte(PrintInt64, line) @@ -799,9 +798,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = ## "Prepares" a function declaration by declaring ## its arguments and typechecking it - # First we declare the function's generics, if it has any. - # This is because the function's return type may in itself - # be a generic, so it needs to exist first + # First we declare the function's generics, if it has any var constraints: seq[tuple[match: bool, kind: Type]] = @[] for gen in fn.node.generics: self.unpackTypes(gen.cond, constraints) @@ -829,8 +826,9 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = self.error("cannot declare more than 16777215 variables at a time") inc(self.stackIndex) typ = self.inferOrError(argument.valueType) - if typ.kind == Typevar: - typ = typ.wrapped + if self.compare(typ, "auto".toIntrinsic()): + fn.valueType.isAuto = true + typ = "any".toIntrinsic() self.names.add(Name(depth: fn.depth + 1, isPrivate: true, owner: fn.owner, @@ -857,8 +855,8 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = # The function needs a return type too! if not node.returnType.isNil(): fn.valueType.returnType = self.inferOrError(node.returnType) - if fn.valueType.returnType.kind == Typevar: - fn.valueType.returnType = fn.valueType.returnType.wrapped + if self.compare(fn.valueType.returnType, "auto".toIntrinsic()): + fn.valueType.isAuto = true fn.position = self.stackIndex self.stackIndex = idx if node.isTemplate: @@ -1045,17 +1043,17 @@ method literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Typ ## as singletons, strings and numbers case node.kind: of trueExpr: - result = Type(kind: Bool) + result = "bool".toIntrinsic() if compile: self.emitByte(LoadTrue, node.token.line) of falseExpr: - result = Type(kind: Bool) + result = "bool".toIntrinsic() if compile: self.emitByte(LoadFalse, node.token.line) of strExpr: - result = Type(kind: String) + result = "string".toIntrinsic() if compile: - self.emitConstant(LiteralExpr(node), Type(kind: String)) + self.emitConstant(LiteralExpr(node), result) of intExpr: let y = IntExpr(node) let kind = self.infer(y) @@ -1159,7 +1157,7 @@ method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Typ method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} = ## Compiles all binary expressions var default: Expression - let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) + let fn = Type(kind: Function, returnType: "any".toIntrinsic(), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) var impl = self.match(node.token.lexeme, fn, node) result = impl.valueType if impl.isGeneric: @@ -1186,7 +1184,7 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com if s.kind == NameKind.CustomType: # This makes it so that the type of # a type comes out as "typevar" - result = Type(kind: Typevar, wrapped: result) + result = result.wrap() if not compile: return result var node = s.ident @@ -1209,29 +1207,16 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com # to have something on the stack to pop off (just to act as # a placeholder) self.emitByte(LoadNil, node.token.line) - elif s.valueType.isBuiltin: - case s.ident.token.lexeme: - of "nil": - self.emitByte(LoadNil, node.token.line) - of "nan": - self.emitByte(LoadNan, node.token.line) - of "inf": - self.emitByte(LoadInf, node.token.line) - else: - discard # Unreachable else: - if not s.belongsTo.isNil() and s.belongsTo.valueType.fun.kind == funDecl and FunDecl(s.belongsTo.valueType.fun).isTemplate: - discard + if s.depth > 0: + # Loads a regular variable from the current frame + self.emitByte(LoadVar, s.ident.token.line) else: - if s.depth > 0: - # Loads a regular variable from the current frame - self.emitByte(LoadVar, s.ident.token.line) - else: - # Loads a global variable from an absolute stack - # position - self.emitByte(LoadGlobal, s.ident.token.line) - # No need to check for -1 here: we already did a nil check above! - self.emitBytes(s.position.toTriple(), s.ident.token.line) + # Loads a global variable from an absolute stack + # position + self.emitByte(LoadGlobal, s.ident.token.line) + # No need to check for -1 here: we already did a nil check above! + self.emitBytes(s.position.toTriple(), s.ident.token.line) method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} = @@ -1332,7 +1317,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type case node.callee.kind: of NodeKind.identExpr: # Calls like hi() - var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node) + var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: "all".toIntrinsic(), args: args), node) result = impl.valueType if impl.isGeneric: result = self.specialize(impl.valueType, argExpr) @@ -1421,8 +1406,6 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type self.error(&"expression has no type", node) else: self.error(&"object of type '{self.stringify(typ)}' is not callable", node) - if not result.isNil() and result.kind == Typevar: - result = result.wrapped method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} = @@ -1626,7 +1609,7 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true proc ifStmt(self: BytecodeCompiler, node: IfStmt) = ## Compiles if/else statements for conditional ## execution of code - self.check(node.condition, Type(kind: Bool)) + self.check(node.condition, "bool".toIntrinsic()) self.expression(node.condition) let jump = self.emitJump(JumpIfFalsePop, node.token.line) self.statement(node.thenBranch) @@ -1640,7 +1623,7 @@ proc ifStmt(self: BytecodeCompiler, node: IfStmt) = proc whileStmt(self: BytecodeCompiler, node: WhileStmt) = ## Compiles C-style while loops and ## desugared C-style for loops - self.check(node.condition, Type(kind: Bool)) + self.check(node.condition, "bool".toIntrinsic()) let start = self.chunk.code.high() self.expression(node.condition) let jump = self.emitJump(JumpIfFalsePop, node.token.line) @@ -1686,7 +1669,7 @@ proc returnStmt(self: BytecodeCompiler, node: ReturnStmt) = elif not self.currentFunction.valueType.returnType.isNil() and node.value.isNil(): self.error("bare return statement is only allowed in void functions", node) if not node.value.isNil(): - if self.currentFunction.valueType.returnType.kind == Auto: + if self.compare(self.currentFunction.valueType.returnType, "auto".toIntrinsic()): self.currentFunction.valueType.returnType = self.inferOrError(node.value) self.check(node.value, self.currentFunction.valueType.returnType) self.expression(node.value) @@ -1852,7 +1835,7 @@ proc switchStmt(self: BytecodeCompiler, node: SwitchStmt) = self.emitByte(DupTop, branch.body.token.line) self.expression(branch.cond) # We look for a matching equality implementation - fn = Type(kind: Function, returnType: Type(kind: Bool), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)]) + fn = Type(kind: Function, returnType: "bool".toIntrinsic(), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)]) impl = self.match("==", fn, node) self.generateCall(impl, @[node.switch, branch.cond], impl.line) ifJump = self.emitJump(JumpIfFalsePop, branch.body.token.line) @@ -1934,9 +1917,13 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = if node.value.isNil(): # Variable has no value: the type declaration # takes over - if typ.kind == Auto: + # TODO: Implement T.default()! + if self.compare(typ, "auto".toIntrinsic()): self.error("automatic types require initialization", node) typ = self.inferOrError(node.valueType) + # One of the few exceptions where we actually don't want to use + # self.compare() is this one, because that will implicitly unwrap + # the typevar and compare the wrapped type, which is not what we want if typ.kind != Typevar: self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name) elif node.valueType.isNil(): @@ -1948,7 +1935,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = # a value: the value's type must match the # type declaration let expected = self.inferOrError(node.valueType) - if expected.kind != Auto: + if not self.compare(expected, "auto".toIntrinsic()): self.check(node.value, expected) # If this doesn't fail, then we're good typ = expected @@ -1956,7 +1943,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = # Let the compiler infer the type (this # is the default behavior already, but # some users may prefer to be explicit!) - typ = self.inferOrError(node.value) + typ = self.inferOrError(node.value) self.expression(node.value) self.emitByte(AddVar, node.token.line) inc(self.stackIndex) diff --git a/tests/auto.pn b/tests/auto.pn index 8d1d5de..33f410e 100644 --- a/tests/auto.pn +++ b/tests/auto.pn @@ -6,6 +6,7 @@ fn sum(a, b: auto): auto { return a + b; } + var x: auto = 1; print(x == 1); print(sum(1, 2) == 3); diff --git a/tests/cast.pn b/tests/cast.pn index ee1971e..0912a0d 100644 --- a/tests/cast.pn +++ b/tests/cast.pn @@ -1,6 +1,13 @@ import std; +# Note: unlike C-style casts, casting in peon tells the compiler +# "hey, please reinterpret the underlying bits of data of this thing +# as the type I'm telling you, trust me bro". There is no data conversion +# occurring whatsoever! For that, use converters (once they're implemented LoL) +print(cast[int](2.0) == 4611686018427387904); +print(cast[float](4611686018427387904) == 2.0); +# If that strikes your fancy, you can do this: var x = int; var caster = cast[x]; print(caster(2.0) == 4611686018427387904); diff --git a/tests/generics2.pn b/tests/generics2.pn index ceda138..e110d99 100644 --- a/tests/generics2.pn +++ b/tests/generics2.pn @@ -12,3 +12,4 @@ fn identity(x: int32): int32 { fn nope[T: int32 | int16](x: T): T { return identity(x); } +