diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 6b4204e..5987eb1 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -181,7 +181,7 @@ proc mark(self: ptr HeapObject): bool = return true -proc markRoots(self: PeonGC): seq[ptr HeapObject] = +proc markRoots(self: PeonGC): seq[ptr HeapObject] = ## Marks root objects *not* to be ## collected by the GC and returns ## their addresses @@ -433,7 +433,7 @@ proc peekc(self: PeonVM, distance: int = 0): uint64 {.used.} = return self.calls[self.calls.high() + distance] -proc getc(self: PeonVM, idx: int): uint64 = +proc getc(self: PeonVM, idx: int): uint64 = ## Getter method that abstracts ## indexing our call stack through ## stack frames @@ -474,7 +474,7 @@ proc popClosure(self: PeonVM, idx: int): uint64 = # Byte-level primitives to read and decode # bytecode -proc readByte(self: PeonVM): uint8 = +proc readByte(self: PeonVM): uint8 = ## Reads a single byte from the ## bytecode and returns it as an ## unsigned 8 bit integer @@ -482,7 +482,7 @@ proc readByte(self: PeonVM): uint8 = return self.chunk.code[self.ip - 1] -proc readShort(self: PeonVM): uint16 = +proc readShort(self: PeonVM): uint16 = ## Reads two bytes from the ## bytecode and returns them ## as an unsigned 16 bit @@ -490,7 +490,7 @@ proc readShort(self: PeonVM): uint16 = return [self.readByte(), self.readByte()].fromDouble() -proc readLong(self: PeonVM): uint32 = +proc readLong(self: PeonVM): uint32 = ## Reads three bytes from the ## bytecode and returns them ## as an unsigned 32 bit @@ -500,7 +500,7 @@ proc readLong(self: PeonVM): uint32 = return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple()) -proc readUInt(self: PeonVM): uint32 = +proc readUInt(self: PeonVM): uint32 = ## Reads three bytes from the ## bytecode and returns them ## as an unsigned 32 bit @@ -511,7 +511,7 @@ proc readUInt(self: PeonVM): uint32 = # Functions to read primitives from the chunk's # constants table -proc constReadInt64(self: PeonVM, idx: int): int64 = +proc constReadInt64(self: PeonVM, idx: int): int64 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int64 @@ -523,7 +523,7 @@ proc constReadInt64(self: PeonVM, idx: int): int64 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt64(self: PeonVM, idx: int): uint64 = +proc constReadUInt64(self: PeonVM, idx: int): uint64 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint64 @@ -535,7 +535,7 @@ proc constReadUInt64(self: PeonVM, idx: int): uint64 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt32(self: PeonVM, idx: int): uint32 = +proc constReadUInt32(self: PeonVM, idx: int): uint32 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int32 @@ -544,7 +544,7 @@ proc constReadUInt32(self: PeonVM, idx: int): uint32 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt32(self: PeonVM, idx: int): int32 = +proc constReadInt32(self: PeonVM, idx: int): int32 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint32 @@ -553,7 +553,7 @@ proc constReadInt32(self: PeonVM, idx: int): int32 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt16(self: PeonVM, idx: int): int16 = +proc constReadInt16(self: PeonVM, idx: int): int16 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int16 @@ -561,7 +561,7 @@ proc constReadInt16(self: PeonVM, idx: int): int16 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt16(self: PeonVM, idx: int): uint16 = +proc constReadUInt16(self: PeonVM, idx: int): uint16 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint16 @@ -569,14 +569,14 @@ proc constReadUInt16(self: PeonVM, idx: int): uint16 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt8(self: PeonVM, idx: int): int8 = +proc constReadInt8(self: PeonVM, idx: int): int8 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int8 result = int8(self.chunk.consts[idx]) -proc constReadUInt8(self: PeonVM, idx: int): uint8 = +proc constReadUInt8(self: PeonVM, idx: int): uint8 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint8 @@ -628,7 +628,7 @@ when debugVM: # So nim shuts up stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[") for i, e in self.calls: stdout.styledWrite(fgYellow, $e) - if i < self.calls.len(): + if i < self.calls.high(): stdout.styledWrite(fgYellow, ", ") styledEcho fgMagenta, "]" if self.operands.len() !> 0: @@ -724,7 +724,7 @@ proc dispatch*(self: PeonVM) = # Calls a peon function. The calling convention here # is pretty simple: the first value in the frame is # the new instruction pointer to jump to, then a - # 32-bit return address follows. After that, all + # 64-bit return address follows. After that, all # arguments and locals follow. Note that, due to # how the stack works, all arguments before the call # are in the reverse order in which they are passed @@ -780,10 +780,6 @@ proc dispatch*(self: PeonVM) = # in a hidden function, so this # will also exit the VM if we're # at the end of the program - while self.calls.len().uint64 !> self.frames[^1] + 2'u64: - # Discards the function's local variables, - # if there is any - discard self.popc() let ret = self.popc() # Return address discard self.popc() # Function address if self.readByte() == 1: @@ -796,7 +792,12 @@ proc dispatch*(self: PeonVM) = if self.frames.len() == 0: # End of the program! return - self.ip = ret.uint + # We change the instruction + # pointer just now because + # if we did it beforehand, + # our readByte() call would've + # read from the wrong offset + self.ip = ret of SetResult: # Sets the result of the # current function. A Return @@ -1023,6 +1024,8 @@ proc dispatch*(self: PeonVM) = # cannot be converted to a date. The number # is in seconds self.push(cast[uint64](getMonoTime().ticks().float() / 1_000_000_000)) + of LogicalNot: + self.push(uint64(not self.pop().bool)) else: discard diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 93156e0..658d2ac 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -53,10 +53,10 @@ type mutable: bool case kind: TypeKind: of Function: - name: string isLambda: bool isGenerator: bool isCoroutine: bool + isGeneric: bool args: seq[tuple[name: string, kind: Type]] returnType: Type isBuiltinFunction: bool @@ -66,10 +66,17 @@ type envLen: int children: seq[Type] parent: Type + retJumps: seq[int] + of CustomType: + fields: TableRef[string, Type] of Reference, Pointer: value: Type of Generic: - cond: Expression + # cond represents a type constraint. For + # example, fn foo[T: int & !uint](...) {...} + # would map to [(true, int), (false, uint)] + cond: seq[tuple[match: bool, kind: Type]] + name: string else: discard @@ -79,12 +86,17 @@ export bytecode type + NameKind {.pure.} = enum + ## A name enumeration type + None, Argument, Var, Function, Type Name = ref object ## A compile-time wrapper around ## statically resolved names # Name of the identifier name: IdentExpr + # Type of the identifier (NOT of the value!) + kind: NameKind # Owner of the identifier (module) owner: string # Scope depth @@ -102,16 +114,16 @@ type codePos: int # The function that owns this variable (may be nil!) belongsTo: Name - # Is this a function argument? - isFunctionArgument: bool # Where is this node declared in the file? line: int # Has this name been closed over? isClosedOver: bool - # Is this a function declaration or a variable - # with a function as value? (The distinction *is* - # important! Check emitFunction()) - isFunDecl: bool + # Has this name been referenced at least once? + resolved: bool + # The AST node associated with this node. This + # is needed because we compile declarations only + # if they're actually used + node: Declaration Loop = object ## A "loop object" used ## by the compiler to emit @@ -125,7 +137,7 @@ type depth: int # Absolute jump offsets into our bytecode that we need to # patch. Used for break statements - breakPos: seq[int] + breakJumps: seq[int] Compiler* = ref object ## A wrapper around the Peon compiler's state @@ -180,7 +192,7 @@ type # List of closed-over variables closedOver: seq[Name] # Compiler procedures called by pragmas - compilerProcs: TableRef[string, proc (self: Compiler, pragma: Pragma, node: ASTNode)] + compilerProcs: TableRef[string, proc (self: Compiler, pragma: Pragma, node: ASTNode, name: Name)] # Stores line data lines: seq[tuple[start, stop: int]] # The source of the current module @@ -206,19 +218,21 @@ proc statement(self: Compiler, node: Statement) proc declaration(self: Compiler, node: Declaration) proc peek(self: Compiler, distance: int = 0): ASTNode proc identifier(self: Compiler, node: IdentExpr) -proc varDecl(self: Compiler, node: VarDecl) -proc matchImpl(self: Compiler, name: string, kind: Type): Name +proc varDecl(self: Compiler, node: VarDecl, name: Name) +proc matchImpl(self: Compiler, name: string, kind: Type, node: ASTNode = nil): Name proc infer(self: Compiler, node: LiteralExpr): Type proc infer(self: Compiler, node: Expression): Type proc findByName(self: Compiler, name: string): seq[Name] proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[Name] proc compare(self: Compiler, a, b: Type): bool proc patchReturnAddress(self: Compiler, pos: int) -proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode) -proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode) -proc dispatchPragmas(self: Compiler, node: ASTnode) -proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) +proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode, name: Name) +proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode, name: Name) +proc dispatchPragmas(self: Compiler, node: ASTnode, name: Name) +proc funDecl(self: Compiler, node: FunDecl, name: Name) +proc typeDecl(self: Compiler, node: TypeDecl, name: Name) proc compileModule(self: Compiler, filename: string) +proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) # End of forward declarations @@ -236,7 +250,7 @@ proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Com result.enableOptimizations = enableOptimizations result.replMode = replMode result.currentModule = "" - result.compilerProcs = newTable[string, proc (self: Compiler, pragma: Pragma, node: ASTNode)]() + result.compilerProcs = newTable[string, proc (self: Compiler, pragma: Pragma, node: ASTNode, name: Name)]() result.compilerProcs["magic"] = handleMagicPragma result.compilerProcs["pure"] = handlePurePragma result.source = "" @@ -256,6 +270,7 @@ proc getRelPos*(self: Compiler, line: int): tuple[start, stop: int] = self.lines ## Utility functions proc `$`*(self: Name): string = $self[] +proc `$`(self: Type): string = $self[] proc peek(self: Compiler, distance: int = 0): ASTNode = @@ -461,23 +476,38 @@ proc resolve(self: Compiler, name: IdentExpr, ## nil when the name can't be found. This function ## has no concept of scope depth, because getStackPos ## does that job. Note that private names declared in - ## other modules will not be resolved! + ## other modules will not be resolved! Another important + ## thing to remember is that if a name is being resolved + ## for the first time, calling this function will also + ## cause its declaration to be compiled for obj in reversed(self.names): if obj.name.token.lexeme == name.token.lexeme: if obj.isPrivate and obj.owner != self.currentModule: continue # There may be a name in the current module that # matches, so we skip this + if not obj.resolved: + obj.resolved = true + obj.codePos = self.chunk.code.len() + case obj.kind: + of NameKind.Var: + self.varDecl(VarDecl(obj.node), obj) + of NameKind.Type: + self.typeDecl(TypeDecl(obj.node), obj) + else: + discard # Functions need to be compiled at the call site return obj return nil proc getStackPos(self: Compiler, name: Name): int = - ## Returns the predicted call stack position of a given name, relative - ## to the current frame + ## Returns the predicted call stack position of a + ## given name, relative to the current frame var found = false result = 2 for variable in self.names: - if variable.isFunDecl or variable.valueType.kind in {CustomType, Generic}: + if variable.kind notin [NameKind.Var, NameKind.Argument]: + continue + elif not variable.belongsTo.isNil() and variable.belongsTo.valueType.isBuiltinFunction: continue if name == variable: found = true @@ -501,23 +531,6 @@ proc getClosurePos(self: Compiler, name: Name): int = return -1 -proc resolve(self: Compiler, name: string, - depth: int = self.scopeDepth): Name = - ## Traverses self.names backwards and returns the - ## first name object with the given name. Returns - ## nil when the name can't be found. This function - ## has no concept of scope depth, because getStackPos - ## does that job. Note that private names declared in - ## other modules will not be resolved! - for obj in reversed(self.names): - if obj.name.token.lexeme == name: - if obj.isPrivate and obj.owner != self.currentModule: - continue # There may be a name in the current module that - # matches, so we skip this - return obj - return nil - - proc compare(self: Compiler, a, b: Type): bool = ## Compares two type objects @@ -536,50 +549,87 @@ proc compare(self: Compiler, a, b: Type): bool = # since peon doesn't have return type # inference return true - elif a.kind == Generic or b.kind == Generic: - # Matching generic argument types - return true - elif a.kind != b.kind: + elif a.kind != b.kind and not (a.kind == Generic or b.kind == Generic): # Next, we see the type discriminant: # If they're different, then they can't - # be the same type! + # be the same type! For generics, we match + # those later, as we need access to the type + # discriminant inside a case statement return false - case a.kind: - # If all previous checks pass, it's time - # to go through each possible type peon - # supports and compare it - of Int8, UInt8, Int16, UInt16, Int32, - UInt32, Int64, UInt64, Float32, Float64, - Char, Byte, String, Nil, Nan, Bool, Inf: - # A value type's type is always equal to - # another one's - return true - of Reference, Pointer: - # Here we already know that both - # a and b are of either of the two - # types in this branch, so we just need - # to compare their values - return self.compare(a.value, b.value) - of Function: - # Functions are a bit trickier - if a.args.len() != b.args.len(): - return false - elif not self.compare(a.returnType, b.returnType): - return false - for (argA, argB) in zip(a.args, b.args): - if not self.compare(argA.kind, argB.kind): + if a.kind != Generic and b.kind != Generic: + case a.kind: + # If all previous checks pass, it's time + # to go through each possible type peon + # supports and compare it + of Int8, UInt8, Int16, UInt16, Int32, + UInt32, Int64, UInt64, Float32, Float64, + Char, Byte, String, Nil, Nan, Bool, Inf: + # A value type's type is always equal to + # another one's + return true + of Reference, Pointer: + # Here we already know that both + # a and b are of either of the two + # types in this branch, so we just need + # to compare their values + return self.compare(a.value, b.value) + of Function: + # Functions are a bit trickier + if a.args.len() != b.args.len(): return false - return true - else: - # TODO: Custom types - discard + if a.isClosure != b.isClosure: + return false + if a.isCoroutine != b.isCoroutine: + return false + elif not self.compare(a.returnType, b.returnType): + return false + for (argA, argB) in zip(a.args, b.args): + if not self.compare(argA.kind, argB.kind): + return false + return true + else: + discard # TODO: Custom types + else: + case a.kind: + of Generic: + # Generic types + case b.kind: + of Generic: + for c1 in a.cond: + for c2 in b.cond: + if self.compare(c1.kind, c2.kind): + return c1.match == c2.match + else: + for constraint in a.cond: + if self.compare(constraint.kind, b): + return constraint.match + else: + discard + case b.kind: + of Generic: + # Generic types + case a.kind: + of Generic: + for c1 in a.cond: + for c2 in b.cond: + if self.compare(c1.kind, c2.kind): + return c1.match == c2.match + else: + for constraint in b.cond: + if self.compare(constraint.kind, a): + return constraint.match + else: + discard + return false proc toIntrinsic(name: string): Type = ## Converts a string to an intrinsic ## type if it is valid and returns nil ## otherwise - if name in ["int", "int64", "i64"]: + if name == "all": + return Type(kind: Any) + elif name in ["int", "int64", "i64"]: return Type(kind: Int64) elif name in ["uint64", "u64", "uint"]: return Type(kind: UInt64) @@ -621,6 +671,7 @@ proc toIntrinsic(name: string): Type = proc infer(self: Compiler, node: LiteralExpr): Type = ## Infers the type of a given literal expression + ## (if the expression is nil, nil is returned) if node.isNil(): return nil case node.kind: @@ -642,7 +693,7 @@ proc infer(self: Compiler, node: LiteralExpr): Type = if size.len() == 1 or size[1] == "f64": return Type(kind: Float64) let typ = size[1].toIntrinsic() - if not self.compare(typ, nil): + if not typ.isNil(): return typ else: self.error(&"invalid type specifier '{size[1]}' for float") @@ -664,7 +715,8 @@ proc infer(self: Compiler, node: LiteralExpr): Type = proc infer(self: Compiler, node: Expression): Type = ## Infers the type of a given expression and - ## returns it + ## returns it (if the node is nil, nil is + ## returned) if node.isNil(): return nil case node.kind: @@ -673,14 +725,16 @@ proc infer(self: Compiler, node: Expression): Type = let name = self.resolve(node) if not name.isNil(): result = name.valueType + if result.kind == Generic: + result = self.resolve(newIdentExpr(Token(lexeme: result.name, kind: Identifier))).valueType else: result = node.name.lexeme.toIntrinsic() of unaryExpr: let node = UnaryExpr(node) - return self.matchImpl(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a))])).valueType.returnType + return self.matchImpl(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a))]), node).valueType.returnType of binaryExpr: let node = BinaryExpr(node) - return self.matchImpl(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a)), ("", self.infer(node.b))])).valueType.returnType + return self.matchImpl(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a)), ("", self.infer(node.b))]), node).valueType.returnType of {intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, infExpr, nanExpr, floatExpr, nilExpr @@ -723,28 +777,6 @@ proc infer(self: Compiler, node: Expression): Type = discard # Unreachable -proc infer(self: Compiler, node: Declaration, strictMutable: bool = true): Type = - ## Infers the type of a given declaration - ## and returns it - if node.isNil(): - return nil - case node.kind: - of NodeKind.funDecl: - var node = FunDecl(node) - let resolved = self.resolve(node.name) - if not resolved.isNil(): - return resolved.valueType - of NodeKind.varDecl: - var node = VarDecl(node) - let resolved = self.resolve(node.name) - if not resolved.isNil(): - return resolved.valueType - else: - return self.infer(node.value, strictMutable) - else: - return # Unreachable - - proc typeToStr(self: Compiler, typ: Type): string = ## Returns the string representation of a ## type object @@ -773,7 +805,12 @@ proc typeToStr(self: Compiler, typ: Type): string = if not typ.returnType.isNil(): result &= &": {self.typeToStr(typ.returnType)}" of Generic: - result = &"T: {typ.cond}" + for i, condition in typ.cond: + if i > 0: + result &= " | " + if not condition.match: + result &= "~" + result &= self.typeToStr(condition.kind) else: discard @@ -791,9 +828,10 @@ proc findByName(self: Compiler, name: string): seq[Name] = proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[Name] = ## Looks for objects that have already been declared ## with the given name and type. If depth is not -1, - ## it also compares the name's scope depth + ## it also compares the name's scope depth. Returns + ## all objects that apply for obj in self.findByName(name): - if self.compare(obj.valueType, kind) and depth == -1 or depth == obj.depth: + if self.compare(obj.valueType, kind) and (depth == -1 or depth == obj.depth): result.add(obj) @@ -806,7 +844,7 @@ proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] {.used.} = result.add(obj) -proc matchImpl(self: Compiler, name: string, kind: Type): Name = +proc matchImpl(self: Compiler, name: string, kind: Type, node: ASTNode = nil): Name = ## Tries to find a matching function implementation ## compatible with the given type and returns its ## name object @@ -833,16 +871,16 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name = elif not self.compare(arg.kind, name.valueType.args[i].kind): msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead" break - self.error(msg) + self.error(msg, node) elif impl.len() > 1: var msg = &"multiple matching implementations of '{name}' found:\n" for fn in reversed(impl): - msg &= &"- '{fn.name.token.lexeme}' in module '{fn.owner}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n" - self.error(msg) - return impl[0] + msg &= &"- in module '{fn.owner}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n" + self.error(msg, node) + result = impl[0] -proc check(self: Compiler, term: Expression, kind: Type) = +proc check(self: Compiler, term: Expression, kind: Type, allowAny: bool = false) = ## Checks the type of term against a known type. ## Raises an error if appropriate and returns ## otherwise @@ -853,14 +891,17 @@ proc check(self: Compiler, term: Expression, kind: Type) = elif term.kind == callExpr and CallExpr(term).callee.kind == identExpr: self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'", term) self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type", term) + elif k.kind == Any and not allowAny: + # Any should only be used internally: error! + self.error("'all' is not a valid type in this context", term) elif not self.compare(k, kind): self.error(&"expecting value of type '{self.typeToStr(kind)}', got '{self.typeToStr(k)}' instead", term) -proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = +proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line: int) = ## Emits instructions for builtin functions ## such as addition or subtraction - if fn.valueType.builtinOp notin ["LogicalOr", "LogicalAnd"]: + if fn.builtinOp notin ["LogicalOr", "LogicalAnd"]: if len(args) == 2: self.expression(args[1]) self.expression(args[0]) @@ -917,117 +958,27 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = "PrintNan": PrintNan, "PrintInf": PrintInf, "PrintString": PrintString, - "SysClock64": SysClock64 + "SysClock64": SysClock64, + "LogicalNot": LogicalNot }.to_table() - if fn.valueType.builtinOp in codes: - self.emitByte(codes[fn.valueType.builtinOp], fn.line) + if fn.builtinOp in codes: + self.emitByte(codes[fn.builtinOp], line) return # Some builtin operations are slightly more complex # so we handle them separately - case fn.valueType.builtinOp: + case fn.builtinOp: of "LogicalOr": self.expression(args[0]) - let jump = self.emitJump(JumpIfTrue, fn.line) + let jump = self.emitJump(JumpIfTrue, line) self.expression(args[1]) self.patchJump(jump) of "LogicalAnd": self.expression(args[0]) - var jump = self.emitJump(JumpIfFalseOrPop, fn.line) + var jump = self.emitJump(JumpIfFalseOrPop, line) self.expression(args[1]) self.patchJump(jump) else: - self.error(&"unknown built-in: '{fn.valueType.builtinOp}'", fn.valueType.fun) - - -proc emitFunction(self: Compiler, name: Name) = - ## Wrapper to emit LoadFunction instructions - if name.isFunDecl: - self.emitByte(LoadInt64, name.line) - self.emitBytes(self.chunk.writeConstant(name.codePos.toLong()), name.line) - # If we're not loading a statically declared - # function, then it must be a function object - # created by previous LoadFunction instructions - # that is now bound to some variable, so we just - # load it - elif self.scopeDepth > 0 and not self.currentFunction.isNil() and name.depth != self.scopeDepth: - self.emitByte(LoadClosure, name.line) - self.emitBytes(self.getClosurePos(name).toTriple(), name.line) - self.emitByte(LoadVar, name.line) - self.emitBytes(self.getStackPos(name).toTriple(), name.line) - else: - self.emitByte(LoadVar, name.line) - self.emitBytes(self.getStackPos(name).toTriple(), name.line) - - -proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) = - ## Version of generateCall that takes Type objects - ## instead of Name objects. The function is assumed - ## to be on the stack - self.emitByte(LoadUInt32, line) - self.emitBytes(self.chunk.writeConstant(0.toQuad()), line) - let pos = self.chunk.consts.len() - 4 - for i, argument in reversed(args): - # We pass the arguments in reverse - # because of how stacks work. They'll - # be reversed again at runtime - self.check(argument, fn.args[^(i + 1)].kind) - self.expression(argument) - # 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.emitBytes(fn.args.len().toTriple(), line) - if fn.isClosure: - self.emitBytes(fn.envLen.toTriple(), line) - self.patchReturnAddress(pos) - - -proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) = - ## Small wrapper that abstracts emitting a call instruction - ## for a given function - if fn.valueType.isBuiltinFunction: - # Builtins map to individual instructions - # (usually 1, but some use more) so we handle - # them differently - self.handleBuiltinFunction(fn, args) - return - if not onStack: - # If we're not calling a function - # whose instruction pointer's is - # already on the stack, we emit it - self.emitFunction(fn) - # We initially emit a dummy return - # address. It is patched later - self.emitByte(LoadUInt32, fn.line) - self.emitBytes(self.chunk.writeConstant(0.toQuad()), fn.line) - let pos = self.chunk.consts.len() - 4 - for i, argument in reversed(args): - # We pass the arguments in reverse - # because of how stacks work. They'll - # be reversed again at runtime - if onStack: - self.check(argument, fn.valueType.args[^i].kind) - self.expression(argument) - # Creates a new call frame and jumps - # to the function's first instruction - # in the code - if not fn.valueType.isClosure: - self.emitByte(Call, fn.line) - else: - self.emitByte(CallClosure, fn.line) - self.emitBytes(fn.valueType.args.len().toTriple(), fn.line) - if fn.valueType.isClosure: - self.emitBytes(fn.valueType.envLen.toTriple(), fn.line) - self.patchReturnAddress(pos) - - -proc checkCallIsPure(self: Compiler, node: ASTnode): bool = - ## Checks if a call has any side effects. Returns - ## true if it doesn't and false otherwise - return true # TODO + self.error(&"unknown built-in: '{fn.builtinOp}'", fn.fun) proc beginScope(self: Compiler) = @@ -1037,9 +988,6 @@ proc beginScope(self: Compiler) = self.scopeOwners.add((self.currentFunction, self.scopeDepth)) -proc `$`(self: Type): string = $self[] - - # Flattens our weird function tree into a linear # list proc flattenImpl(self: Type, to: var seq[Type]) = @@ -1056,21 +1004,22 @@ proc endScope(self: Compiler) = ## Ends the current local scope if self.scopeDepth < 0: self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") - dec(self.scopeDepth) discard self.scopeOwners.pop() + dec(self.scopeDepth) var names: seq[Name] = @[] var popCount = 0 for name in self.names: if name.depth > self.scopeDepth: names.add(name) - if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl and name.depth > 0: - # We don't increase the pop count for these kinds of objects + if name.kind in [NameKind.Var, NameKind.Argument]: + # We don't increase the pop count for some kinds of objects # because they're not stored the same way as regular variables - # (for types, generics and function declarations). We also don't - # pop for local scopes because the VM takes care of closing a function's - # stack frame - inc(popCount) - if name.isFunDecl and name.valueType.children.len() > 0 and name.depth == 0: + # (for types, generics and function declarations) + if name.belongsTo.isNil() or not name.belongsTo.valueType.isBuiltinFunction: + # We don't pop arguments to builtin functions because those don't + # actually have scopes: their arguments are temporaries on the stack + inc(popCount) + elif name.kind == NameKind.Function and name.valueType.children.len() > 0 and name.depth == 0: # When a closure goes out of scope, its environment is reclaimed. # This includes the environments of every other closure that may # have been contained within it, too @@ -1127,7 +1076,35 @@ proc endScope(self: Compiler) = inc(idx) -proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = + +proc unpackGenerics(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: + let name = self.infer(condition) + if name.isNil(): + self.error(&"cannot infer type of '{IdentExpr(condition).token.lexeme}' in generic declaration", condition) + list.add((accept, name)) + of binaryExpr: + let condition = BinaryExpr(condition) + case condition.operator.lexeme: + of "|", "or": + 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 "~", "not": + 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 declareName(self: Compiler, node: Declaration, mutable: bool = false): Name = ## Statically declares a name into the current scope. ## "Declaring" a name only means updating our internal ## list of identifiers so that further calls to resolve() @@ -1143,10 +1120,10 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = # slap myself 100 times with a sign saying "I'm dumb". Mark my words self.error("cannot declare more than 16777215 variables at a time") for name in self.findByName(node.name.token.lexeme): - if name.depth == self.scopeDepth and not name.isFunctionArgument: - # Trying to redeclare a variable in the same scope/context is an error, but it's okay - # if it's a function argument (for example, if you want to copy a number to - # mutate it) + if name.kind == NameKind.Var and name.depth == self.scopeDepth: + # Trying to redeclare a variable in the same scope is an error, but it's okay + # if it's something else, like a function argument (for example, if you want to copy a + # number to mutate it) self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}") self.names.add(Name(depth: self.scopeDepth, name: node.name, @@ -1154,42 +1131,49 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = owner: self.currentModule, isConst: node.isConst, valueType: self.infer(node.value), - codePos: self.chunk.code.len(), isLet: node.isLet, line: node.token.line, - belongsTo: self.currentFunction + belongsTo: self.currentFunction, + kind: NameKind.Var, + node: node )) if mutable: self.names[^1].valueType.mutable = true + return self.names[^1] of NodeKind.funDecl: var node = FunDecl(node) - # We declare the generics before the function so we - # can refer to them later - for gen in node.generics: - self.names.add(Name(depth: self.scopeDepth + 1, - isPrivate: true, - isConst: false, - owner: self.currentModule, - line: node.token.line, - valueType: Type(kind: Generic, mutable: false, cond: gen.cond), - name: gen.name)) - self.names.add(Name(depth: self.scopeDepth, - isPrivate: node.isPrivate, - isConst: false, - owner: self.currentModule, - valueType: Type(kind: Function, - name: node.name.token.lexeme, - returnType: self.infer(node.returnType), - args: @[], - fun: node, - children: @[]), - codePos: self.chunk.code.len(), - name: node.name, - isLet: false, - line: node.token.line, - isFunDecl: true, - )) - let fn = self.names[^1] + var generics: seq[Name] = @[] + if node.generics.len() > 0: + # We declare the generics before the function so we + # can refer to them later + var constraints: seq[tuple[match: bool, kind: Type]] + for gen in node.generics: + constraints = @[] + self.unpackGenerics(gen.cond, constraints) + self.names.add(Name(depth: self.scopeDepth + 1, + isPrivate: true, + isConst: false, + owner: self.currentModule, + line: node.token.line, + valueType: Type(kind: Generic, name: gen.name.token.lexeme, mutable: false, cond: constraints), + name: gen.name, + node: node)) + generics.add(self.names[^1]) + let fn = Name(depth: self.scopeDepth, + isPrivate: node.isPrivate, + isConst: false, + owner: self.currentModule, + valueType: Type(kind: Function, + returnType: self.infer(node.returnType), + args: @[], + fun: node, + children: @[]), + name: node.name, + node: node, + isLet: false, + line: node.token.line, + kind: NameKind.Function) + self.names.add(fn) var name: Name for argument in node.arguments: if self.names.high() > 16777215: @@ -1206,8 +1190,8 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = codePos: 0, isLet: false, line: argument.name.token.line, - isFunctionArgument: true, - belongsTo: fn + belongsTo: fn, + kind: NameKind.Argument ) self.names.add(name) name.valueType = self.infer(argument.valueType) @@ -1215,6 +1199,9 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = if name.valueType.isNil(): self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name) fn.valueType.args.add((argument.name.token.lexeme, name.valueType)) + if generics.len() > 0: + fn.valueType.isGeneric = true + return fn else: discard # TODO: Types, enums @@ -1235,11 +1222,11 @@ proc patchBreaks(self: Compiler) = ## because the size of code ## to skip is not known before ## the loop is fully compiled - for brk in self.currentLoop.breakPos: + for brk in self.currentLoop.breakJumps: self.patchJump(brk) -proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTNode) = +proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTNode, name: Name) = ## Handles the "magic" pragma. Assumes the given name is already ## declared if pragma.args.len() != 1: @@ -1249,14 +1236,13 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTNode) = elif node.kind != NodeKind.funDecl: self.error("'magic' pragma is not valid in this context") var node = FunDecl(node) - var fn = self.resolve(node.name) - fn.valueType.isBuiltinFunction = true - fn.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2] + name.valueType.isBuiltinFunction = true + name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2] # The magic pragma ignores the function's body node.body = nil -proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTNode) = +proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTNode, name: Name) = ## Handles the "pure" pragma case node.kind: of NodeKind.funDecl: @@ -1267,7 +1253,7 @@ proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTNode) = self.error("'pure' pragma is not valid in this context") -proc dispatchPragmas(self: Compiler, node: ASTnode) = +proc dispatchPragmas(self: Compiler, node: ASTnode, name: Name) = ## Dispatches pragmas bound to objects var pragmas: seq[Pragma] = @[] case node.kind: @@ -1280,31 +1266,21 @@ proc dispatchPragmas(self: Compiler, node: ASTnode) = for pragma in pragmas: if pragma.name.token.lexeme notin self.compilerProcs: self.error(&"unknown pragma '{pragma.name.token.lexeme}'") - self.compilerProcs[pragma.name.token.lexeme](self, pragma, node) - - -proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Name = - ## Specializes generic arguments in functions - var fn = name.deepCopy() - result = fn - var typ: Type - for i in 0..args.high(): - if fn.valueType.args[i].kind.kind == Generic: - typ = self.infer(args[i]) - fn.valueType.args[i].kind = typ - self.resolve(fn.valueType.args[i].name).valueType = typ - if fn.valueType.args[i].kind.isNil(): - self.error(&"cannot specialize generic function: argument {i + 1} has no type") + self.compilerProcs[pragma.name.token.lexeme](self, pragma, node, name) proc patchReturnAddress(self: Compiler, pos: int) = ## Patches the return address of a function ## call - let address = self.chunk.code.len().toQuad() + let address = self.chunk.code.len().toLong() self.chunk.consts[pos] = address[0] self.chunk.consts[pos + 1] = address[1] self.chunk.consts[pos + 2] = address[2] self.chunk.consts[pos + 3] = address[3] + self.chunk.consts[pos + 4] = address[4] + self.chunk.consts[pos + 5] = address[5] + self.chunk.consts[pos + 6] = address[6] + self.chunk.consts[pos + 7] = address[7] proc terminateProgram(self: Compiler, pos: int, terminateScope: bool = true) = @@ -1342,20 +1318,19 @@ proc beginProgram(self: Compiler, incremental: bool = false): int = isLet: false, owner: self.currentModule, valueType: Type(kind: Function, - name: "", returnType: nil, args: @[], ), codePos: 12, # Jump address is hardcoded name: newIdentExpr(Token(lexeme: "", kind: Identifier)), - isFunDecl: true, + kind: NameKind.Function, line: -1) self.names.add(main) self.scopeOwners.add((main, 0)) - self.emitByte(LoadInt64, 1) + self.emitByte(LoadUInt64, 1) self.emitBytes(self.chunk.writeConstant(main.codePos.toLong()), 1) - self.emitByte(LoadUInt32, 1) - self.emitBytes(self.chunk.writeConstant(0.toQuad()), 1) + self.emitByte(LoadUInt64, 1) + self.emitBytes(self.chunk.writeConstant(0.toLong()), 1) self.emitByte(Call, 1) self.emitBytes(0.toTriple(), 1) result = 5 @@ -1451,27 +1426,27 @@ proc literal(self: Compiler, node: ASTNode) = proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) = ## Emits the code to call a unary operator - self.generateCall(fn, @[op.a]) + self.generateCall(fn, @[op.a], fn.line) proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) = ## Emits the code to call a binary operator - self.generateCall(fn, @[op.a, op.b]) + self.generateCall(fn, @[op.a, op.b], fn.line) proc unary(self: Compiler, node: UnaryExpr) = ## Compiles unary expressions such as decimal ## and bitwise negation let valueType = self.infer(node.a) - let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)])) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), node) self.callUnaryOp(funct, node) proc binary(self: Compiler, node: BinaryExpr) = - ## Compiles all binary expressions + ## Compiles all binary expression let typeOfA = self.infer(node.a) let typeOfB = self.infer(node.b) - let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)])) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]), node) self.callBinaryOp(funct, node) @@ -1485,11 +1460,11 @@ proc identifier(self: Compiler, node: IdentExpr) = # no matter the scope depth self.emitConstant(node, self.infer(node)) else: - if s.valueType.kind == Function and s.isFunDecl: + if s.valueType.kind == Function and s.kind == NameKind.Function: # Functions have no runtime # representation: they're just # a location to jump to - self.emitByte(LoadInt64, node.token.line) + self.emitByte(LoadUInt64, node.token.line) self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line) elif self.scopeDepth > 0 and not self.currentFunction.isNil() and s.depth != self.scopeDepth and self.scopeOwners[s.depth].owner != self.currentFunction: # Loads a closure variable. Stored in a separate "closure array" in the VM that does not @@ -1598,35 +1573,114 @@ proc whileStmt(self: Compiler, node: WhileStmt) = self.patchJump(jump) +proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) = + ## Version of generateCall that takes Type objects + ## instead of Name objects. The function is assumed + ## to be on the stack + self.emitByte(LoadUInt64, line) + self.emitBytes(self.chunk.writeConstant(0.toLong()), line) + let pos = self.chunk.consts.len() - 8 + for i, argument in reversed(args): + # We pass the arguments in reverse + # because of how stacks work. They'll + # be reversed again at runtime + self.check(argument, fn.args[^(i + 1)].kind) + self.expression(argument) + # 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.emitBytes(args.len().toTriple(), line) + if fn.isClosure: + self.emitBytes(fn.envLen.toTriple(), line) + self.patchReturnAddress(pos) + + +proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = + ## Small wrapper that abstracts emitting a call instruction + ## for a given function + if fn.valueType.isBuiltinFunction: + self.handleBuiltinFunction(fn.valueType, args, line) + return + self.emitByte(LoadUInt64, line) + self.emitBytes(self.chunk.writeConstant(fn.codePos.toLong()), line) + self.emitByte(LoadUInt64, line) + self.emitBytes(self.chunk.writeConstant(0.toLong()), line) + let pos = self.chunk.consts.len() - 8 + for arg in reversed(args): + self.expression(arg) + # 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.emitBytes(args.len().toTriple(), line) + if fn.valueType.isClosure: + self.emitBytes(fn.valueType.envLen.toTriple(), line) + self.patchReturnAddress(pos) + + +proc shadowGenerics(self: Compiler, fn: Name, args: seq[Expression]) = + ## Specializes a generic function by + ## shadowing the names of the generic + ## types with their respective concrete + ## types + var mapping: TableRef[string, Type] = newTable[string, Type]() + var kind: Type + # This first loop checks if a user tries to reassign a generic's + # name to a different type + for i, (name, typ) in fn.valueType.args: + if typ.kind != Generic: + continue + kind = self.infer(args[i]) + if typ.name in mapping and not self.compare(kind, mapping[typ.name]): + self.error(&"expected generic argument '{typ.name}' to be of type {self.typeToStr(mapping[typ.name])}, got {self.typeToStr(kind)} instead") + mapping[typ.name] = kind + for gen in mapping.keys(): + kind = mapping[gen] + self.names.add(Name(depth: self.scopeDepth + 1, + isPrivate: true, + isConst: false, + owner: self.currentModule, + line: fn.line, + valueType: kind, + name: newIdentExpr(Token(kind: TokenType.Identifier, lexeme: gen)))) + + proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = ## Compiles code to call a chain of function calls var args: seq[tuple[name: string, kind: Type]] = @[] var argExpr: seq[Expression] = @[] var kind: Type - if not self.checkCallIsPure(node.callee): - if self.currentFunction.valueType.name != "": - self.error(&"cannot make sure that calls to '{self.currentFunction.valueType.name}' are side-effect free") - else: - self.error(&"cannot make sure that call is side-effect free") # TODO: Keyword arguments - for i, argument in node.arguments.positionals: + var i = node.arguments.positionals.len() + for argument in node.arguments.positionals: + dec(i) kind = self.infer(argument) if kind.isNil(): if argument.kind == identExpr: self.error(&"reference to undeclared name '{IdentExpr(argument).name.lexeme}'") - self.error(&"cannot infer the type of argument {i + 1} in function call") + if node.callee.kind != identExpr: + self.error(&"cannot infer the type of argument {i + 1} in call") + else: + self.error(&"cannot infer the type of argument {i + 1} in call to '{node.callee.token.lexeme}'") args.add(("", kind)) argExpr.add(argument) - for argument in node.arguments.keyword: - # TODO - discard - if args.len() >= 16777216: - self.error(&"cannot pass more than 16777215 arguments") case node.callee.kind: of identExpr: # Calls like hi() - result = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args)) - self.generateCall(result, argExpr) + result = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args), node) + if result.valueType.isGeneric: + self.shadowGenerics(result, argExpr) + # Here is when the function gets *actually* compiled + self.funDecl(FunDecl(result.node), result) + # Now we call it + self.generateCall(result, argExpr, node.token.line) of NodeKind.callExpr: # Calling a call expression, like hello()() var node: Expression = node @@ -1636,7 +1690,6 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = node = CallExpr(node).callee for exp in reversed(all): self.callExpr(exp) - # TODO case all[^1].callee.kind: of identExpr: let fn = self.resolve(IdentExpr(all[^1].callee)) @@ -1719,7 +1772,6 @@ proc deferStmt(self: Compiler, node: DeferStmt) = for b in chunk.code: self.deferred.add(b) self.chunk = oldChunk - self.chunk.code &= chunk.code self.chunk.consts &= chunk.consts self.chunk.lines &= chunk.lines self.chunk.cfi &= chunk.cfi @@ -1732,11 +1784,15 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = if not node.value.isNil(): self.expression(node.value) self.emitByte(OpCode.SetResult, node.token.line) - self.emitByte(OpCode.Return, node.token.line) - if not node.value.isNil(): - self.emitByte(1, node.token.line) - else: - self.emitByte(0, node.token.line) + # Since the "set result" part and "exit the function" part + # of our return mechanism are already decoupled into two + # separate opcodes, we perform the former and then jump to + # the function's last return statement, which is always emitted + # by funDecl() at the end of the function's lifecycle, greatly + # simplifying the whole thing since there's just one return + # instruction to jump to instead of many potential points + # where the function returns from + self.currentFunction.valueType.retJumps.add(self.emitJump(JumpForwards, node.token.line)) proc yieldStmt(self: Compiler, node: YieldStmt) = @@ -1763,7 +1819,7 @@ proc continueStmt(self: Compiler, node: ContinueStmt) = proc breakStmt(self: Compiler, node: BreakStmt) = ## Compiles break statements. A break statement ## jumps to the end of the loop - self.currentLoop.breakPos.add(self.emitJump(OpCode.JumpForwards, node.token.line)) + self.currentLoop.breakJumps.add(self.emitJump(OpCode.JumpForwards, node.token.line)) if self.currentLoop.depth > self.scopeDepth: # Breaking out of a loop closes its scope self.endScope() @@ -1857,7 +1913,7 @@ proc statement(self: Compiler, node: Statement) = # for loops to while loops let loop = self.currentLoop self.currentLoop = Loop(start: self.chunk.code.len(), - depth: self.scopeDepth, breakPos: @[]) + depth: self.scopeDepth, breakJumps: @[]) self.whileStmt(WhileStmt(node)) self.patchBreaks() self.currentLoop = loop @@ -1877,7 +1933,7 @@ proc statement(self: Compiler, node: Statement) = self.expression(Expression(node)) -proc varDecl(self: Compiler, node: VarDecl) = +proc varDecl(self: Compiler, node: VarDecl, name: Name) = ## Compiles variable declarations let expected = self.infer(node.valueType) let actual = self.infer(node.value) @@ -1894,55 +1950,41 @@ proc varDecl(self: Compiler, node: VarDecl) = if not expected.isNil(): self.error(&"expected value of type '{self.typeToStr(expected)}', but '{node.name.token.lexeme}' is of type '{self.typeToStr(actual)}'") self.expression(node.value) - self.declareName(node, mutable=node.token.kind == TokenType.Var) self.emitByte(StoreVar, node.token.line) self.emitBytes(self.getStackPos(self.names[^1]).toTriple(), node.token.line) -proc typeDecl(self: Compiler, node: TypeDecl) = +proc typeDecl(self: Compiler, node: TypeDecl, name: Name) = ## Compiles type declarations # TODO -proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) = +proc funDecl(self: Compiler, node: FunDecl, name: Name) = ## Compiles function declarations - if node.token.kind == Operator and node.token.lexeme in [".", ]: - self.error(&"The '{node.token.lexeme}' cannot be overridden", node) - var jmp: int - self.declareName(node) - self.dispatchPragmas(node) + if node.token.kind == Operator and node.name.token.lexeme in [".", ]: + self.error(&"Due to current compiler limitations, the '{node.name.token.lexeme}' operator cannot be overridden", node.name) var node = node - var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn + var jmp: int # We store the current function var function = self.currentFunction if not self.currentFunction.isNil(): - self.currentFunction.valueType.children.add(fn.valueType) - fn.valueType.parent = function.valueType - self.currentFunction = fn - if fn.valueType.isBuiltinFunction: - fn.codePos = self.chunk.code.len() - # We take the arguments off of our name list - # because they become temporaries on the stack. - # Builtin functions (usually) map to a single - # bytecode instruction to avoid unnecessary - # overhead from peon's calling convention - # This also means that peon's fast builtins - # can only be relatively simple - self.names = self.names[0..^node.arguments.len() + 1] - elif not node.body.isNil(): + self.currentFunction.valueType.children.add(name.valueType) + name.valueType.parent = function.valueType + self.currentFunction = name + if not node.body.isNil(): # A function's code is just compiled linearly # and then jumped over jmp = self.emitJump(JumpForwards, node.token.line) - fn.codePos = self.chunk.code.len() + name.codePos = self.chunk.code.len() # We let our debugger know this function's boundaries self.chunk.cfi.add(self.chunk.code.high().toTriple()) - self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, fn: fn)) + self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, fn: name)) let idx = self.chunk.cfi.len() self.chunk.cfi.add(0.toTriple()) # Patched it later self.chunk.cfi.add(uint8(node.arguments.len())) if not node.name.isNil(): - self.chunk.cfi.add(fn.name.token.lexeme.len().toDouble()) - var s = fn.name.token.lexeme + self.chunk.cfi.add(name.name.token.lexeme.len().toDouble()) + var s = name.name.token.lexeme if s.len() >= uint16.high().int: s = node.name.token.lexeme[0..uint16.high()] self.chunk.cfi.add(s.toBytes()) @@ -1974,11 +2016,13 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression discard # Unreachable if not hasVal and not typ.isNil(): # There is no explicit return statement anywhere in the function's - # body: while this is not a tremendously useful piece of information (since - # the presence of at least one doesn't mean all control flow cases are - # covered), it definitely is an error worth reporting + # body: while this is not a tremendously useful piece of information + # (since the presence of at least one doesn't mean all control flow + # cases are covered), it definitely is an error worth reporting self.error("function has an explicit return type, but no return statement was found", node) hasVal = hasVal and not typ.isNil() + for jump in self.currentFunction.valueType.retJumps: + self.patchJump(jump) self.endScope() # Terminates the function's context self.emitByte(OpCode.Return, self.peek().token.line) @@ -2006,14 +2050,12 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression proc declaration(self: Compiler, node: Declaration) = - ## Compiles all declarations + ## Handles all declarations. They are not compiled + ## right away, but rather only when they're referenced + ## the first time case node.kind: - of NodeKind.varDecl: - self.varDecl(VarDecl(node)) - of NodeKind.funDecl: - self.funDecl(FunDecl(node)) - of NodeKind.typeDecl: - self.typeDecl(TypeDecl(node)) + of NodeKind.varDecl, NodeKind.funDecl, NodeKind.typeDecl: + self.dispatchPragmas(node, self.declareName(node)) else: self.statement(Statement(node)) diff --git a/src/frontend/lexer.nim b/src/frontend/lexer.nim index 5315235..6d24a46 100644 --- a/src/frontend/lexer.nim +++ b/src/frontend/lexer.nim @@ -530,7 +530,7 @@ proc parseBackticks(self: Lexer) = ## parser complaining about syntax ## errors while not self.match("`") and not self.done(): - if self.peek().isAlphaNumeric() or self.symbols.existsSymbol(self.peek()) or self.peek() in ["&", "|"]: + if self.peek().isAlphaNumeric() or self.symbols.existsSymbol(self.peek()): discard self.step() continue self.error(&"unexpected character: '{self.peek()}'") diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index 354e53d..bd52344 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -147,7 +147,6 @@ type StoreVar, # Stores the value of b at position a in the stack 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 - LiftArgument, # Closes over a function argument PopClosure, ## Looping and jumping Jump, # Absolute, unconditional jump into the bytecode @@ -171,11 +170,12 @@ type ## Coroutines Await, # Calls an asynchronous function ## Misc - Assert, # Raises an AssertionFailed exception if x is false - NoOp, # Just a no-op - 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 + Assert, # Raises an AssertionFailed exception if x is false + NoOp, # Just a no-op + 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 + LogicalNot # We group instructions by their operation/operand types for easier handling when debugging @@ -239,6 +239,7 @@ const simpleInstructions* = {Return, LoadNil, PrintNan, PrintInf, PrintString, + LogicalNot } # Constant instructions are instructions that operate on the bytecode constant table @@ -251,7 +252,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, LiftArgument, PopClosure} +const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, PopClosure} # 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/util/symbols.nim b/src/util/symbols.nim index 698f164..68a4423 100644 --- a/src/util/symbols.nim +++ b/src/util/symbols.nim @@ -59,5 +59,5 @@ proc fillSymbolTable*(tokenizer: Lexer) = tokenizer.symbols.addKeyword("ref", TokenType.Ref) tokenizer.symbols.addKeyword("ptr", TokenType.Ptr) for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=", - ">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%"]: + ">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^"]: tokenizer.symbols.addSymbol(sym, Symbol) \ No newline at end of file diff --git a/tests/generics.pn b/tests/generics.pn index 62fa781..a787ac2 100644 --- a/tests/generics.pn +++ b/tests/generics.pn @@ -1,18 +1,11 @@ import std; -operator `+`(a, b: int): int { - #pragma[magic: "AddInt64", pure] -} - - -operator `+`(a, b: int32): int32 { - #pragma[magic: "AddInt64", pure] -} - fn sum[T: int | int32](a, b: T): T { return a + b; } -sum(1, 2); -sum(1'i32, 2'i32); \ No newline at end of file + +# print(sum(1, 2)); +# print(sum(1'i32, 2'i32)); +print(sum(1'i16, 2'i16)); # Will not work if uncommented! \ No newline at end of file diff --git a/tests/std.pn b/tests/std.pn index ceffca2..00539e6 100644 --- a/tests/std.pn +++ b/tests/std.pn @@ -535,19 +535,44 @@ operator `or`*(a, b: bool): bool { } -operator `&`*(a, b: bool): bool { - #pragma[magic: "LogicalAnd", pure] +operator `not`*(a: bool): bool { + #pragma[magic: "LogicalNot", pure] } -operator `|`*(a, b: bool): bool { - #pragma[magic: "LogicalOr", pure] +operator `&`*(a, b: int): bool { + #pragma[magic: "And", pure] +} + + +operator `|`*(a, b: int): int { + #pragma[magic: "Or", pure] +} + + +operator `~`*(a: int): int { + #pragma[magic: "Not", pure] +} + + +operator `>>`*(a, b: int): int { + #pragma[magic: "RShift", pure] +} + + +operator `<<`*(a, b: int): int { + #pragma[magic: "LShift", pure] +} + + +operator `^`*(a, b: int): int { + #pragma[magic: "Xor", pure] } # Assignment operators -operator `=`*[T: Any](a: var T, b: T) { +operator `=`*[T: all](a: var T, b: T) { #pragma[magic: "GenericAssign"] }