diff --git a/README.md b/README.md index fa3d4be..ba94678 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Type system: - Custom types - Intrinsics ✅ + - Type unions ✅ - Generics ✅ - Functions ✅ - Closures ✅ @@ -85,8 +86,8 @@ Misc: - Pragmas ✅ - Attribute resolution ✅ - - UFCS - - Import system with namespaces and visibility control ✅ + - UFCS (Universal Function Call Syntax) + - Import system (with namespaces and visibility control) ✅ __Note__: Since peon isn't complete yet, the toolchain (lexer, parser, compiler and VM) may change, but since they @@ -101,9 +102,9 @@ have been implemented alredady): - Easy C/Nim interop via FFI - C/C++ backend - Nim backend -- Structured concurrency -- Capability-based programming (i.e. functions are passed objects to act on the real world) -- Parametric Polymorphism (with untagged typed unions) ✅ +- Structured concurrency (must-have!) +- Capability-based programming (i.e. functions are passed objects to act on the real world. This is just a maybe) +- Parametric Polymorphism ✅ - Simple OOP (with multiple dispatch!) - RTTI, with methods that dispatch at runtime based on the true type of a value - Limited compile-time evaluation (embed the Peon VM in the C/C++/Nim backend and use that to execute peon code at compile time) diff --git a/docs/manual.md b/docs/manual.md index 85c1045..e606610 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -135,7 +135,6 @@ genericSum(3.14, 0.1); genericSum(1'u8, 250'u8); ``` -__Note__: The generic `Number` type is currently not defined. You can use type unions instead #### More generics! diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 5176326..2fca63e 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -46,7 +46,7 @@ type UInt32, Int64, UInt64, Float32, Float64, Char, Byte, String, Function, CustomType, Nil, Nan, Bool, Inf, Typevar, Generic, - Reference, Pointer, Any, All + Reference, Pointer, Any, All, Union Type = ref object ## A wrapper around ## compile-time types @@ -77,6 +77,8 @@ type # would map to [(true, int), (false, uint)] cond: seq[tuple[match: bool, kind: Type]] name: string + of Union: + types: seq[tuple[match: bool, kind: Type]] else: discard @@ -261,14 +263,17 @@ proc expression(self: Compiler, node: Expression) 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) +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 getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} -proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type -proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type -proc inferOrError(self: Compiler, node: Expression, allowGeneric: bool = false): Type +proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.} +proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.} +proc infer(self: Compiler, node: LiteralExpr): Type +proc infer(self: Compiler, node: Expression): Type +proc inferOrError(self: Compiler, node: Expression): Type proc findByName(self: Compiler, name: string): seq[Name] proc findInModule(self: Compiler, name: string, module: Name): seq[Name] proc findByType(self: Compiler, name: string, kind: Type): seq[Name] @@ -723,8 +728,11 @@ proc getStackPos(self: Compiler, name: Name): int = # Arguments to builtin functions are optimized away to stack # temporaries. There is no stack frame for builtins, so we skip # these names too - elif variable.kind == Argument and variable.belongsTo.valueType.isBuiltinFunction: - continue + elif variable.kind == Argument: + if variable.belongsTo.valueType.isBuiltinFunction: + continue + elif not variable.belongsTo.resolved: + continue # This variable isn't in declared in our module, # but we may still have access to it elif variable.owner != name.owner: @@ -734,9 +742,11 @@ proc getStackPos(self: Compiler, name: Name): int = if variable.isPrivate or name.owner notin variable.exportedTo: inc(result) continue + # Note: this MUST be an if, NOT an elif! if name.ident == variable.ident and variable.depth == name.depth and name.owner == variable.owner: if not name.belongsTo.isNil() and not variable.belongsTo.isNil(): if name.belongsTo != variable.belongsTo and variable.belongsTo.depth > name.belongsTo.depth: + # Argument with the same name but in a different function: ignore it dec(result) continue found = true @@ -758,6 +768,28 @@ proc getClosurePos(self: Compiler, name: Name): int = return -1 +proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool = + ## Compares type unions between each other + var + long = a + short = 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: + continue + else: + matched = true + break + if not matched: + return false + matched = false + return true + + proc compare(self: Compiler, a, b: Type): bool = ## Compares two type objects ## for equality (works with nil!) @@ -765,36 +797,19 @@ proc compare(self: Compiler, a, b: Type): bool = # The nil code here is for void functions (when # we compare their return types) result = false + # Note: 'All' is a type internal to the peon + # compiler that cannot be generated from user + # code in any way. It's used mostly for matching + # function return types (at least until we don't + # have return type inference) and it matches any + # type, including nil if a.isNil(): - result = b.isNil() or b.kind == All - elif b.isNil(): - result = a.isNil() or a.kind == All - elif a.kind == All: - result = true - elif b.kind == All: - result = true - # 'Any' is an type internal to - # the peon compiler that cannot - # be produced from regular user - # code. We use it mostly when - # resolving function calls (where - # we don't care about the type of - # the function's return value, as - # long as it has one) because peon - # doesn't have return type inference - # (yet :)). A seemingly identical type - # 'All' also exists, but while 'Any' - # means 'any type', 'All' means 'any - # type or no type at all' - elif a.kind == Any: - result = not b.isNil() - elif b.kind == Any: - result = not a.isNil() - # Note: the elif chain here is important! - # If we used regular if statements, we'd run - # the risk of running more checks than we - # need to and incorrectly modifying the result - elif a.kind != Generic and b.kind != Generic and a.kind == b.kind: + return b.isNil() or b.kind == All + if b.isNil(): + return a.isNil() or a.kind == All + if 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 case a.kind: @@ -803,7 +818,9 @@ proc compare(self: Compiler, a, b: Type): bool = Char, Byte, String, Nil, Nan, Bool, Inf: # A value's type is always equal to # another one's - result = true + return true + of Union: + return self.compareUnions(a.types, b.types) of Reference, Pointer: # Here we already know that both # a and b are of either of the two @@ -856,24 +873,28 @@ proc compare(self: Compiler, a, b: Type): bool = if not self.compare(argA.kind, argB.kind): temp = false break - result = temp + return temp else: - discard # TODO: Custom types - elif a.kind == Generic: + discard # TODO: Custom types, enums + if 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: + 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: - for c1 in a.cond: - for c2 in b.cond: - if self.compare(c1.kind, c2.kind): - # Here return is fine, because there's - # no more checks after this one! - if c1.match != c2.match: - return false - else: - return false - # We only return at the end because when matching - # generics we want to match *all* constraints at - # once, not just find the first match - return true + return self.compareUnions(a.cond, b.cond) else: for constraint in a.cond: if self.compare(constraint.kind, b): @@ -882,16 +903,9 @@ proc compare(self: Compiler, a, b: Type): bool = else: return false return true - elif b.kind == Generic: + if b.kind == Generic: if a.kind == Generic: - for c1 in a.cond: - for c2 in b.cond: - if self.compare(c1.kind, c2.kind): - if c1.match != c2.match: - return false - else: - return false - return true + return self.compareUnions(a.cond, b.cond) else: for constraint in b.cond: if self.compare(constraint.kind, a): @@ -900,9 +914,18 @@ proc compare(self: Compiler, a, b: Type): bool = else: return false return true - # TODO: Is this ok? - else: - result = false + 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 + if b.kind == Any: + return a.kind == Any proc toIntrinsic(name: string): Type = @@ -951,7 +974,7 @@ proc toIntrinsic(name: string): Type = return nil -proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): 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(): @@ -995,88 +1018,26 @@ proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type discard # TODO -proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): 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). Always returns a concrete type - ## unless allowGeneric is set to true + ## returned) if node.isNil(): return nil - if node.isLiteral(): - return self.infer(LiteralExpr(node), allowGeneric) case node.kind: of identExpr: - let node = IdentExpr(node) - var name = self.resolve(node) - if not name.isNil(): - result = name.valueType - if not result.isNil() and result.kind == Generic and not allowGeneric: - if name.belongsTo.isNil(): - name = self.resolve(result.name) - if not name.isNil(): - result = name.valueType - else: - for arg in name.belongsTo.valueType.args: - if node.token.lexeme == arg.name: - result = arg.kind - else: - result = node.name.lexeme.toIntrinsic() + result = self.identifier(IdentExpr(node), compile=false).valueType of unaryExpr: - let node = UnaryExpr(node) - var default: Expression - let impl = self.match(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a), default)]), node) - result = impl.valueType.returnType - if result.kind == Generic and not allowGeneric: - result = self.specialize(impl, @[node.a]).valueType.returnType + result = self.unary(UnaryExpr(node), compile=false).valueType.returnType of binaryExpr: - let node = BinaryExpr(node) - var default: Expression - let impl = self.match(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a), default), ("", self.infer(node.b), default)]), node) - result = impl.valueType.returnType - if result.kind == Generic and not allowGeneric: - result = self.specialize(impl, @[node.a, node.b]).valueType.returnType + result = self.binary(BinaryExpr(node), compile=false).valueType.returnType of {intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, infExpr, nanExpr, floatExpr, nilExpr }: result = self.infer(LiteralExpr(node)) - of lambdaExpr: - var node = LambdaExpr(node) - result = Type(kind: Function, returnType: nil, args: @[], isLambda: true, fun: node) - var default: Expression - if not node.returnType.isNil(): - result.returnType = self.infer(node.returnType) - for argument in node.arguments: - result.args.add((argument.name.token.lexeme, self.infer(argument.valueType), default)) - of callExpr: - var node = CallExpr(node) - case node.callee.kind: - of identExpr: - let resolved = self.resolve(IdentExpr(node.callee)) - if not resolved.isNil(): - case resolved.valueType.kind: - of Function: - if resolved.isGeneric and not allowGeneric: - var args: seq[Expression] = @[] - for argument in node.arguments.positionals: - args.add(argument) - for argument in node.arguments.keyword: - args.add(argument.value) - result = self.specialize(resolved, args).valueType.returnType - else: - result = resolved.valueType.returnType - else: - result = resolved.valueType - else: - result = nil - of lambdaExpr: - result = self.infer(LambdaExpr(node.callee).returnType) - of callExpr: - result = self.infer(CallExpr(node.callee)) - if not result.isNil(): - result = result.returnType - else: - discard # Unreachable + of NodeKind.callExpr: + result = self.call(CallExpr(node), compile=false).valueType.returnType of varExpr: result = self.infer(Var(node).value) result.mutable = true @@ -1089,15 +1050,15 @@ proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type = of NodeKind.getItemExpr: result = self.getItemExpr(GetItemExpr(node), compile=false) else: - discard # Unreachable + discard # TODO -proc inferOrError(self: Compiler, node: Expression, allowGeneric: bool = false): 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 - result = self.infer(node, allowGeneric) + result = self.infer(node) if result.isNil(): self.error("expression has no type", node) @@ -1145,6 +1106,15 @@ proc typeToStr(self: Compiler, typ: Type): string = result &= ", " else: result &= "}" + of Any: + return "any" + of Union: + for i, condition in typ.types: + if i > 0: + result &= " | " + if not condition.match: + result &= "~" + result &= self.typeToStr(condition.kind) of Generic: for i, condition in typ.cond: if i > 0: @@ -1204,12 +1174,12 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: var impl: seq[Name] = @[] var temp: Name for obj in self.findByName(name): - if obj.isGeneric: + if obj.isGeneric and args.len() > 0: temp = self.specialize(obj, args) else: temp = obj if self.compare(kind, temp.valueType): - impl.add(temp) + impl.add(self.match(name, temp.valueType, node)) if impl.len() == 0: var msg = &"failed to find a suitable implementation for '{name}'" let names = self.findByName(name) @@ -1423,7 +1393,7 @@ proc endScope(self: Compiler) = # Arguments to builtin functions become temporaries on the # stack and are popped automatically continue - if not name.belongsTo.resolved or not name.isReal: + if not name.belongsTo.resolved or not name.belongsTo.isReal: # Function hasn't been compiled yet, # so we can't get rid of its arguments # (it may need them later) @@ -1492,7 +1462,31 @@ proc unpackGenerics(self: Compiler, condition: Expression, list: var seq[tuple[m self.error("invalid type constraint in generic declaration", condition) -proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.discardable.} = +proc unpackUnion(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = + ## Recursively unpacks a type union + case condition.kind: + of identExpr: + list.add((accept, self.inferOrError(condition))) + of binaryExpr: + let condition = BinaryExpr(condition) + case condition.operator.lexeme: + of "|": + self.unpackGenerics(condition.a, list) + self.unpackGenerics(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) + 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.} = ## 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() @@ -1555,8 +1549,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d of NodeKind.importStmt: var node = ImportStmt(node) # We change the name of the module internally so that - # if you do import /path/to/mod, then doing mod.f() - # will work without any extra work on our end. Note how + # if you import /path/to/mod, then doing mod.f() will + # still work without any extra work on our end. Note how # we don't change the metadata about the identifier's # position so that error messages still highlight the # full path @@ -1564,7 +1558,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename() self.names.add(Name(depth: self.depth, owner: self.currentModule, - file: "", # The file of the module isn't known until compiled! + file: "", # The file of the module isn't known until it's compiled! path: path, ident: node.moduleName, line: node.moduleName.token.line, @@ -1574,8 +1568,34 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d )) n = self.names[^1] declaredName = self.names[^1].ident.token.lexeme + of NodeKind.typeDecl: + var node = TypeDecl(node) + self.names.add(Name(kind: NameKind.CustomType, + depth: self.depth, + owner: self.currentModule, + node: node, + ident: node.name, + line: node.token.line, + isPrivate: node.isPrivate, + isReal: true, + belongsTo: self.currentFunction + ) + ) + n = self.names[^1] + if node.value.isNil(): + discard # TODO: Fields + else: + case node.value.kind: + of identExpr: + n.valueType = self.inferOrError(node.value) + of binaryExpr: + # Type union + n.valueType = Type(kind: Union, types: @[]) + self.unpackUnion(node.value, n.valueType.types) + else: + discard else: - discard # TODO: Types, enums + discard # TODO: enums self.dispatchPragmas(n) case n.kind: of NameKind.Function: @@ -1629,12 +1649,43 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) = self.error("'magic' pragma: wrong number of arguments") elif pragma.args[0].kind != strExpr: self.error("'magic' pragma: wrong type of argument (constant string expected)") - elif name.node.kind != NodeKind.funDecl: + elif name.node.kind == NodeKind.funDecl: + name.valueType.isBuiltinFunction = 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]) + else: self.error("'magic' pragma is not valid in this context") - var node = FunDecl(name.node) - name.valueType.isBuiltinFunction = true - name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2] - proc handleErrorPragma(self: Compiler, pragma: Pragma, name: Name) = @@ -1869,31 +1920,36 @@ proc literal(self: Compiler, node: ASTNode) = self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)") -proc unary(self: Compiler, node: UnaryExpr) {.inline.} = +proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.} = ## Compiles all unary expressions var default: Expression let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default)]) - let impl = self.match(node.token.lexeme, fn, node, @[node.a]) - self.generateCall(impl, @[node.a], impl.line) + result = self.match(node.token.lexeme, fn, node, @[node.a]) + if compile: + self.generateCall(result, @[node.a], result.line) -proc binary(self: Compiler, node: BinaryExpr) {.inline.} = +proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.} = ## Compiles all binary expressions var default: Expression let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) - let impl = self.match(node.token.lexeme, fn, node, @[node.a, node.b]) - self.generateCall(impl, @[node.a, node.b], impl.line) + result = self.match(node.token.lexeme, fn, node, @[node.a, node.b]) + if compile: + self.generateCall(result, @[node.a, node.b], result.line) -proc identifier(self: Compiler, node: IdentExpr, name: Name = nil) = +proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Name {.discardable.} = ## Compiles access to identifiers var s = name if s.isNil(): s = self.resolveOrError(node) var t = self.findByType(s.ident.token.lexeme, Type(kind: All)) s = t[0] # Shadowing! + result = s + if not compile: + return if s.isConst: # Constants are always emitted as Load* instructions # no matter the scope depth @@ -1943,7 +1999,7 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil) = self.emitBytes(self.getStackPos(s).toTriple(), s.ident.token.line) -proc assignment(self: Compiler, node: ASTNode) = +proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.discardable.} = ## Compiles assignment expressions case node.kind: of assignExpr: @@ -1956,6 +2012,8 @@ proc assignment(self: Compiler, node: ASTNode) = self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name) self.check(node.value, r.valueType) self.expression(node.value) + if not compile: + return if not r.isClosedOver: self.emitByte(StoreVar, node.token.line) self.emitBytes(self.getStackPos(r).toTriple(), node.token.line) @@ -1966,8 +2024,7 @@ proc assignment(self: Compiler, node: ASTNode) = self.emitByte(StoreClosure, node.token.line) self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line) of setItemExpr: - let typ = self.inferOrError(SetItemExpr(node)) - + discard # TODO else: self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)") @@ -2043,6 +2100,9 @@ proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) {. proc prepareFunction(self: Compiler, fn: Name) = + ## "Prepares" a function declaration by declaring + ## its arguments and typechecking it + # First we declare the function's generics, if it has any. # This is because the function's return type may in itself # be a generic, so it needs to exist first @@ -2074,7 +2134,7 @@ proc prepareFunction(self: Compiler, fn: Name) = file: fn.file, isConst: false, ident: argument.name, - valueType: self.inferOrError(argument.valueType, allowGeneric=true), + valueType: self.inferOrError(argument.valueType), codePos: 0, isLet: false, line: argument.name.token.line, @@ -2091,7 +2151,7 @@ proc prepareFunction(self: Compiler, fn: Name) = fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType, default)) # The function needs a return type too! if not FunDecl(fn.node).returnType.isNil(): - fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType, allowGeneric=true) + fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType) proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = @@ -2108,7 +2168,7 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = self.emitByte(LoadUInt64, line) self.emitBytes(self.chunk.writeConstant(fn.codePos.toLong()), line) else: - discard + discard # Unreachable if fn.valueType.forwarded: self.forwarded.add((fn, self.chunk.consts.high() - 7)) self.emitByte(LoadUInt64, line) @@ -2172,14 +2232,14 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name = discard # TODO: Custom user-defined types -proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = +proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.} = ## Compiles code to call a chain of function calls var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[] var argExpr: seq[Expression] = @[] var default: Expression var kind: Type for i, argument in node.arguments.positionals: - kind = self.infer(argument, allowGeneric=false) # We don't use inferOrError so that we can raise a more appropriate error message + kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message if kind.isNil(): if argument.kind == NodeKind.identExpr: self.error(&"reference to undeclared name '{argument.token.lexeme}'", argument) @@ -2187,7 +2247,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = args.add(("", kind, default)) argExpr.add(argument) for i, argument in node.arguments.keyword: - kind = self.infer(argument.value, allowGeneric=false) + kind = self.infer(argument.value) if kind.isNil(): if argument.value.kind == NodeKind.identExpr: self.error(&"reference to undeclared name '{argument.value.token.lexeme}'", argument.value) @@ -2199,7 +2259,8 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = # 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 - self.generateCall(result, argExpr, node.token.line) + if compile: + self.generateCall(result, argExpr, node.token.line) of NodeKind.callExpr: # Calling a call expression, like hello()() var node: Expression = node @@ -2215,7 +2276,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = # the way back to the first one earlier) and work # our way to the innermost call for exp in reversed(all): - self.callExpr(exp) + self.call(exp, compile) # TODO: Calling lambdas on-the-fly (i.e. on the same line) else: let typ = self.infer(node) @@ -2252,7 +2313,7 @@ proc expression(self: Compiler, node: Expression) = ## Compiles all expressions case node.kind: of NodeKind.callExpr: - self.callExpr(CallExpr(node)) + self.call(CallExpr(node)) of NodeKind.getItemExpr: self.getItemExpr(GetItemExpr(node)) of NodeKind.pragmaExpr: @@ -2377,8 +2438,7 @@ proc breakStmt(self: Compiler, node: BreakStmt) = proc assertStmt(self: Compiler, node: AssertStmt) = ## Compiles assert statements (raise ## AssertionError if the expression is falsey) - self.expression(node.expression) - self.emitByte(OpCode.Assert, node.token.line) + # TODO proc forEachStmt(self: Compiler, node: ForEachStmt) = @@ -2388,7 +2448,7 @@ proc forEachStmt(self: Compiler, node: ForEachStmt) = proc importStmt(self: Compiler, node: ImportStmt) = ## Imports a module at compile time - self.declareName(node) + self.declare(node) var module = self.names[^1] try: self.compileModule(module) @@ -2540,7 +2600,7 @@ proc varDecl(self: Compiler, node: VarDecl) = typ = expected self.expression(node.value) self.emitByte(AddVar, node.token.line) - self.declareName(node) + self.declare(node) var name = self.names[^1] name.valueType = typ @@ -2587,7 +2647,6 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) = else: self.chunk.functions.add(0.toDouble()) if BlockStmt(node.body).code.len() == 0: - raise newException(IndexDefect, "") self.error("cannot declare function with empty body") # Since the deferred array is a linear # sequence of instructions and we want @@ -2655,12 +2714,12 @@ proc declaration(self: Compiler, node: Declaration) = ## the first time case node.kind: of NodeKind.funDecl: - var name = self.declareName(node) + var name = self.declare(node) if name.isGeneric: # We typecheck generics immediately self.funDecl(FunDecl(node), name) of NodeKind.typeDecl: - self.declareName(node) + self.declare(node) of NodeKind.varDecl: # We compile this immediately because we # need to keep the stack in the right state @@ -2708,7 +2767,7 @@ proc compileModule(self: Compiler, module: Name) = var moduleName = module.path & ".pn" for i, searchPath in moduleLookupPaths: if searchPath == "": - path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName)) + path = absolutePath(joinPath(splitPath(self.file).head, moduleName)) else: path = joinPath(searchPath, moduleName) if fileExists(path): diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index 6838439..4d4c1b7 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -276,6 +276,7 @@ type isEnum*: bool isRef*: bool parent*: IdentExpr + value*: Expression Pragma* = ref object of Expression name*: IdentExpr diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index e55e95a..015ccb1 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -139,9 +139,8 @@ proc newOperatorTable: OperatorTable = result.tokens = @[] for prec in Precedence: result.precedence[prec] = @[] - # Assignment and attribute accessing are (currently) - # the only builtins that cannot be easily implemented - # from within peon itself + # These operators are currently not built-in + # due to compiler limitations result.addOperator("=") result.addOperator(".") @@ -735,7 +734,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement = var path = "" for i, searchPath in moduleLookupPaths: if searchPath == "": - path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName)) + path = absolutePath(joinPath(splitPath(self.file).head, moduleName)) else: path = joinPath(searchPath, moduleName) if fileExists(path): @@ -986,6 +985,8 @@ proc parseGenericConstraint(self: Parser): Expression = result = newBinaryExpr(result, self.step(), self.parseGenericConstraint()) of "~": result = newUnaryExpr(self.step(), result) + of ",": + discard # Comma is handled in parseGenerics() else: self.error("invalid type constraint in generic declaration") @@ -1183,9 +1184,9 @@ proc typeDecl(self: Parser): TypeDecl = ## Parses type declarations let token = self.peek(-1) self.expect(Identifier, "expecting type name after 'type'") + var name = newIdentExpr(self.peek(-1), self.scopeDepth) let isPrivate = not self.match("*") self.checkDecl(isPrivate) - var name = newIdentExpr(self.peek(-1), self.scopeDepth) var fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]] = @[] var defaults: seq[Expression] = @[] var generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[] @@ -1194,44 +1195,58 @@ proc typeDecl(self: Parser): TypeDecl = if self.match(LeftBracket): self.parseGenerics(result) self.expect("=", "expecting '=' after type name") - case self.step().lexeme: + var hasNone = false + case self.peek().lexeme: of "ref": + discard self.step() self.expect("object", "expecting 'object' after 'ref'") result.isRef = true of "enum": + discard self.step() result.isEnum = true of "object": + discard self.step() discard # Default case else: - self.error("invalid syntax") - if not result.isEnum and self.match("of"): - self.expect(Identifier, "expecting parent type name after 'of'") - result.parent = newIdentExpr(self.peek(-1)) - self.expect(LeftBrace, "expecting '{' after type declaration") - if self.match(TokenType.Pragma): - for pragma in self.parsePragmas(): - pragmas.add(pragma) - var - argName: IdentExpr - argPrivate: bool - argType: Expression - while not self.match(RightBrace) and not self.done(): - self.expect(Identifier, "expecting field name") - argName = newIdentExpr(self.peek(-1), self.scopeDepth) - if not result.isEnum: - argPrivate = not self.match("*") - self.expect(":", "expecting ':' after field name") - argType = self.expression() - result.fields.add((argName, argType, argPrivate)) - if self.match("="): - result.defaults.add(self.expression()) - else: - result.fields.add((argName, nil, false)) - if not result.isEnum: - self.expect(";", "expecting semicolon after type field declaration") - else: - if not self.check(RightBrace): - self.expect(",", "expecting comma after enum field declaration") + hasNone = true + if hasNone: + result.value = self.expression() + while not self.check(";"): + case self.peek().lexeme: + of "|": # Type unions! + result.value = newBinaryExpr(result.value, self.step(), self.expression()) + else: + discard + else: + if not result.isEnum and not hasNone and self.match("of"): + self.expect(Identifier, "expecting parent type name after 'of'") + result.parent = newIdentExpr(self.peek(-1)) + if not self.match(";"): # Type has no fields. Usually for unions or aliases + self.expect(LeftBrace, "expecting '{' after type declaration") + if self.match(TokenType.Pragma): + for pragma in self.parsePragmas(): + pragmas.add(pragma) + var + argName: IdentExpr + argPrivate: bool + argType: Expression + while not self.match(RightBrace) and not self.done(): + self.expect(Identifier, "expecting field name") + argName = newIdentExpr(self.peek(-1), self.scopeDepth) + if not result.isEnum: + argPrivate = not self.match("*") + self.expect(":", "expecting ':' after field name") + argType = self.expression() + result.fields.add((argName, argType, argPrivate)) + if self.match("="): + result.defaults.add(self.expression()) + else: + result.fields.add((argName, nil, false)) + if not result.isEnum: + self.expect(";", "expecting semicolon after type field declaration") + else: + if not self.check(RightBrace): + self.expect(",", "expecting comma after enum field declaration") result.pragmas = pragmas diff --git a/src/main.nim b/src/main.nim index 79ab3f5..50cb26c 100644 --- a/src/main.nim +++ b/src/main.nim @@ -85,6 +85,8 @@ proc repl = elif input == "#clear": stdout.write("\x1Bc") continue + elif input == "": + continue else: current &= input & "\n" if current.len() == 0: diff --git a/src/peon/stdlib/builtins/arithmetics.pn b/src/peon/stdlib/builtins/arithmetics.pn index d47b83b..9619ce8 100644 --- a/src/peon/stdlib/builtins/arithmetics.pn +++ b/src/peon/stdlib/builtins/arithmetics.pn @@ -1,12 +1,14 @@ # Builtin arithmetic operators for Peon -# Note: Most of these do nothing on their own. All they do + +# Note: These stubs do nothing on their own. All they do # is serve as placeholders for emitting specific VM # instructions. They're implemented this way because: # - They tie into the existing type system nicely # - It makes the implementation easier and more flexible +import values; -operator `+`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { +operator `+`*[T: Integer](a, b: T): T { #pragma[magic: "Add", pure] } @@ -21,7 +23,7 @@ operator `+`(a, b: float32): float32 { } -operator `-`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { +operator `-`*[T: Integer](a, b: T): T { #pragma[magic: "Subtract", pure] } @@ -36,12 +38,12 @@ operator `-`*(a, b: float32): float32 { } -operator `-`*[T: int | int32 | int16 | int8](a: T): T { +operator `-`*[T: SignedInteger](a: T): T { #pragma[magic: "Negate", pure] } -operator `-`*[T: uint64 | uint32 | uint16 | uint8](a: T): T { +operator `-`*[T: UnsignedInteger](a: T): T { #pragma[magic: "", error: "unsigned integer cannot be negative"] } @@ -61,7 +63,7 @@ operator `-`*(a: inf): inf { } -operator `*`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { +operator `*`*[T: Integer](a, b: T): T { #pragma[magic: "Multiply", pure] } @@ -76,12 +78,12 @@ operator `*`*(a, b: float32): float32 { } -operator `/`*[T: int | int32 | int16 | int8](a, b: T): T { +operator `/`*[T: SignedInteger](a, b: T): T { #pragma[magic: "SignedDivide", pure] } -operator `/`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T { +operator `/`*[T: UnsignedInteger](a, b: T): T { #pragma[magic: "Divide", pure] } @@ -96,16 +98,16 @@ operator `/`*(a, b: float32): float32 { } -operator `**`*[T: int | int32 | int16 | int8](a, b: T): T { +operator `**`*[T: SignedInteger](a, b: T): T { #pragma[magic: "SignedPow", pure] } -operator `**`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T { +operator `**`*[T: UnsignedInteger](a, b: T): T { #pragma[magic: "Pow", pure] } -operator `%`*(a, b: int64): int64 { +operator `%`*[T: SignedInteger](a, b: T): T { #pragma[magic: "SignedMod", pure] } diff --git a/src/peon/stdlib/builtins/bitwise.pn b/src/peon/stdlib/builtins/bitwise.pn index 7f6d639..8ff9cf2 100644 --- a/src/peon/stdlib/builtins/bitwise.pn +++ b/src/peon/stdlib/builtins/bitwise.pn @@ -1,4 +1,6 @@ # Bitwise operations on primitive types +import values; + operator `&`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { #pragma[magic: "And", pure] diff --git a/src/peon/stdlib/builtins/comparisons.pn b/src/peon/stdlib/builtins/comparisons.pn index a7664c9..9489ad4 100644 --- a/src/peon/stdlib/builtins/comparisons.pn +++ b/src/peon/stdlib/builtins/comparisons.pn @@ -1,30 +1,31 @@ # Comparison operators +import values; -operator `>`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { + +operator `>`*[T: Number](a, b: T): bool { #pragma[magic: "GreaterThan", pure] } -operator `<`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { +operator `<`*[T: Number](a, b: T): bool { #pragma[magic: "LessThan", pure] } -operator `==`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { +operator `==`*[T: Number](a, b: T): bool { #pragma[magic: "Equal", pure] - } -operator `!=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { +operator `!=`*[T: Number](a, b: T): bool { #pragma[magic: "NotEqual", pure] } -operator `>=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { +operator `>=`*[T: Number](a, b: T): bool { #pragma[magic: "GreaterOrEqual", pure] } -operator `<=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { +operator `<=`*[T: Number](a, b: T): bool { #pragma[magic: "LessOrEqual", pure] } diff --git a/src/peon/stdlib/builtins/logical.pn b/src/peon/stdlib/builtins/logical.pn index c6d945d..87f49fc 100644 --- a/src/peon/stdlib/builtins/logical.pn +++ b/src/peon/stdlib/builtins/logical.pn @@ -1,4 +1,6 @@ # Logical operators +import values; + operator `and`*(a, b: bool): bool { #pragma[magic: "LogicalAnd", pure] diff --git a/src/peon/stdlib/builtins/misc.pn b/src/peon/stdlib/builtins/misc.pn index 6d51988..a3674e6 100644 --- a/src/peon/stdlib/builtins/misc.pn +++ b/src/peon/stdlib/builtins/misc.pn @@ -1,4 +1,5 @@ # Various miscellaneous utilities +import values; # Some useful builtins diff --git a/src/peon/stdlib/builtins/values.pn b/src/peon/stdlib/builtins/values.pn new file mode 100644 index 0000000..4b43456 --- /dev/null +++ b/src/peon/stdlib/builtins/values.pn @@ -0,0 +1,24 @@ +# 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"]} + +# Some convenience aliases +type int* = int64; +type float* = float64; +type SignedInteger* = int64 | int32 | int16 | int8; +type UnsignedInteger* = uint64 | uint32 | uint16 | uint8; +type Integer* = SignedInteger | UnsignedInteger; +type Number* = Integer | float64 | float32; \ No newline at end of file diff --git a/src/peon/stdlib/std.pn b/src/peon/stdlib/std.pn index 8210e5f..7d96d68 100644 --- a/src/peon/stdlib/std.pn +++ b/src/peon/stdlib/std.pn @@ -1,18 +1,20 @@ ## The peon standard library +import builtins/values; import builtins/arithmetics; import builtins/bitwise; import builtins/logical; import builtins/misc; import builtins/comparisons; - +export values; export arithmetics; export bitwise; export logical; export misc; export comparisons; + var version* = 1; var _private = 5; # Invisible outside the module (underscore is to silence warning) var test* = 0x60; \ No newline at end of file diff --git a/src/util/fmterr.nim b/src/util/fmterr.nim index 8f0eacc..0be908a 100644 --- a/src/util/fmterr.nim +++ b/src/util/fmterr.nim @@ -32,9 +32,10 @@ proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], f if not fn.isNil() and fn.kind == funDecl: stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme) stderr.styledWriteLine(styleBright, fgDefault, ": ", msg) - stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0.. 0: + stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..