diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 2fca63e..b445a13 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -50,7 +50,7 @@ type Type = ref object ## A wrapper around ## compile-time types - mutable: bool + isBuiltin: bool case kind: TypeKind: of Function: isLambda: bool @@ -58,7 +58,6 @@ type isCoroutine: bool args: seq[tuple[name: string, kind: Type, default: Expression]] returnType: Type - isBuiltinFunction: bool builtinOp: string fun: Declaration isClosure: bool @@ -76,6 +75,7 @@ type # example, fn foo[T: int & ~uint](...) {...} # would map to [(true, int), (false, uint)] cond: seq[tuple[match: bool, kind: Type]] + asUnion: bool # If this is true, the constraint is treated like a type union name: string of Union: types: seq[tuple[match: bool, kind: Type]] @@ -259,15 +259,14 @@ type proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil, incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[], showMismatches: bool = false, mode: CompileMode = Debug): Chunk -proc expression(self: Compiler, node: Expression) +proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} 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, name: Name = nil, compile: bool = true): Name {.discardable.} proc varDecl(self: Compiler, node: VarDecl) -proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name -proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: seq[Expression] = @[], allowFwd: bool = true): Name -proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.} +proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name +proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable.} proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.} proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.} @@ -387,7 +386,7 @@ proc warning(self: Compiler, kind: WarningKind, message: string, name: Name = ni stderr.styledWriteLine(styleBright, fgDefault, ": ", message) try: # We try to be as specific as possible with the warning message, pointing to the - # line it belongs to, but since warnings are almost never raised from the source + # line it belongs to, but since warnings are not always raised from the source # file they're generated in, we take into account the fact that retrieving the # exact warning location may fail and bail out silently if it does let line = readFile(file).splitLines()[node.token.line - 1].strip(chars={'\n'}) @@ -594,12 +593,12 @@ proc fixLines(self: Compiler, where, count: int, added: bool = true) = ## instructions were injected (added = true, ## the default) or that count instructions ## were removed (added = false). The where - ## argument is the position where the code change was - ## performed + ## argument is the position where the code + ## change was performed if added: - # We don't do any bounds () checking here because I doubt - # there's ever going to be even close to int.high() instructions - # on a line :P + # We don't do any bounds checking here because I doubt + # there's ever going to be even close to int.high() + # instructions on a line :P inc(self.chunk.lines[self.chunk.getIdx(self.chunk.getLine(where)) + 1], count) else: if self.chunk.lines[self.chunk.getIdx(self.chunk.getLine(where)) + 1] > 0: @@ -607,6 +606,8 @@ proc fixLines(self: Compiler, where, count: int, added: bool = true) = proc fixNames(self: Compiler, where, oldLen: int) = + ## Fixes the codePos field of our name objects + ## after the size of the bytecode has changed let offset = self.chunk.code.len() - oldLen for name in self.names: if name.codePos > where: @@ -629,8 +630,8 @@ proc insertAt(self: Compiler, where: int, opcode: OpCode, data: openarray[uint8] # into it self.fixJumps(where, oldLen) self.fixLines(where, self.chunk.code.len() - oldLen, true) - self.fixFunctionOffsets(oldLen, where) self.fixNames(where, oldLen) + self.fixFunctionOffsets(oldLen, where) proc compileDecl(self: Compiler, name: Name) = @@ -729,7 +730,7 @@ proc getStackPos(self: Compiler, name: Name): int = # temporaries. There is no stack frame for builtins, so we skip # these names too elif variable.kind == Argument: - if variable.belongsTo.valueType.isBuiltinFunction: + if variable.belongsTo.valueType.isBuiltin: continue elif not variable.belongsTo.resolved: continue @@ -776,26 +777,18 @@ proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): b if b.len() > a.len(): long = b short = a - var matched = false - for cond1 in long: - for cond2 in short: - if not self.compare(cond2.kind, cond1.kind) or cond2.match != cond1.match: + var i = 0 + for cond1 in short: + for cond2 in long: + if not self.compare(cond1.kind, cond2.kind) or cond1.match != cond2.match: continue - else: - matched = true - break - if not matched: - return false - matched = false - return true + inc(i) + return i >= short.len() proc compare(self: Compiler, a, b: Type): bool = ## Compares two type objects - ## for equality (works with nil!) - - # The nil code here is for void functions (when - # we compare their return types) + ## for equality result = false # Note: 'All' is a type internal to the peon # compiler that cannot be generated from user @@ -805,22 +798,21 @@ proc compare(self: Compiler, a, b: Type): bool = # type, including nil if a.isNil(): return b.isNil() or b.kind == All - if b.isNil(): + elif b.isNil(): return a.isNil() or a.kind == All - if a.kind == All or b.kind == All: + elif a.kind == All or b.kind == All: return true - if a.kind != Generic and b.kind != Generic and a.kind == b.kind: - # Here we make sure to compare only concrete - # types + elif 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, Nan, Bool, Inf: - # A value's type is always equal to - # another one's + UInt32, Int64, UInt64, Float32, Float64, + Char, Byte, String, Nil, Nan, Bool, Inf, Any: return true of Union: return self.compareUnions(a.types, b.types) + of Generic: + return self.compareUnions(a.cond, b.cond) of Reference, Pointer: # Here we already know that both # a and b are of either of the two @@ -829,13 +821,12 @@ proc compare(self: Compiler, a, b: Type): bool = return self.compare(a.value, b.value) of Function: # Functions are a bit trickier to compare - var temp: bool = true if a.args.len() != b.args.len(): - temp = false - if a.isCoroutine != b.isCoroutine: - temp = false - if not self.compare(a.returnType, b.returnType): - temp = false + return false + if a.isCoroutine != b.isCoroutine or a.isGenerator != b.isGenerator: + return false + if not self.compare(b.returnType, a.returnType): + return false var i = 0 for (argA, argB) in zip(a.args, b.args): # When we compare functions with forward @@ -847,8 +838,7 @@ proc compare(self: Compiler, a, b: Type): bool = if a.forwarded: if b.forwarded: if argA.name != argB.name: - temp = false - break + return false else: if argB.name == "": # An empty argument name means @@ -857,75 +847,59 @@ proc compare(self: Compiler, a, b: Type): bool = # to match the argument name continue if argA.name != argB.name: - temp = false - break + return false elif b.forwarded: if a.forwarded: if argA.name != argB.name: - temp = false - break + return false else: if argA.name == "": continue if argA.name != argB.name: - temp = false - break + return false if not self.compare(argA.kind, argB.kind): - temp = false - break - return temp + return false + return true else: discard # TODO: Custom types, enums - if a.kind == Union: + elif a.kind == Union: for constraint in a.types: - if self.compare(constraint.kind, b): - if not constraint.match: - return false - else: - return false - return true - if b.kind == Union: + if self.compare(constraint.kind, b) and constraint.match: + return true + return false + elif b.kind == Union: for constraint in b.types: - if self.compare(constraint.kind, a): - if not constraint.match: - return false - else: - return false - return true - if a.kind == Generic: - if b.kind == Generic: - return self.compareUnions(a.cond, b.cond) + if self.compare(constraint.kind, a) and constraint.match: + return true + return false + elif a.kind == Generic: + if a.asUnion: + for constraint in a.cond: + if self.compare(constraint.kind, b) and constraint.match: + return true + return false else: for constraint in a.cond: - if self.compare(constraint.kind, b): - if not constraint.match: - return false - else: + if not self.compare(constraint.kind, b) or not constraint.match: return false return true - if b.kind == Generic: - if a.kind == Generic: - return self.compareUnions(a.cond, b.cond) + elif b.kind == Generic: + if b.asUnion: + for constraint in b.cond: + if self.compare(constraint.kind, a) and constraint.match: + return true + return false else: for constraint in b.cond: - if self.compare(constraint.kind, a): - if not constraint.match: - return false - else: + if not self.compare(constraint.kind, a) or not constraint.match: return false - return true - if a.kind == Any: - # Why this difference? The reason is that - # while, for example, int is a subtype of - # any, meaning that passing int to a function - # that takes any is fine, this means that any - # cannot be a subtype of int! Basically, you - # can't pass any where int is expected, because - # that would break the type system in ways I'd - # rather not talk about + return true + elif a.kind == Any or b.kind == Any: + # Here we already know that neither of + # these types are nil, so we can always + # just return true return true - if b.kind == Any: - return a.kind == Any + return false proc toIntrinsic(name: string): Type = @@ -970,13 +944,10 @@ proc toIntrinsic(name: string): Type = return Type(kind: Typevar) elif name == "string": return Type(kind: String) - else: - return nil 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: @@ -1002,16 +973,10 @@ proc infer(self: Compiler, node: LiteralExpr): Type = return typ else: self.error(&"invalid type specifier '{size[1]}' for float") - of nilExpr: - return Type(kind: Nil) of trueExpr: return Type(kind: Bool) of falseExpr: return Type(kind: Bool) - of nanExpr: - return Type(kind: TypeKind.Nan) - of infExpr: - return Type(kind: TypeKind.Inf) of strExpr: return Type(kind: String) else: @@ -1020,8 +985,7 @@ proc infer(self: Compiler, node: LiteralExpr): Type = proc infer(self: Compiler, node: Expression): Type = ## Infers the type of a given expression and - ## returns it (if the node is nil, nil is - ## returned) + ## returns it if node.isNil(): return nil case node.kind: @@ -1032,15 +996,11 @@ proc infer(self: Compiler, node: Expression): Type = of binaryExpr: result = self.binary(BinaryExpr(node), compile=false).valueType.returnType of {intExpr, hexExpr, binExpr, octExpr, - strExpr, falseExpr, trueExpr, infExpr, - nanExpr, floatExpr, nilExpr + strExpr, falseExpr, trueExpr, floatExpr }: result = self.infer(LiteralExpr(node)) of NodeKind.callExpr: - result = self.call(CallExpr(node), compile=false).valueType.returnType - of varExpr: - result = self.infer(Var(node).value) - result.mutable = true + result = self.call(CallExpr(node), compile=false).returnType of refExpr: result = Type(kind: Reference, value: self.infer(Ref(node).value)) of ptrExpr: @@ -1056,14 +1016,13 @@ proc infer(self: Compiler, node: Expression): Type = proc inferOrError(self: Compiler, node: Expression): Type = ## Attempts to infer the type of ## the given expression and raises an - ## error with an appropriate message if - ## it fails + ## error with if it fails result = self.infer(node) if result.isNil(): self.error("expression has no type", node) -proc typeToStr(self: Compiler, typ: Type): string = +proc stringify(self: Compiler, typ: Type): string = ## Returns the string representation of a ## type object if typ.isNil(): @@ -1075,23 +1034,20 @@ proc typeToStr(self: Compiler, typ: Type): string = TypeKind.Inf: result &= ($typ.kind).toLowerAscii() of Pointer: - result &= &"ptr {self.typeToStr(typ.value)}" + result &= &"ptr {self.stringify(typ.value)}" of Reference: - result &= &"ref {self.typeToStr(typ.value)}" + result &= &"ref {self.stringify(typ.value)}" of Function: result &= "fn (" for i, (argName, argType, argDefault) in typ.args: - result &= &"{argName}: " - if argType.mutable: - result &= "var " - result &= self.typeToStr(argType) + result &= &"{argName}: {self.stringify(argType)}" if not argDefault.isNil(): result &= &" = {argDefault}" if i < typ.args.len() - 1: result &= ", " result &= ")" if not typ.returnType.isNil(): - result &= &": {self.typeToStr(typ.returnType)}" + result &= &": {self.stringify(typ.returnType)}" if typ.fun.pragmas.len() > 0: result &= " {" for i, pragma in typ.fun.pragmas: @@ -1114,14 +1070,14 @@ proc typeToStr(self: Compiler, typ: Type): string = result &= " | " if not condition.match: result &= "~" - result &= self.typeToStr(condition.kind) + result &= self.stringify(condition.kind) of Generic: for i, condition in typ.cond: if i > 0: result &= " | " if not condition.match: result &= "~" - result &= self.typeToStr(condition.kind) + result &= self.stringify(condition.kind) else: discard @@ -1167,22 +1123,47 @@ proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] {.used.} = result.add(obj) -proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: seq[Expression] = @[], allowFwd: bool = true): Name = +proc check(self: Compiler, term: Expression, kind: Type) {.inline.} = + ## Checks the type of term against a known type. + ## Raises an error if appropriate and returns + ## otherwise + 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: + 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 + case typ.kind: + of Any: + return true + of Generic: + for condition in typ.cond: + if condition.kind.isAny(): + return true + of Union: + for condition in typ.types: + if condition.kind.isAny(): + return true + else: + discard + return false + + +proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name = ## Tries to find a matching function implementation ## compatible with the given type and returns its ## name object var impl: seq[Name] = @[] - var temp: Name for obj in self.findByName(name): - if obj.isGeneric and args.len() > 0: - temp = self.specialize(obj, args) - else: - temp = obj - if self.compare(kind, temp.valueType): - impl.add(self.match(name, temp.valueType, node)) + if self.compare(kind, obj.valueType): + impl.add(obj) if impl.len() == 0: - var msg = &"failed to find a suitable implementation for '{name}'" let names = self.findByName(name) + var msg = &"failed to find a suitable implementation for '{name}'" if names.len() > 0: msg &= &", found {len(names)} potential candidate" if names.len() > 1: @@ -1190,18 +1171,15 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: if self.showMismatches: msg &= ":" for name in names: - msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.typeToStr(name.valueType)}" + msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.stringify(name.valueType)}" if name.valueType.kind != Function: msg &= ": not a callable" elif kind.args.len() != name.valueType.args.len(): msg &= &": wrong number of arguments (expected {name.valueType.args.len()}, got {kind.args.len()})" else: for i, arg in kind.args: - if name.valueType.args[i].kind.mutable and not arg.kind.mutable: - msg &= &": first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'" - break - elif not self.compare(arg.kind, name.valueType.args[i].kind): - msg &= &": first mismatch at position {i + 1}: (expected {self.typeToStr(name.valueType.args[i].kind)}, got {self.typeToStr(arg.kind)})" + 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 else: msg &= " (compile with --showMismatches for more details)" @@ -1217,28 +1195,19 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: if self.showMismatches: msg &= ":" for fn in reversed(impl): - msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.typeToStr(fn.valueType)}" + msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.stringify(fn.valueType)}" else: msg &= " (compile with --showMismatches for more details)" self.error(msg, node) if impl[0].valueType.forwarded and not allowFwd: - self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.typeToStr(impl[0].valueType)}'") + self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'") result = impl[0] + for (a, b) in zip(result.valueType.args, kind.args): + if not a.kind.isAny() and b.kind.isAny(): + self.error("any is not a valid type in this context", node) self.compileDecl(result) -proc 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 - let k = self.inferOrError(term) - if k.kind == Any and not allowAny: - # Any should only be used internally: error! - self.error("'any' 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)}", term) - - proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line: int) = ## Emits instructions for builtin functions ## such as addition or subtraction @@ -1284,25 +1253,47 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line "GreaterThan": GreaterThan, "LessOrEqual": LessOrEqual, "GreaterOrEqual": GreaterOrEqual, - "PrintInt64": PrintInt64, - "PrintUInt64": PrintUInt64, - "PrintInt32": PrintInt32, - "PrintUInt32": PrintUInt32, - "PrintInt16": PrintInt16, - "PrintUInt16": PrintUInt16, - "PrintInt8": PrintInt8, - "PrintUInt8": PrintUInt8, - "PrintFloat64": PrintFloat64, - "PrintFloat32": PrintFloat32, - "PrintHex": PrintHex, - "PrintBool": PrintBool, - "PrintNan": PrintNan, - "PrintInf": PrintInf, "PrintString": PrintString, "SysClock64": SysClock64, "LogicalNot": LogicalNot, "NegInf": LoadNInf }.to_table() + if fn.builtinOp == "print": + var typ = self.expression(args[0], compile=false) + if typ.kind == Function: + typ = typ.returnType + case typ.kind: + of Int64: + self.emitByte(PrintInt64, line) + of Int32: + self.emitByte(PrintInt32, line) + of Int16: + self.emitByte(PrintInt16, line) + of Int8: + self.emitByte(PrintInt8, line) + of UInt64: + self.emitByte(PrintUInt64, line) + of UInt32: + self.emitByte(PrintUInt32, line) + of UInt16: + self.emitByte(PrintUInt16, line) + of UInt8: + self.emitByte(PrintUInt8, line) + of Float64: + self.emitByte(PrintFloat64, line) + of Float32: + self.emitByte(PrintFloat32, line) + of String: + self.emitByte(PrintString, line) + of Bool: + self.emitByte(PrintBool, line) + of Nan: + self.emitByte(PrintNan, line) + of Inf: + self.emitByte(PrintInf, line) + else: + self.error("invalid type for built-in 'print'", args[0]) + return if fn.builtinOp in codes: self.emitByte(codes[fn.builtinOp], line) return @@ -1389,7 +1380,7 @@ proc endScope(self: Compiler) = if name.kind notin [NameKind.Var, NameKind.Argument]: continue elif name.kind == NameKind.Argument: - if name.belongsTo.valueType.isBuiltinFunction: + if name.belongsTo.valueType.isBuiltin: # Arguments to builtin functions become temporaries on the # stack and are popped automatically continue @@ -1406,7 +1397,7 @@ proc endScope(self: Compiler) = self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name) of NameKind.Argument: if not name.ident.token.lexeme.startsWith("_") and name.isPrivate: - if not name.belongsTo.valueType.isBuiltinFunction and name.belongsTo.isReal: + if not name.belongsTo.valueType.isBuiltin and name.belongsTo.isReal: # Builtin functions never use their arguments. We also don't emit this # warning if the function was generated internally by the compiler (for # example as a result of generic specialization) because such objects do @@ -1471,22 +1462,22 @@ proc unpackUnion(self: Compiler, condition: Expression, list: var seq[tuple[matc let condition = BinaryExpr(condition) case condition.operator.lexeme: of "|": - self.unpackGenerics(condition.a, list) - self.unpackGenerics(condition.b, list) + 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.unpackGenerics(condition.a, list, accept=false) + 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, mutable: bool = false): Name {.discardable.} = +proc declare(self: Compiler, node: ASTNode): Name {.discardable.} = ## 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() @@ -1519,8 +1510,6 @@ proc declare(self: Compiler, node: ASTNode, mutable: bool = false): Name {.disca isReal: true )) n = self.names[^1] - if mutable: - self.names[^1].valueType.mutable = true of NodeKind.funDecl: var node = FunDecl(node) declaredName = node.name.token.lexeme @@ -1596,7 +1585,8 @@ proc declare(self: Compiler, node: ASTNode, mutable: bool = false): Name {.disca discard else: discard # TODO: enums - self.dispatchPragmas(n) + if not n.isNil(): + self.dispatchPragmas(n) case n.kind: of NameKind.Function: self.prepareFunction(n) @@ -1650,40 +1640,15 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) = elif pragma.args[0].kind != strExpr: self.error("'magic' pragma: wrong type of argument (constant string expected)") elif name.node.kind == NodeKind.funDecl: - name.valueType.isBuiltinFunction = true + name.valueType.isBuiltin = true name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2] elif name.node.kind == NodeKind.typeDecl: - case pragma.args[0].token.lexeme[1..^2]: - of "int64": - name.valueType = Type(kind: Int64) - of "int32": - name.valueType = Type(kind: Int32) - of "int16": - name.valueType = Type(kind: Int16) - of "int8": - name.valueType = Type(kind: Int8) - of "uint64": - name.valueType = Type(kind: UInt64) - of "uint32": - name.valueType = Type(kind: UInt32) - of "uint16": - name.valueType = Type(kind: UInt16) - of "uint8": - name.valueType = Type(kind: UInt8) - of "float64": - name.valueType = Type(kind: Float64) - of "float32": - name.valueType = Type(kind: Float32) - of "bool": - name.valueType = Type(kind: Bool) - of "string": - name.valueType = Type(kind: String) - of "any": - name.valueType = Type(kind: Any) - of "all": - self.error("don't even think about it (compiler-chan is angry at you)", pragma) - else: - self.error("'magic' pragma: wrong argument value", pragma.args[0]) + name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic() + if name.valueType.isNil(): + self.error("'magic' pragma: wrong argument value", pragma.args[0]) + if name.valueType.kind == All: + self.error("don't even think about it (compiler-chan is angry at you)", pragma) + name.valueType.isBuiltin = true else: self.error("'magic' pragma is not valid in this context") @@ -1838,12 +1803,6 @@ proc literal(self: Compiler, node: ASTNode) = self.emitByte(LoadTrue, node.token.line) of falseExpr: self.emitByte(LoadFalse, node.token.line) - of nilExpr: - self.emitByte(LoadNil, node.token.line) - of infExpr: - self.emitByte(LoadInf, node.token.line) - of nanExpr: - self.emitByte(LoadNan, node.token.line) of strExpr: self.emitConstant(LiteralExpr(node), Type(kind: String)) of intExpr: @@ -1926,7 +1885,7 @@ proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discar let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default)]) - result = self.match(node.token.lexeme, fn, node, @[node.a]) + result = self.match(node.token.lexeme, fn, node) if compile: self.generateCall(result, @[node.a], result.line) @@ -1935,7 +1894,7 @@ proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.disc ## Compiles all binary expressions var default: Expression let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) - result = self.match(node.token.lexeme, fn, node, @[node.a, node.b]) + result = self.match(node.token.lexeme, fn, node) if compile: self.generateCall(result, @[node.a, node.b], result.line) @@ -1961,8 +1920,18 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool # they're referenced self.emitByte(LoadUInt64, node.token.line) self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line) + elif s.valueType.isBuiltin: + case s.ident.token.lexeme: + of "nil": + self.emitByte(LoadNil, node.token.line) + of "nan": + self.emitByte(LoadNan, node.token.line) + of "inf": + self.emitByte(LoadInf, node.token.line) + else: + discard # Unreachable elif s.depth > 0 and not s.belongsTo.isNil() and s.belongsTo != self.currentFunction and self.currentFunction.valueType in s.belongsTo.valueType.children: - # Loads a closure variable + # Loads a closure variable from a closure environment if not s.isClosedOver: var fn = self.currentFunction.valueType while true: @@ -2018,9 +1987,6 @@ proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.dis self.emitByte(StoreVar, node.token.line) self.emitBytes(self.getStackPos(r).toTriple(), node.token.line) else: - # Loads a closure variable. Stored in a separate "closure array" in the VM that does not - # align its semantics with the call stack. This makes closures work as expected and is - # not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway) self.emitByte(StoreClosure, node.token.line) self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line) of setItemExpr: @@ -2111,7 +2077,7 @@ proc prepareFunction(self: Compiler, fn: Name) = self.unpackGenerics(gen.cond, constraints) self.names.add(Name(depth: fn.depth + 1, isPrivate: true, - valueType: Type(kind: Generic, name: gen.name.token.lexeme, mutable: false, cond: constraints), + valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints), codePos: 0, isLet: false, line: fn.node.token.line, @@ -2158,7 +2124,7 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = ## Small wrapper that abstracts emitting a call instruction ## for a given function self.dispatchDelayedPragmas(fn) - if fn.valueType.isBuiltinFunction: + if fn.valueType.isBuiltin: self.handleBuiltinFunction(fn.valueType, args, line) return case fn.kind: @@ -2189,50 +2155,35 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = self.patchReturnAddress(pos) -proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name = - ## Specializes a generic type by - ## instantiating a concrete version - ## of it +proc specialize(self: Compiler, typ: Type, args: seq[Expression]): Type {.discardable.} = + ## Specializes a generic type. + ## Used for typechecking at the + ## call site var mapping: TableRef[string, Type] = newTable[string, Type]() var kind: Type - result = deepCopy(name) - result.isGeneric = false - result.isReal = false - case name.kind: - of NameKind.Function: + result = deepCopy(typ) + case result.kind: + of TypeKind.Function: # This first loop checks if a user tries to reassign a generic's # name to a different type - for i, (name, typ, default) in result.valueType.args: + for i, (name, typ, default) in result.args: if typ.kind != Generic: continue - kind = self.infer(args[i]) + kind = self.inferOrError(args[i]) if typ.name in mapping and not self.compare(kind, mapping[typ.name]): - self.error(&"expecting generic argument '{typ.name}' to be of type {self.typeToStr(mapping[typ.name])}, got {self.typeToStr(kind)}") + self.error(&"expecting generic argument '{typ.name}' to be of type {self.stringify(mapping[typ.name])}, got {self.stringify(kind)}") mapping[typ.name] = kind - result.valueType.args[i].kind = kind - for (argExpr, argTuple) in zip(args, result.valueType.args): - if self.names.high() > 16777215: - self.error("cannot declare more than 16777215 variables at a time") - self.names.add(Name(depth: name.depth + 1, - isPrivate: true, - owner: self.currentModule, - file: self.file, - isConst: false, - ident: newIdentExpr(Token(kind: Identifier, line: argExpr.token.line, lexeme: argTuple.name)), - valueType: argTuple.kind, - codePos: 0, - isLet: false, - line: name.line, - belongsTo: result, - kind: NameKind.Argument - )) - if not result.valueType.returnType.isNil() and result.valueType.returnType.kind == Generic: - result.valueType.returnType = mapping[result.valueType.returnType.name] + result.args[i].kind = kind + if not result.returnType.isNil() and result.returnType.kind == Generic: + if result.returnType.name in mapping: + result.returnType = mapping[result.returnType.name] + else: + self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun) else: discard # TODO: Custom user-defined types -proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.} = +proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable.} = ## Compiles code to call a chain of function calls var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[] var argExpr: seq[Expression] = @[] @@ -2257,10 +2208,13 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discarda case node.callee.kind: of identExpr: # Calls like hi() - result = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node, argExpr) - # Now we call it + let impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node) + result = impl.valueType + if impl.isGeneric: + result = self.specialize(result, argExpr) if compile: - self.generateCall(result, argExpr, node.token.line) + # Now we call it + self.generateCall(impl, argExpr, node.token.line) of NodeKind.callExpr: # Calling a call expression, like hello()() var node: Expression = node @@ -2283,7 +2237,7 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discarda if typ.isNil(): self.error(&"expression has no type") else: - self.error(&"object of type '{self.typeToStr(typ)}' is not callable") + self.error(&"object of type '{self.stringify(typ)}' is not callable") proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} = @@ -2309,13 +2263,13 @@ proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type self.error("invalid syntax", node) -proc expression(self: Compiler, node: Expression) = +proc expression(self: Compiler, node: Expression, compile: bool = true): Type {.discardable.} = ## Compiles all expressions case node.kind: of NodeKind.callExpr: - self.call(CallExpr(node)) + return self.call(CallExpr(node), compile) of NodeKind.getItemExpr: - self.getItemExpr(GetItemExpr(node)) + return self.getItemExpr(GetItemExpr(node), compile) of NodeKind.pragmaExpr: discard # TODO # Note that for setItem and assign we don't convert @@ -2323,20 +2277,20 @@ proc expression(self: Compiler, node: Expression) = # would be lost in the call anyway. The differentiation # happens in self.assignment() of setItemExpr, assignExpr: - self.assignment(node) + return self.assignment(node, compile).valueType of identExpr: - self.identifier(IdentExpr(node)) + return self.identifier(IdentExpr(node), compile=compile).valueType of unaryExpr: # Unary expressions such as ~5 and -3 - self.unary(UnaryExpr(node)) + return self.unary(UnaryExpr(node), compile).valueType of groupingExpr: # Grouping expressions like (2 + 1) - self.expression(GroupingExpr(node).expression) + return self.expression(GroupingExpr(node).expression, compile) of binaryExpr: # Binary expressions such as 2 ^ 5 and 0.66 * 3.14 - self.binary(BinaryExpr(node)) + return self.binary(BinaryExpr(node)).valueType of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, - infExpr, nanExpr, floatExpr, nilExpr: + floatExpr: # Since all of these AST nodes share the # same overall structure and the kind # field is enough to tell one from the @@ -2349,37 +2303,33 @@ proc expression(self: Compiler, node: Expression) = # TODO proc awaitStmt(self: Compiler, node: AwaitStmt) = - ## Compiles await statements. An await statement - ## is like an await expression, but parsed in the - ## context of statements for usage outside expressions, - ## meaning it can be used standalone. It's basically the - ## same as an await expression followed by a semicolon. - ## Await expressions and statements are the only native - ## construct to run coroutines from within an already - ## asynchronous context (which should be orchestrated - ## by an event loop). They block in the caller until - ## the callee returns - self.expression(node.expression) - self.emitByte(OpCode.Await, node.token.line) + ## Compiles await statements + # TODO proc deferStmt(self: Compiler, node: DeferStmt) = - ## Compiles defer statements. A defer statement - ## is executed right before its containing function - ## exits (either because of a return or an exception) - var oldChunk = self.chunk - var chunk = newChunk() - chunk.consts = self.chunk.consts - chunk.lines = self.chunk.lines - chunk.functions = self.chunk.functions - self.chunk = chunk - self.expression(node.expression) - for b in chunk.code: - self.deferred.add(b) - self.chunk = oldChunk - self.chunk.consts &= chunk.consts - self.chunk.lines &= chunk.lines - self.chunk.functions &= chunk.functions + ## Compiles defer statements + + +# TODO +proc yieldStmt(self: Compiler, node: YieldStmt) = + ## Compiles yield statements + + +# TODO +proc raiseStmt(self: Compiler, node: RaiseStmt) = + ## Compiles raise statements + + +# TODO +proc assertStmt(self: Compiler, node: AssertStmt) = + ## Compiles assert statements + # TODO + + +# TODO +proc forEachStmt(self: Compiler, node: ForEachStmt) = + ## Compiles foreach loops proc returnStmt(self: Compiler, node: ReturnStmt) = @@ -2404,18 +2354,6 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = # than straight to the return statement self.currentFunction.valueType.retJumps.add(self.emitJump(JumpForwards, node.token.line)) -# TODO -proc yieldStmt(self: Compiler, node: YieldStmt) = - ## Compiles yield statements - self.expression(node.expression) - self.emitByte(OpCode.Yield, node.token.line) - -# TODO -proc raiseStmt(self: Compiler, node: RaiseStmt) = - ## Compiles raise statements - self.expression(node.exception) - self.emitByte(OpCode.Raise, node.token.line) - proc continueStmt(self: Compiler, node: ContinueStmt) = ## Compiles continue statements. A continue statement @@ -2434,17 +2372,6 @@ proc breakStmt(self: Compiler, node: BreakStmt) = # Breaking out of a loop closes its scope self.endScope() -# TODO -proc assertStmt(self: Compiler, node: AssertStmt) = - ## Compiles assert statements (raise - ## AssertionError if the expression is falsey) - # TODO - - -proc forEachStmt(self: Compiler, node: ForEachStmt) = - ## Compiles foreach loops - # TODO - proc importStmt(self: Compiler, node: ImportStmt) = ## Imports a module at compile time @@ -2620,11 +2547,15 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) = name.valueType.forwarded = true self.currentFunction = function return - if not self.currentFunction.isNil(): - self.currentFunction.valueType.children.add(name.valueType) + if not function.isNil() and self.depth > 1: + # Calling a function immediately compiles it, so if + # we didn't check for the scope depth we'd mistakenly + # mark functions as being nested when they actually + # aren't + function.valueType.children.add(name.valueType) name.valueType.parent = function.valueType self.currentFunction = name - if self.currentFunction.valueType.isBuiltinFunction: + if self.currentFunction.valueType.isBuiltin: self.currentFunction = function return # A function's code is just compiled linearly @@ -2717,7 +2648,26 @@ proc declaration(self: Compiler, node: Declaration) = var name = self.declare(node) if name.isGeneric: # We typecheck generics immediately + name.resolved = true self.funDecl(FunDecl(node), name) + # After we're done compiling a generic + # function, we pull a magic trick: since, + # from here on, the user will be able to + # call this with any of the types in the + # generic constraint, we switch every generic + # to a type union (which, conveniently, have an + # identical layout) so that the compiler will + # typecheck the function as if its arguments + # were all types of the constraint at once, + # while still allowing the user to call it with + # any type in said constraint + for i, argument in name.valueType.args: + if argument.kind.kind != Generic: + continue + else: + argument.kind.asUnion = true + if not name.valueType.returnType.isNil() and name.valueType.returnType.isNil(): + name.valueType.returnType.asUnion = true of NodeKind.typeDecl: self.declare(node) of NodeKind.varDecl: diff --git a/src/frontend/lexer.nim b/src/frontend/lexer.nim index 789e2d1..379a101 100644 --- a/src/frontend/lexer.nim +++ b/src/frontend/lexer.nim @@ -668,6 +668,7 @@ proc lex*(self: Lexer, source, file: string): seq[Token] = self.start = self.current self.lineCurrent = self.linePos self.tokens.add(Token(kind: EndOfFile, lexeme: "", - line: self.line, pos: (self.current, self.current))) + line: self.line, pos: (self.current, self.current), + relPos: (start: 0, stop: self.linePos - 1))) self.incLine() return self.tokens diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index 4d4c1b7..a9d4422 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -73,12 +73,8 @@ type hexExpr, octExpr, binExpr, - nilExpr, - nanExpr, - infExpr, identExpr, # Identifier pragmaExpr, - varExpr, refExpr, ptrExpr @@ -131,9 +127,6 @@ type TrueExpr* = ref object of LiteralExpr FalseExpr* = ref object of LiteralExpr - NilExpr* = ref object of LiteralExpr - NanExpr* = ref object of LiteralExpr - InfExpr* = ref object of LiteralExpr IdentExpr* = ref object of Expression name*: Token @@ -300,19 +293,12 @@ proc isConst*(self: ASTNode): bool = ## constants case self.kind: of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, - infExpr, nanExpr, floatExpr, nilExpr: + floatExpr: return true else: return false -proc isLiteral*(self: ASTNode): bool {.inline.} = - ## Returns if the AST node represents a literal - self.kind in {intExpr, hexExpr, binExpr, octExpr, - strExpr, falseExpr, trueExpr, infExpr, - nanExpr, floatExpr, nilExpr - } - ## AST node constructors proc newASTNode*(kind: NodeKind, token: Token): ASTNode = ## Initializes a new generic ASTNode object @@ -329,13 +315,6 @@ proc newPragma*(name: IdentExpr, args: seq[LiteralExpr]): Pragma = result.token = name.token -proc newVarExpr*(expression: Expression, token: Token): Var = - new(result) - result.kind = varExpr - result.value = expression - result.token = token - - proc newRefExpr*(expression: Expression, token: Token): Ref = new(result) result.kind = refExpr @@ -384,12 +363,6 @@ proc newTrueExpr*(token: Token): LiteralExpr = LiteralExpr(kind: trueExpr, token: token, literal: token) proc newFalseExpr*(token: Token): LiteralExpr = LiteralExpr(kind: falseExpr, token: token, literal: token) -proc newNaNExpr*(token: Token): LiteralExpr = LiteralExpr(kind: nanExpr, - token: token, literal: token) -proc newNilExpr*(token: Token): LiteralExpr = LiteralExpr(kind: nilExpr, - token: token, literal: token) -proc newInfExpr*(token: Token): LiteralExpr = LiteralExpr(kind: infExpr, - token: token, literal: token) proc newStrExpr*(literal: Token): StrExpr = @@ -667,8 +640,8 @@ proc `$`*(self: ASTNode): string = return "nil" case self.kind: of intExpr, floatExpr, hexExpr, binExpr, octExpr, strExpr, trueExpr, - falseExpr, nanExpr, nilExpr, infExpr: - if self.kind in {trueExpr, falseExpr, nanExpr, nilExpr, infExpr}: + falseExpr: + if self.kind in {trueExpr, falseExpr}: result &= &"Literal({($self.kind)[0..^5]})" elif self.kind == strExpr: result &= &"Literal({LiteralExpr(self).literal.lexeme[1..^2].escape()})" @@ -773,10 +746,8 @@ proc `$`*(self: ASTNode): string = of pragmaExpr: var self = Pragma(self) result &= &"Pragma(name={self.name}, args={self.args})" - of varExpr: - result &= &"Var({Var(self).value})" of refExpr: - result &= &"Ptr({Ref(self).value})" + result &= &"Ref({Ref(self).value})" of ptrExpr: result &= &"Ptr({Ptr(self).value})" else: diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index 7a0b5f3..fc01fda 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -271,7 +271,7 @@ const argumentDoubleInstructions* = {PopN, } # Jump instructions jump at relative or absolute bytecode offsets const jumpInstructions* = {Jump, JumpIfFalse, JumpIfFalsePop, JumpForwards, JumpBackwards, - JumpIfTrue} + JumpIfTrue, JumpIfFalseOrPop} proc newChunk*: Chunk = diff --git a/src/frontend/meta/token.nim b/src/frontend/meta/token.nim index 1752818..e1c5568 100644 --- a/src/frontend/meta/token.nim +++ b/src/frontend/meta/token.nim @@ -24,9 +24,6 @@ type # Booleans True, False, - # Other singleton types - Infinity, NotANumber, Nil - # Control flow statements If, Else, diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 015ccb1..4666205 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -317,10 +317,6 @@ proc primary(self: Parser): Expression = result = newTrueExpr(self.step()) of False: result = newFalseExpr(self.step()) - of TokenType.NotANumber: - result = newNanExpr(self.step()) - of Nil: - result = newNilExpr(self.step()) of Float: result = newFloatExpr(self.step()) of Integer: @@ -343,7 +339,7 @@ proc primary(self: Parser): Expression = result = newYieldExpr(self.expression(), tok) else: # Empty yield - result = newYieldExpr(newNilExpr(Token()), tok) + result = newYieldExpr(nil, tok) of Await: let tok = self.step() if self.currentFunction.isNil(): @@ -364,8 +360,6 @@ proc primary(self: Parser): Expression = result = newBinExpr(self.step()) of String: result = newStrExpr(self.step()) - of Infinity: - result = newInfExpr(self.step()) of Function: discard self.step() result = Expression(self.funDecl(isLambda=true)) @@ -375,9 +369,6 @@ proc primary(self: Parser): Expression = of Generator: discard self.step() result = Expression(self.funDecl(isGenerator=true, isLambda=true)) - of TokenType.Var: - discard self.step() - result = newVarExpr(self.expression(), self.peek(-1)) of TokenType.Ref: discard self.step() result = newRefExpr(self.expression(), self.peek(-1)) @@ -654,7 +645,7 @@ proc yieldStmt(self: Parser): Statement = if not self.check(Semicolon): result = newYieldStmt(self.expression(), tok) else: - result = newYieldStmt(newNilExpr(Token(lexeme: "nil")), tok) + result = newYieldStmt(nil, tok) endOfLine("missing semicolon after 'yield'") @@ -857,7 +848,8 @@ proc parsePragmas(self: Parser): seq[Pragma] = elif self.match("("): while not self.match(")") and not self.done(): exp = self.primary() - if not exp.isLiteral(): + if exp.kind notin {strExpr, intExpr, octExpr, binExpr, hexExpr, floatExpr, + trueExpr, falseExpr}: self.error("pragma arguments can only be literals", exp.token) args.add(LiteralExpr(exp)) if not self.match(","): @@ -865,7 +857,8 @@ proc parsePragmas(self: Parser): seq[Pragma] = self.expect(LeftParen, "unterminated parenthesis in pragma arguments") else: exp = self.primary() - if not exp.isLiteral(): + if exp.kind notin {strExpr, intExpr, octExpr, binExpr, hexExpr, floatExpr, + trueExpr, falseExpr}: self.error("pragma arguments can only be literals", exp.token) args.add(LiteralExpr(exp)) result.add(newPragma(name, args)) diff --git a/src/peon/stdlib/builtins/misc.pn b/src/peon/stdlib/builtins/misc.pn index a3674e6..bed888a 100644 --- a/src/peon/stdlib/builtins/misc.pn +++ b/src/peon/stdlib/builtins/misc.pn @@ -9,30 +9,6 @@ fn clock*: float { } -fn print*(x: int) { - #pragma[magic: "PrintInt64"] +fn print*[T: Number | string | bool | nan | inf](x: T) { + #pragma[magic: "print"] } - -fn print*(x: int32) { - #pragma[magic: "PrintInt32"] -} - - -fn print*(x: uint64) { - #pragma[magic: "PrintUInt64"] -} - - -fn print*(x: float) { - #pragma[magic: "PrintFloat64"] -} - - -fn print*(x: string) { - #pragma[magic: "PrintString"] -} - - -fn print*(x: bool) { - #pragma[magic: "PrintBool"] -} \ No newline at end of file diff --git a/src/peon/stdlib/builtins/values.pn b/src/peon/stdlib/builtins/values.pn index 4b43456..a267d18 100644 --- a/src/peon/stdlib/builtins/values.pn +++ b/src/peon/stdlib/builtins/values.pn @@ -1,23 +1,73 @@ # Stub type declarations for peon's intrinsic types -type int64* = object {#pragma[magic: "int64"]} -type int32* = object {#pragma[magic: "int32"]} -type int16* = object {#pragma[magic: "int16"]} -type int8* = object {#pragma[magic: "int8"]} -type uint64* = object {#pragma[magic: "uint64"]} -type uint32* = object {#pragma[magic: "uint32"]} -type uint16* = object {#pragma[magic: "uint16"]} -type uint8* = object {#pragma[magic: "uint8"]} -type float64* = object {#pragma[magic: "float64"]} -type float32* = object {#pragma[magic: "float32"]} -type bool* = object {#pragma[magic: "bool"]} -type string* = object {#pragma[magic: "string"]} -type any* = object {#pragma[magic: "any"]} +type int64* = object { + #pragma[magic: "int64"] +} + +type int32* = object { + #pragma[magic: "int32"] +} + +type int16* = object { + #pragma[magic: "int16"] +} + +type int8* = object { + #pragma[magic: "int8"] +} + +type uint64* = object { + #pragma[magic: "uint64"] +} + +type uint32* = object { + #pragma[magic: "uint32"] +} + +type uint16* = object { + #pragma[magic: "uint16"] +} + +type uint8* = object { + #pragma[magic: "uint8"] +} + +type float64* = object { + #pragma[magic: "float64"] +} + +type float32* = object { + #pragma[magic: "float32"] +} + +type bool* = object { + #pragma[magic: "bool"] +} + +type string* = object { + #pragma[magic: "string"] +} + +type any* = object { + #pragma[magic: "any"] +} + +type inf* = object { + #pragma[magic: "inf"] +} + +type nil* = object { + #pragma[magic: "nil"] +} + +type nan* = object { + #pragma[magic: "nan"] +} # Some convenience aliases -type int* = int64; -type float* = float64; +type int* = int64; +type float* = float64; type SignedInteger* = int64 | int32 | int16 | int8; type UnsignedInteger* = uint64 | uint32 | uint16 | uint8; type Integer* = SignedInteger | UnsignedInteger; diff --git a/src/util/symbols.nim b/src/util/symbols.nim index 05038ac..8a50a52 100644 --- a/src/util/symbols.nim +++ b/src/util/symbols.nim @@ -51,9 +51,6 @@ proc fillSymbolTable*(tokenizer: Lexer) = # but we don't need to care about that until # we're in the parsing/compilation steps so # it's fine - tokenizer.symbols.addKeyword("nan", NotANumber) - tokenizer.symbols.addKeyword("inf", Infinity) - tokenizer.symbols.addKeyword("nil", TokenType.Nil) tokenizer.symbols.addKeyword("true", True) tokenizer.symbols.addKeyword("false", False) tokenizer.symbols.addKeyword("ref", TokenType.Ref) diff --git a/tests/calls.pn b/tests/calls.pn index 12c58d4..3da7c97 100644 --- a/tests/calls.pn +++ b/tests/calls.pn @@ -19,4 +19,4 @@ fn fooBar(a, b: int): int { noReturn(1); -print(fooBar(1, 3) == 1); # true \ No newline at end of file +print(fooBar(1, 3) == 1); # true diff --git a/tests/generics.pn b/tests/generics.pn index 4af0c8d..addd178 100644 --- a/tests/generics.pn +++ b/tests/generics.pn @@ -1,4 +1,4 @@ -# Another test for generic functions +# A test for generic functions import std; @@ -9,4 +9,4 @@ fn sum[T: int | int32](a, b: T): T { print(sum(1, 2)); # Prints 3 print(sum(1'i32, 2'i32)); # Also prints 3! -#print(sum(1.0, 2.0)); # Will fail to compile \ No newline at end of file +#print(sum(1.0, 2.0)); # Will fail to compile diff --git a/tests/generics2.pn b/tests/generics2.pn index 2264253..ceda138 100644 --- a/tests/generics2.pn +++ b/tests/generics2.pn @@ -1,4 +1,6 @@ # Tests more stuff about generics. This test should fail to compile +import std; + fn identity(x: int32): int32 { return x;