diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 585d57c..41a9700 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -690,7 +690,7 @@ when debugVM: # So nim shuts up styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'") -proc dispatch*(self: var PeonVM) = +proc dispatch*(self: var PeonVM) {.inline.} = ## Main bytecode dispatch loop var instruction {.register.}: OpCode while true: @@ -764,10 +764,6 @@ proc dispatch*(self: var PeonVM) = # not needed there anymore discard self.pop() discard self.pop() - of ReplExit: - # Preserves the VM's state for the next - # execution. Used in the REPL - return of Return: # Returns from a function. # Every peon program is wrapped @@ -1079,14 +1075,16 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl: proc resume*(self: var PeonVM, chunk: Chunk) = ## Resumes execution of the given chunk (which - ## may have changed since the last call to run()). - ## No other state mutation occurs and all stacks as - ## well as other metadata are left intact. This should - ## not be used directly unless you know what you're - ## doing, as incremental compilation support is very - ## experimental and highly unstable - self.chunk = chunk + ## may have changed since the last call to run()). No other + ## state mutation occurs and all stacks as well as other + ## metadata are left intact. This should not be used directly + ## unless you know what you're doing, as incremental compilation + ## support is very experimental and highly unstable try: + self.chunk = chunk + when debugVM: + if self.breakpoints == @[0'u64]: + self.debugNext = true self.dispatch() except NilAccessDefect: stderr.writeLine("Memory Access Violation: SIGSEGV") diff --git a/src/frontend/compiler/compiler.nim b/src/frontend/compiler/compiler.nim index 136d2e1..a5064de 100644 --- a/src/frontend/compiler/compiler.nim +++ b/src/frontend/compiler/compiler.nim @@ -59,6 +59,9 @@ type Type* = ref object ## A wrapper around ## compile-time types + + # Is this type a builtin? + isBuiltin*: bool case kind*: TypeKind: of Function: isLambda*: bool @@ -86,6 +89,9 @@ type name*: string of Union: types*: seq[tuple[match: bool, kind: Type]] + of Typevar: + # What type do we represent? + wrapped*: Type else: discard @@ -141,8 +147,6 @@ type # Has the compiler generated this name internally or # does it come from user code? isReal*: bool - # Is this name a builtin? - isBuiltin*: bool ## BACKEND-SPECIFIC FIELDS @@ -234,6 +238,7 @@ proc getSource*(self: Compiler): string {.inline.} = self.source ## implemented in the same module). They are methods because we need to dispatch to their actual specific ## implementations inside each target module, so we need the runtime type of the compiler object to be ## taken into account +method makeConcrete(self: Compiler, node: GenericExpr, compile: bool = true): Type {.base.} = nil method expression*(self: Compiler, node: Expression, compile: bool = true): Type {.discardable, base.} = nil method identifier*(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable, base.} = nil method call*(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable, base.} = nil @@ -258,7 +263,7 @@ method dispatchDelayedPragmas(self: Compiler, name: Name) {.base.} = discard ## Utility functions proc `$`*(self: Name): string = $(self[]) -proc `$`(self: Type): string = $(self[]) +proc `$`*(self: Type): string = $(self[]) proc hash(self: Name): Hash = self.ident.token.lexeme.hash() @@ -421,20 +426,22 @@ method compare*(self: Compiler, a, b: Type): bool = # 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 + # type, including nil (nim's nil, not our nil) if a.isNil(): return b.isNil() or b.kind == All elif b.isNil(): return a.isNil() or a.kind == All elif a.kind == All or b.kind == All: return true - elif a.kind == b.kind: + 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: return true + of Typevar: + return self.compare(a.wrapped, b.wrapped) of Union: return self.compareUnions(a.types, b.types) of Generic: @@ -455,36 +462,22 @@ method compare*(self: Compiler, a, b: Type): bool = return false var i = 0 for (argA, argB) in zip(a.args, b.args): - # When we compare functions with forward - # declarations, or forward declarations - # between each other, we need to be more - # strict (as in: check argument names and - # their default values, any pragma associated - # with the function, and whether they are pure) - if a.forwarded: - if b.forwarded: - if argA.name != argB.name: - return false - else: - if argB.name == "": - # An empty argument name means - # we crafted this type object - # manually, so we don't need - # to match the argument name - continue - if argA.name != argB.name: - return false - elif b.forwarded: - if a.forwarded: - if argA.name != argB.name: - return false - else: - if argA.name == "": - continue - if argA.name != argB.name: - return false + if argA.name == "": + continue + if argB.name == "": + continue + if argA.name != argB.name: + return false if not self.compare(argA.kind, argB.kind): return false + if a.forwarded or b.forwarded: + # We need to be more strict when checking forward + # declarations + if b.fun.pragmas.len() != a.fun.pragmas.len(): + return false + for (pragA, pragB) in zip(a.fun.pragmas, b.fun.pragmas): + if pragA != pragB: + return false return true else: discard # TODO: Custom types, enums @@ -615,6 +608,8 @@ method infer*(self: Compiler, node: Expression): Type = if node.isNil(): return nil case node.kind: + of NodeKind.genericExpr: + result = self.makeConcrete(GenericExpr(node), compile=false) of NodeKind.identExpr: result = self.identifier(IdentExpr(node), compile=false, strict=false) of NodeKind.unaryExpr: @@ -659,16 +654,30 @@ method stringify*(self: Compiler, typ: Type): string = of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32, Float64, Char, Byte, String, Nil, TypeKind.Nan, Bool, - TypeKind.Inf, Auto: + TypeKind.Inf, Auto, Any: result &= ($typ.kind).toLowerAscii() + of Typevar: + result = self.stringify(typ.wrapped) of Pointer: result &= &"ptr {self.stringify(typ.value)}" of Reference: result &= &"ref {self.stringify(typ.value)}" of Function: - result &= "fn (" + result &= "fn " + if typ.fun.generics.len() > 0: + result &= "[" + for i, gen in typ.fun.generics: + result &= &"{gen.name.token.lexeme}: {self.stringify(self.inferOrError(gen.cond))}" + if i < typ.fun.generics.len() - 1: + result &= ", " + result &= "]" + result &= "(" for i, (argName, argType, argDefault) in typ.args: - result &= &"{argName}: {self.stringify(argType)}" + result &= &"{argName}: " + if argType.kind == Generic: + result &= argType.name + else: + result &= self.stringify(argType) if not argDefault.isNil(): result &= &" = {argDefault}" if i < typ.args.len() - 1: @@ -690,8 +699,6 @@ method stringify*(self: Compiler, typ: Type): string = result &= ", " else: result &= "}" - of Any: - return "any" of Union: for i, condition in typ.types: if i > 0: @@ -725,9 +732,10 @@ method 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 - ## module, regardless of whether they are exported to - ## the current one or not + ## empty string, returns all public names within the + ## given module, regardless of whether they are exported + ## to the current one or not (this is used at import time + ## and for the export statement) if name == "": for obj in reversed(self.names): if obj.owner.isNil(): @@ -765,13 +773,14 @@ proc check*(self: Compiler, term: Expression, kind: Type) {.inline.} = let k = self.inferOrError(term) if not self.compare(k, kind): self.error(&"expecting value of type {self.stringify(kind)}, got {self.stringify(k)}", term) - elif k.kind == Any and kind.kind != Any: + if k.kind == Any and kind.kind notin [Any, Generic]: self.error(&"any is not a valid type in this context") proc isAny*(typ: Type): bool = ## Returns true if the given type is - ## of (or contains) the any type + ## of (or contains) the any type. Not + ## applicable to typevars case typ.kind: of Any: return true @@ -791,10 +800,7 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all ## Tries to find a matching function implementation ## compatible with the given type and returns its ## name object - var impl: seq[Name] = @[] - for obj in self.findByName(name): - if self.compare(kind, obj.valueType): - impl.add(obj) + var impl: seq[Name] = self.findByType(name, kind) if impl.len() == 0: let names = self.findByName(name) var msg = &"failed to find a suitable implementation for '{name}'" @@ -812,6 +818,8 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all msg &= &": wrong number of arguments (expected {name.valueType.args.len()}, got {kind.args.len()})" else: for i, arg in kind.args: + if arg.name != "" and name.valueType.args[i].name != "" and arg.name != name.valueType.args[i].name: + msg &= &": unexpected argument '{arg.name}' at position {i + 1}" if not self.compare(arg.kind, name.valueType.args[i].kind): msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.valueType.args[i].kind)}, got {self.stringify(arg.kind)})" break @@ -838,6 +846,7 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all 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.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'") result = impl[0] + result.resolved = true 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) @@ -849,56 +858,36 @@ proc beginScope*(self: Compiler) = inc(self.depth) -proc unpackGenerics*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = +proc unpackTypes*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = ## Recursively unpacks a type constraint in a generic type case condition.kind: of identExpr: - list.add((accept, self.inferOrError(condition))) - if list[^1].kind.kind == Auto: + var typ = self.inferOrError(condition) + if typ.kind != Typevar: + self.error(&"expecting a type name, got value of type {self.stringify(typ)} instead", condition) + typ = typ.wrapped + if typ.kind == Auto: self.error("automatic types cannot be used within generics", condition) + list.add((accept, typ)) of binaryExpr: let condition = BinaryExpr(condition) case condition.operator.lexeme: of "|": - self.unpackGenerics(condition.a, list) - self.unpackGenerics(condition.b, list) + self.unpackTypes(condition.a, list) + self.unpackTypes(condition.b, list) else: self.error("invalid type constraint in generic declaration", condition) of unaryExpr: let condition = UnaryExpr(condition) case condition.operator.lexeme: of "~": - self.unpackGenerics(condition.a, list, accept=false) + self.unpackTypes(condition.a, list, accept=false) else: self.error("invalid type constraint in generic declaration", condition) else: self.error("invalid type constraint in generic declaration", condition) -proc unpackUnion*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = - ## Recursively unpacks a type union - case condition.kind: - of identExpr: - list.add((accept, self.inferOrError(condition))) - of binaryExpr: - let condition = BinaryExpr(condition) - case condition.operator.lexeme: - of "|": - self.unpackUnion(condition.a, list) - self.unpackUnion(condition.b, list) - else: - self.error("invalid type constraint in type union", condition) - of unaryExpr: - let condition = UnaryExpr(condition) - case condition.operator.lexeme: - of "~": - self.unpackUnion(condition.a, list, accept=false) - else: - self.error("invalid type constraint in type union", condition) - else: - self.error("invalid type constraint in type union", condition) - - proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = ## Statically declares a name into the current scope. ## "Declaring" a name only means updating our internal @@ -957,19 +946,6 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = fn.valueType.compiled = true if node.generics.len() > 0: fn.isGeneric = true - var typ: Type - for argument in node.arguments: - typ = self.infer(argument.valueType) - if not typ.isNil() and typ.kind == Auto: - fn.valueType.isAuto = true - if fn.isGeneric: - self.error("automatic types cannot be used within generics", argument.valueType) - break - typ = self.infer(node.returnType) - if not typ.isNil() and typ.kind == Auto: - fn.valueType.isAuto = true - if fn.isGeneric: - self.error("automatic types cannot be used within generics", node.returnType) self.names.add(fn) self.prepareFunction(fn) n = fn @@ -1017,10 +993,13 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = case node.value.kind: of identExpr: n.valueType = self.inferOrError(node.value) + if n.valueType.kind == Typevar: + # Type alias! + n.valueType = n.valueType.wrapped of binaryExpr: # Type union n.valueType = Type(kind: Union, types: @[]) - self.unpackUnion(node.value, n.valueType.types) + self.unpackTypes(node.value, n.valueType.types) else: discard else: @@ -1043,7 +1022,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = continue if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]: if name.depth < n.depth: - self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n) + self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope ({name.owner.file}:{name.ident.token.line}:{name.ident.token.relPos.start})", node=n.ident) if name.owner != n.owner: - self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n) + self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}:{name.ident.token.line}:{name.ident.token.relPos.start})", node=n.ident) return n diff --git a/src/frontend/compiler/targets/bytecode/opcodes.nim b/src/frontend/compiler/targets/bytecode/opcodes.nim index 0213af3..a98d578 100644 --- a/src/frontend/compiler/targets/bytecode/opcodes.nim +++ b/src/frontend/compiler/targets/bytecode/opcodes.nim @@ -201,7 +201,6 @@ type SysClock64, # Pushes the output of a monotonic clock on the stack LoadTOS, # Pushes the top of the call stack onto the operand stack DupTop, # Duplicates the top of the operand stack onto the operand stack - ReplExit, # Exits the VM immediately, leaving its state intact. Used in the REPL LoadGlobal # Loads a global variable @@ -282,7 +281,6 @@ const simpleInstructions* = {Return, LoadNil, Float32GreaterOrEqual, Float32LessOrEqual, DupTop, - ReplExit, Identity } diff --git a/src/frontend/compiler/targets/bytecode/target.nim b/src/frontend/compiler/targets/bytecode/target.nim index 2215d89..a1ccee4 100644 --- a/src/frontend/compiler/targets/bytecode/target.nim +++ b/src/frontend/compiler/targets/bytecode/target.nim @@ -466,7 +466,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio "Identity": Identity }.to_table() if fn.builtinOp == "print": - let typ = self.inferOrError(args[0]) + var typ = self.inferOrError(args[0]) case typ.kind: of Int64: self.emitByte(PrintInt64, line) @@ -528,6 +528,13 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio let jump = self.emitJump(JumpIfFalseOrPop, line) self.expression(args[1]) self.patchJump(jump) + of "cast": + # Type casts are a merely compile-time construct: + # they don't produce any code at runtime because + # the underlying data representation does not change! + # The only reason why there's a "cast" pragma is to + # make it so that the peon stub can have no body + discard else: self.error(&"unknown built-in: '{fn.builtinOp}'", fn.fun) @@ -543,6 +550,9 @@ proc patchForwardDeclarations(self: BytecodeCompiler) = if forwarded.isPrivate != impl.isPrivate: self.error(&"implementation of '{impl.ident.token.lexeme}' has a mismatching visibility modifier from its forward declaration", impl.ident) if position == 0: + # Forward declaration created by funDecl (it's + # necessary to make sure that there's no unimplemented + # forward declarations) continue pos = impl.codePos.toLong() self.chunk.consts[position] = pos[0] @@ -585,7 +595,7 @@ proc endScope(self: BytecodeCompiler) = if name.kind notin [NameKind.Var, NameKind.Argument]: continue elif name.kind == NameKind.Argument and not name.belongsTo.isNil(): - if name.belongsTo.isBuiltin: + if name.belongsTo.valueType.isBuiltin: # Arguments to builtin functions become temporaries on the # stack and are popped automatically continue @@ -607,7 +617,7 @@ proc endScope(self: BytecodeCompiler) = 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.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved: + if not name.belongsTo.isNil() and not name.belongsTo.valueType.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved: # 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 @@ -639,56 +649,6 @@ proc endScope(self: BytecodeCompiler) = inc(idx) -proc unpackGenerics(self: BytecodeCompiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = - ## Recursively unpacks a type constraint in a generic type - case condition.kind: - of identExpr: - list.add((accept, self.inferOrError(condition))) - if list[^1].kind.kind == Auto: - self.error("automatic types cannot be used within generics", condition) - of binaryExpr: - let condition = BinaryExpr(condition) - case condition.operator.lexeme: - of "|": - self.unpackGenerics(condition.a, list) - self.unpackGenerics(condition.b, list) - else: - self.error("invalid type constraint in generic declaration", condition) - of unaryExpr: - let condition = UnaryExpr(condition) - case condition.operator.lexeme: - of "~": - self.unpackGenerics(condition.a, list, accept=false) - else: - self.error("invalid type constraint in generic declaration", condition) - else: - self.error("invalid type constraint in generic declaration", condition) - - -proc unpackUnion(self: BytecodeCompiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = - ## Recursively unpacks a type union - case condition.kind: - of identExpr: - list.add((accept, self.inferOrError(condition))) - of binaryExpr: - let condition = BinaryExpr(condition) - case condition.operator.lexeme: - of "|": - self.unpackUnion(condition.a, list) - self.unpackUnion(condition.b, list) - else: - self.error("invalid type constraint in type union", condition) - of unaryExpr: - let condition = UnaryExpr(condition) - case condition.operator.lexeme: - of "~": - self.unpackUnion(condition.a, list, accept=false) - else: - self.error("invalid type constraint in type union", condition) - else: - self.error("invalid type constraint in type union", condition) - - proc emitLoop(self: BytecodeCompiler, begin: int, line: int) = ## Emits a JumpBackwards instruction with the correct ## jump offset @@ -717,19 +677,20 @@ proc handleMagicPragma(self: BytecodeCompiler, pragma: Pragma, name: Name) = ## Handles the "magic" pragma. Assumes the given name is already ## declared if pragma.args.len() != 1: - self.error("'magic' pragma: wrong number of arguments") + self.error(&"'magic' pragma: wrong number of arguments (expected 1, got {len(pragma.args)})") elif pragma.args[0].kind != strExpr: - self.error("'magic' pragma: wrong type of argument (constant string expected)") + self.error(&"'magic' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))})") elif name.node.kind == NodeKind.funDecl: - name.isBuiltin = true + name.valueType.isBuiltin = true name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2] + name.valueType.compiled = true elif name.node.kind == NodeKind.typeDecl: name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic() if name.valueType.kind == All: - self.error("don't even think about it (compiler-chan is angry at you)", pragma) + self.error("don't even think about it (compiler-chan is angry at you :/)", pragma) if name.valueType.isNil(): self.error("'magic' pragma: wrong argument value", pragma.args[0]) - name.isBuiltin = true + name.valueType.isBuiltin = true else: self.error("'magic' pragma is not valid in this context") @@ -814,6 +775,9 @@ proc generateCall(self: BytecodeCompiler, fn: Type, args: seq[Expression], line: ## instead of Name objects (used for lambdas and ## consequent calls). The function's address is ## assumed to be on the stack + if fn.isBuiltin: + self.handleBuiltinFunction(fn, args, line) + return self.emitByte(LoadUInt64, line) self.emitBytes(self.chunk.writeConstant(0.toLong()), line) let pos = self.chunk.consts.len() - 8 @@ -840,7 +804,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = # be a generic, so it needs to exist first var constraints: seq[tuple[match: bool, kind: Type]] = @[] for gen in fn.node.generics: - self.unpackGenerics(gen.cond, constraints) + self.unpackTypes(gen.cond, constraints) self.names.add(Name(depth: fn.depth + 1, isPrivate: true, valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints), @@ -857,19 +821,23 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = let idx = self.stackIndex self.stackIndex = 1 var default: Expression - var node = FunDecl(fn.node) + let node = FunDecl(fn.node) var i = 0 + var typ: Type for argument in node.arguments: if self.names.high() > 16777215: 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 self.names.add(Name(depth: fn.depth + 1, isPrivate: true, owner: fn.owner, file: fn.file, isConst: false, ident: argument.name, - valueType: if not fn.valueType.isAuto: self.inferOrError(argument.valueType) else: Type(kind: Any), + valueType: typ, codePos: 0, isLet: false, line: argument.name.token.line, @@ -881,14 +849,16 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = )) if node.arguments.high() - node.defaults.high() <= node.arguments.high(): # There's a default argument! - fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType, node.defaults[i])) + fn.valueType.args.add((self.names[^1].ident.token.lexeme, typ, node.defaults[i])) inc(i) else: # This argument has no default - fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType, default)) + fn.valueType.args.add((self.names[^1].ident.token.lexeme, typ, default)) # The function needs a return type too! - if not FunDecl(fn.node).returnType.isNil(): - fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType) + 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 fn.position = self.stackIndex self.stackIndex = idx if node.isTemplate: @@ -941,8 +911,7 @@ proc prepareAutoFunction(self: BytecodeCompiler, fn: Name, args: seq[tuple[name: proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) = ## Small wrapper that abstracts emitting a call instruction ## for a given function - self.dispatchDelayedPragmas(fn) - if fn.isBuiltin: + if fn.valueType.isBuiltin: self.handleBuiltinFunction(fn.valueType, args, line) return case fn.kind: @@ -969,15 +938,13 @@ proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: proc specialize(self: BytecodeCompiler, typ: Type, args: seq[Expression]): Type {.discardable.} = - ## Specializes a generic type. - ## Used for typechecking at the - ## call site + ## Instantiates a generic type var mapping: TableRef[string, Type] = newTable[string, Type]() var kind: Type result = deepCopy(typ) case result.kind: of TypeKind.Function: - # This first loop checks if a user tries to reassign a generic's + # This loop checks if a user tries to reassign a generic's # name to a different type for i, (name, typ, default) in result.args: if typ.kind != Generic: @@ -990,6 +957,17 @@ proc specialize(self: BytecodeCompiler, typ: Type, args: seq[Expression]): Type if not result.returnType.isNil() and result.returnType.kind == Generic: if result.returnType.name in mapping: result.returnType = mapping[result.returnType.name] + elif mapping.len() == 0: + # The function has no generic arguments, + # just a generic return type + var typ: Type + for i, gen in result.fun.generics: + if gen.name.token.lexeme == result.returnType.name: + typ = result.args[i].kind + break + if typ.isNil(): + self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun) + result.returnType = typ else: self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun) else: @@ -1000,12 +978,10 @@ proc terminateProgram(self: BytecodeCompiler, pos: int) = ## Utility to terminate a peon program self.patchForwardDeclarations() self.endScope() - if self.replMode: - self.emitByte(ReplExit, self.peek().token.line) - else: - self.emitByte(OpCode.Return, self.peek().token.line) - self.emitByte(0, self.peek().token.line) # Entry point has no return value - self.patchReturnAddress(pos) + + self.emitByte(OpCode.Return, self.peek().token.line) + self.emitByte(0, self.peek().token.line) # Entry point has no return value + self.patchReturnAddress(pos) proc beginProgram(self: BytecodeCompiler): int = @@ -1207,6 +1183,10 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com if s.isNil() and not strict: return nil result = s.valueType + 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) if not compile: return result var node = s.ident @@ -1224,7 +1204,12 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com # they're referenced self.emitByte(LoadUInt64, node.token.line) self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line) - elif s.isBuiltin: + elif s.kind == NameKind.CustomType: + # Types have no runtime representation either, but we need + # 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) @@ -1284,6 +1269,44 @@ method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)") +method makeConcrete(self: BytecodeCompiler, node: GenericExpr, compile: bool = true): Type = + ## Builds a concrete type from the given generic + ## instantiation + var name = self.resolveOrError(node.ident) + if not name.isGeneric: + self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required") + var fun = FunDecl(name.node) + if fun.generics.len() != node.args.len(): + self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()} instead)") + var concrete = deepCopy(name.valueType) + var types: seq[Type] = @[] + var map = newTable[string, Type]() + for arg in node.args: + types.add(self.inferOrError(arg)) + if types[^1].kind != Typevar: + self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])} instead", arg) + for (gen, value) in zip(fun.generics, node.args): + map[gen.name.token.lexeme] = self.inferOrError(value) + for i, argument in concrete.args: + if argument.kind.kind != Generic: + continue + elif argument.name in map: + concrete.args[i].kind = map[argument.name] + else: + self.error(&"unknown generic argument name '{argument.name}'", concrete.fun) + if not concrete.returnType.isNil() and concrete.returnType.kind == Generic: + if concrete.returnType.name in map: + concrete.returnType = map[concrete.returnType.name] + else: + self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun) + if compile: + # Types don't exist at runtime, but if you want to + # assign them to variables then you need *something* + # to pop off the stack, so we just push a nil + self.emitByte(LoadNil, node.token.line) + result = concrete + + method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type {.discardable.} = ## Compiles function calls var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[] @@ -1321,7 +1344,9 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type if not impl.valueType.compiled: self.funDecl(FunDecl(result.fun), impl) result = result.returnType + self.dispatchDelayedPragmas(impl) if compile: + # Lambdas can't be templates :P if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate: for arg in reversed(argExpr): self.expression(arg) @@ -1357,7 +1382,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type self.generateCall(result, argExpr, node.token.line) result = result.returnType of NodeKind.getItemExpr: - var node = GetItemExpr(node.callee) + let node = GetItemExpr(node.callee) result = self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))) var fn: Name # getItemExpr returns a Type object, but @@ -1375,17 +1400,29 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type if compile: self.generateCall(fn, argExpr, node.token.line) of NodeKind.lambdaExpr: + # Calling a lambda var node = LambdaExpr(node.callee) - var impl = self.lambdaExpr(node, compile=compile) + let impl = self.lambdaExpr(node, compile=compile) result = impl.returnType if compile: self.generateCall(impl, argExpr, node.token.line) + of NodeKind.genericExpr: + # Instantiating a generic type + let node = GenericExpr(node.callee) + let concrete = self.makeConcrete(node) + var impl = self.resolve(node.ident).deepCopy() + impl.valueType = concrete + result = impl.valueType.returnType + if compile: + self.generateCall(impl, argExpr, node.token.line) else: let typ = self.infer(node) if typ.isNil(): 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.} = @@ -1547,6 +1584,8 @@ method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} = ## Compiles all expressions case node.kind: + of NodeKind.genericExpr: + return self.makeConcrete(GenericExpr(node)) of NodeKind.callExpr: return self.call(CallExpr(node), compile) of NodeKind.getItemExpr: @@ -1796,7 +1835,7 @@ proc namedBlock(self: BytecodeCompiler, node: NamedBlockStmt) = proc switchStmt(self: BytecodeCompiler, node: SwitchStmt) = - ## Compiles C-style switch statements + ## Compiles switch statements self.expression(node.switch) let typeOfA = self.inferOrError(node.switch) var ifJump: int = -1 @@ -1895,9 +1934,11 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = if node.value.isNil(): # Variable has no value: the type declaration # takes over - typ = self.inferOrError(node.valueType) if typ.kind == Auto: self.error("automatic types require initialization", node) + typ = self.inferOrError(node.valueType) + if typ.kind != Typevar: + self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name) elif node.valueType.isNil(): # Variable has no type declaration: the type # of its value takes over @@ -1915,7 +1956,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.infer(node.value) + typ = self.inferOrError(node.value) self.expression(node.value) self.emitByte(AddVar, node.token.line) inc(self.stackIndex) @@ -1945,7 +1986,7 @@ proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name) = self.forwarded.add((name, 0)) name.valueType.forwarded = true return - if name.isBuiltin: + if name.valueType.isBuiltin: # Builtins are handled at call time return self.currentFunction = name @@ -2087,8 +2128,6 @@ proc compile*(self: BytecodeCompiler, ast: seq[Declaration], file: string, lines else: self.ast = ast self.current = 0 - if not incremental: - self.stackIndex = 1 self.lines = lines self.source = source self.isMainModule = isMainModule @@ -2099,9 +2138,8 @@ proc compile*(self: BytecodeCompiler, ast: seq[Declaration], file: string, lines if not incremental: self.jumps = @[] self.modules = newTable[string, Name]() + self.stackIndex = 1 let pos = self.beginProgram() - let idx = self.stackIndex - self.stackIndex = idx while not self.done(): self.declaration(Declaration(self.step())) self.terminateProgram(pos) diff --git a/src/frontend/parsing/ast.nim b/src/frontend/parsing/ast.nim index f911785..d06fcae 100644 --- a/src/frontend/parsing/ast.nim +++ b/src/frontend/parsing/ast.nim @@ -79,6 +79,7 @@ type pragmaExpr, refExpr, ptrExpr, + genericExpr, switchStmt # Here I would've rather used object variants, and in fact that's what was in @@ -155,6 +156,11 @@ type name: IdentExpr, value: Expression]]] closeParen*: Token # Needed for error reporting + GenericExpr* = ref object of Expression + ident*: IdentExpr + args*: seq[Expression] + + UnaryExpr* = ref object of Expression operator*: Token a*: Expression @@ -187,7 +193,7 @@ type ends*: seq[Expression] AssignExpr* = ref object of Expression - name*: Expression + name*: IdentExpr value*: Expression ExprStmt* = ref object of Statement @@ -459,6 +465,13 @@ proc newCallExpr*(callee: Expression, arguments: tuple[positionals: seq[ result.token = token +proc newGenericExpr*(ident: IdentExpr, args: seq[Expression]): GenericExpr = + result = GenericExpr(kind: genericExpr) + result.ident = ident + result.args = args + result.token = ident.token + + proc newSliceExpr*(expression: Expression, ends: seq[Expression], token: Token): SliceExpr = result = SliceExpr(kind: sliceExpr) result.expression = expression @@ -487,7 +500,7 @@ proc newYieldExpr*(expression: Expression, token: Token): YieldExpr = result.token = token -proc newAssignExpr*(name: Expression, value: Expression, +proc newAssignExpr*(name: IdentExpr, value: Expression, token: Token): AssignExpr = result = AssignExpr(kind: assignExpr) result.name = name @@ -786,6 +799,9 @@ proc `$`*(self: ASTNode): string = result &= &"Ref({Ref(self).value})" of ptrExpr: result &= &"Ptr({Ptr(self).value})" + of genericExpr: + var self = GenericExpr(self) + result &= &"Generic(ident={self.ident}, args={self.args})" else: discard @@ -843,6 +859,13 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] = stop = self.token.relPos.stop + 1 # -8 so the error highlights the #pragma[ part as well result = (self.token.relPos.start - 8, stop) + of genericExpr: + var self = GenericExpr(self) + let ident = getRelativeBoundaries(self.ident) + var stop: int = ident.stop + 2 + if self.args.len() > 0: + stop = getRelativeBoundaries(self.args[^1]).stop + result = (ident.start, stop) else: result = (0, 0) \ No newline at end of file diff --git a/src/frontend/parsing/parser.nim b/src/frontend/parsing/parser.nim index 06a5dc7..77d99f0 100644 --- a/src/frontend/parsing/parser.nim +++ b/src/frontend/parsing/parser.nim @@ -418,11 +418,12 @@ proc makeCall(self: Parser, callee: Expression): CallExpr = self.error("can not pass more than 255 arguments in function call") break argument = self.expression() - if argument.kind == binaryExpr and BinaryExpr(argument).operator.lexeme == "=": - if IdentExpr(BinaryExpr(argument).a) in argNames: - self.error("duplicate keyword argument in function call is not allowed") - argNames.add(IdentExpr(BinaryExpr(argument).a)) - arguments.keyword.add((name: IdentExpr(BinaryExpr(argument).a), value: BinaryExpr(argument).b)) + if argument.kind == assignExpr: + var assign = AssignExpr(argument) + if assign.name in argNames: + self.error("duplicate keyword arguments are not allowed", assign.name.token) + argNames.add(assign.name) + arguments.keyword.add((name: assign.name, value: assign.value)) elif arguments.keyword.len() == 0: arguments.positionals.add(argument) else: @@ -436,10 +437,18 @@ proc makeCall(self: Parser, callee: Expression): CallExpr = result.closeParen = self.peek(-1) -proc parseGenericArgs(self: Parser) = - ## Parses function generic arguments - ## like function[type](arg) - discard # TODO +proc parseGenericArgs(self: Parser): Expression = + ## Parses expressions like someType[someGeneric] + ## that are needed to instantiate generics + var item = newIdentExpr(self.peek(-2), self.scopeDepth) + var types: seq[Expression] = @[] + while not self.check(RightBracket) and not self.done(): + self.expect(Identifier) + types.add(newIdentExpr(self.peek(-1), self.scopeDepth)) + if not self.match(Comma): + break + self.expect(RightBracket) + return newGenericExpr(item, types) proc call(self: Parser): Expression = @@ -454,8 +463,9 @@ proc call(self: Parser): Expression = 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) + if self.peek(-2).kind != Identifier: + self.error("invalid syntax") + result = self.parseGenericArgs() else: break @@ -564,7 +574,7 @@ proc parseAssign(self: Parser): Expression = var value = self.expression() case result.kind: of identExpr, sliceExpr: - result = newAssignExpr(result, value, tok) + result = newAssignExpr(IdentExpr(result), value, tok) result.file = self.file of getItemExpr: result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok) @@ -839,7 +849,7 @@ proc tryStmt(self: Parser): Statement = var elseClause: Statement while self.match(Except): if self.match(LeftBrace): - handlers.add((body: self.blockStmt(), exc: newIdentExpr(self.peek(-1)))) + handlers.add((body: self.blockStmt(), exc: newIdentExpr(self.peek(-1), self.scopeDepth))) else: self.expect(Identifier, "expecting exception name after 'except'") self.expect(LeftBrace, "expecting '{' after exception name") diff --git a/src/main.nim b/src/main.nim index 844af95..26461ed 100644 --- a/src/main.nim +++ b/src/main.nim @@ -50,7 +50,7 @@ proc getLineEditor: LineEditor = let history = result.plugHistory() result.bindHistory(history) - +#[ proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, breakpoints: seq[uint64] = @[]) = styledEcho fgMagenta, "Welcome into the peon REPL!" var @@ -104,7 +104,7 @@ proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: Comp for node in tree: styledEcho fgGreen, "\t", $node echo "" - compiled = compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=true) + discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=first) if debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, "stdin") @@ -152,6 +152,7 @@ proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: Comp file = relativePath(file, getCurrentDir()) stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}") quit(0) +]# proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], @@ -219,6 +220,9 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "the selected backend is not implemented yet") elif backend == PeonBackend.Bytecode: serialized = serializer.loadFile(f) + if debugCompiler: + styledEcho fgCyan, "Compilation step:\n" + debugger.disassembleChunk(serialized.chunk, f) if backend == PeonBackend.Bytecode and debugSerializer: styledEcho fgCyan, "Serialization step: " styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch @@ -415,6 +419,7 @@ when isMainModule: if breaks.len() == 0 and debugVM: breaks.add(0) if file == "": - repl(warnings, mismatches, mode, breaks) + echo "Sorry, the REPL is currently broken :(" + #repl(warnings, mismatches, mode, breaks) else: runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, backend, output) diff --git a/src/peon/stdlib/builtins/values.pn b/src/peon/stdlib/builtins/values.pn index 16fffc4..73df018 100644 --- a/src/peon/stdlib/builtins/values.pn +++ b/src/peon/stdlib/builtins/values.pn @@ -68,6 +68,12 @@ type auto* = object { #pragma[magic: "auto"] } + +type typevar* = object { + #pragma[magic: "typevar"] +} + + # Some convenience aliases type int* = int64; type float* = float64; diff --git a/src/peon/stdlib/std.pn b/src/peon/stdlib/std.pn index 8d98299..906efb1 100644 --- a/src/peon/stdlib/std.pn +++ b/src/peon/stdlib/std.pn @@ -21,4 +21,9 @@ var test* = 0x60; fn testGlobals*: bool { return version == 1 and _private == 5 and test == 0x60; +} + + +fn cast*[T: any](x: any): T { + #pragma[magic: "cast"] } \ No newline at end of file diff --git a/src/util/fmterr.nim b/src/util/fmterr.nim index 1f2f2be..7028f79 100644 --- a/src/util/fmterr.nim +++ b/src/util/fmterr.nim @@ -49,9 +49,9 @@ proc print*(exc: CompileError) = var contents: string if file notin ["", "", "stdin"]: file = relativePath(exc.file, getCurrentDir()) - contents = readFile(file).splitLines()[exc.line - 1].strip(chars={'\n'}) + contents = readFile(file).strip(chars={'\n'}).splitLines()[exc.line - 1] else: - contents = exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) + contents = exc.compiler.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1] printError(file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function, exc.msg) @@ -62,7 +62,7 @@ proc print*(exc: ParseError) = var file = exc.file if file notin ["", ""]: file = relativePath(exc.file, getCurrentDir()) - printError(file, exc.parser.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}), + printError(file, exc.parser.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1], exc.line, exc.token.relPos, exc.parser.getCurrentFunction(), exc.msg) @@ -73,6 +73,6 @@ proc print*(exc: LexingError) = var file = exc.file if file notin ["", ""]: file = relativePath(exc.file, getCurrentDir()) - printError(file, exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}), + printError(file, exc.lexer.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1], exc.line, exc.pos, nil, exc.msg)