diff --git a/README.md b/README.md index 7c1ae5f..9129ad5 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ Peon is a modern, multi-paradigm, async-first programming language with a focus [Go to the Manual](docs/manual.md) +## DISCLAIMER + +Peon is currently in the process of being rewritten, but the current design just doesn't allow for that. Hence, a [new repository](https://git.nocturn9x.space/nocturn9x/peon-rewrite) +has been created, to start from a blank slate and take what I learned from Peon 0.1.x and make it significantly better + ## What's peon? __Note__: For simplicity reasons, the verbs in this section refer to the present even though part of what's described here is not implemented yet. @@ -123,4 +128,4 @@ out for yourself. Fortunately, the process is quite straightforward: peon. Hopefully I will automate this soon, but as of right now the work is all manual -__Note__: On Linux, peon will also look into `~/.local/peon/stdlib` by default, so you can just create the `~/.local/peon` folder and copy `src/peon/stdlib` there \ No newline at end of file +__Note__: On Linux, peon will also look into `~/.local/peon/stdlib` by default, so you can just create the `~/.local/peon` folder and copy `src/peon/stdlib` there diff --git a/run_tests.py b/run_tests.py index 77c1bdc..4fde7b8 100644 --- a/run_tests.py +++ b/run_tests.py @@ -26,8 +26,8 @@ def main() -> int: try: cmd = f"nim {NIM_FLAGS} r src/main.nim {test_file} {PEON_FLAGS}" out = subprocess.run(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out.check_returncode() except subprocess.CalledProcessError as e: - print(f"An error occurred while executing test -> {type(e).__name__}: {e}") failed.add(test_file) continue if not all(map(lambda s: s == b"true", out.stdout.splitlines())): diff --git a/src/backend/bytecode/vm.nim b/src/backend/bytecode/vm.nim index 7f2dd01..cec0f1a 100644 --- a/src/backend/bytecode/vm.nim +++ b/src/backend/bytecode/vm.nim @@ -32,7 +32,6 @@ import std/sets import std/monotimes when debugVM or debugMem or debugGC or debugAlloc: - import std/sequtils import std/terminal @@ -227,17 +226,15 @@ proc markRoots(self: var PeonVM): HashSet[ptr HeapObject] = # the object to be reachable (potentially leading to a nasty # memory leak). Hopefully, in a 64-bit address space, this # occurrence is rare enough for us to ignore - var result = initHashSet[uint64](self.gc.pointers.len()) + result = initHashSet[ptr HeapObject](self.gc.pointers.len()) for obj in self.calls: if obj in self.gc.pointers: - result.incl(obj) + result.incl(cast[ptr HeapObject](obj)) for obj in self.operands: if obj in self.gc.pointers: - result.incl(obj) - var obj: ptr HeapObject + result.incl(cast[ptr HeapObject](obj)) for p in result: - obj = cast[ptr HeapObject](p) - if obj.mark(): + if p.mark(): when debugMarkGC: echo &"DEBUG - GC: Marked object: {obj[]}" when debugGC: @@ -842,17 +839,9 @@ proc dispatch*(self: var PeonVM) {.inline.} = # Pops a value off the operand stack discard self.pop() of PushC: - # Pushes a value from the operand stack - # onto the call stack + # Pops a value off the operand stack + # and pushes it onto the call stack self.pushc(self.pop()) - of PopRepl: - # Pops a peon object off the - # operand stack and prints it. - # Used in interactive REPL mode - if self.frames.len() !> 1: - discard self.pop() - continue - echo self.pop() of PopN: # Pops N elements off the call stack for _ in 0.. 0, we're # in a local scope, otherwise it's global depth*: int - # Are we in REPL mode? - replMode*: bool # List of all compile-time names names*: seq[Name] # Stores line data for error reporting @@ -222,12 +224,6 @@ type # Currently imported modules modules*: TableRef[string, Name] - TypedNode* = ref object - ## A wapper for AST nodes - ## with attached type information - kind*: Type - node*: ASTNode - ## Public getters for nicer error formatting @@ -235,19 +231,16 @@ proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >= self.ast.len proc getCurrentFunction*(self: Compiler): Declaration {.inline.} = (if self.currentFunction.isNil(): nil else: self.currentFunction.valueType.fun) proc getSource*(self: Compiler): string {.inline.} = self.source -## Some forward declarations (some of them arere actually stubs because nim forces forward declarations to be -## implemented in the same module). They are methods because we need to dispatch to their actual specific +## Some forward declarations (some of them are actually stubs because nim forces forward declarations to be +## implemented in the same module). Some of them are methods because we need to dispatch to their actual specific ## implementations inside each target module, so we need the runtime type of the compiler object to be ## taken into account -method makeConcrete(self: Compiler, node: GenericExpr, compile: bool = true): Type {.base.} = nil method expression*(self: Compiler, node: Expression, compile: bool = true): Type {.discardable, base.} = nil -method identifier*(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable, base.} = nil -method call*(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable, base.} = nil -method getItemExpr*(self: Compiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable, base.} = nil -method unary*(self: Compiler, node: UnaryExpr, compile: bool = true): Type {.discardable, base.} = nil -method binary*(self: Compiler, node: BinaryExpr, compile: bool = true): Type {.discardable, base.} = nil -method lambdaExpr*(self: Compiler, node: LambdaExpr, compile: bool = true): Type {.discardable, base.} = nil -method literal*(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable, base.} = nil +method prepareFunction*(self: Compiler, name: Name) {.base.} = discard +method dispatchPragmas(self: Compiler, name: Name) {.base.} = discard +method dispatchDelayedPragmas(self: Compiler, name: Name) {.base.} = discard +# These are not methods because their behavior is shared across backends and does +# not need to change proc infer*(self: Compiler, node: LiteralExpr): Type proc infer*(self: Compiler, node: Expression): Type proc inferOrError*(self: Compiler, node: Expression): Type @@ -256,9 +249,9 @@ proc findInModule*(self: Compiler, name: string, module: Name): seq[Name] proc findByType*(self: Compiler, name: string, kind: Type): seq[Name] proc compare*(self: Compiler, a, b: Type): bool proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name -method prepareFunction*(self: Compiler, name: Name) {.base.} = discard -method dispatchPragmas(self: Compiler, name: Name) {.base.} = discard -method dispatchDelayedPragmas(self: Compiler, name: Name) {.base.} = discard +proc resolve*(self: Compiler, name: string): Name +proc resolve*(self: Compiler, name: IdentExpr): Name +proc resolveOrError*[T: IdentExpr | string](self: Compiler, name: T): Name ## End of forward declarations ## Utility functions @@ -294,7 +287,7 @@ proc error*(self: Compiler, message: string, node: ASTNode = nil) {.inline.} = proc warning*(self: Compiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) = ## Raises a warning. Note that warnings are always disabled in REPL mode - if self.replMode or kind in self.disabledWarnings: + if kind in self.disabledWarnings: return var node: ASTNode = node var fn: Declaration @@ -354,7 +347,7 @@ proc wrap*(self: Type): Type = return self -proc unwrap*(self: Type): Type = +proc unwrap*(self: Type): Type {.inline.} = ## Unwraps a typevar if it's not already ## unwrapped if self.kind == Typevar: @@ -417,7 +410,7 @@ proc resolveOrError*[T: IdentExpr | string](self: Compiler, name: T): Name = self.error(&"reference to undefined name '{name}'") -proc compareUnions*(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool = +proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type, value: LiteralExpr]]): bool = ## Compares type unions between each other var long = a @@ -449,7 +442,7 @@ proc compare*(self: Compiler, a, b: Type): bool = 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: + if a.kind in [All, Auto] or b.kind in [All, Auto]: return true if a.kind == b.kind: # Here we compare types with the same kind discriminant @@ -459,7 +452,11 @@ proc compare*(self: Compiler, a, b: Type): bool = Char, Byte, String, Nil, TypeKind.Nan, Bool, TypeKind.Inf, Any, Auto: return true + of Array: + return self.compare(a.elemType, b.elemType) and a.size == b.size of Typevar: + if a.wrapped.isNil() or b.wrapped.isNil(): + return true return self.compare(a.wrapped, b.wrapped) of Union: return self.compareUnions(a.types, b.types) @@ -502,10 +499,6 @@ proc compare*(self: Compiler, a, b: Type): bool = return true else: discard # TODO: Custom types, enums - if a.kind == Typevar: - return self.compare(a.wrapped, b) - if b.kind == Typevar: - return self.compare(a, b.wrapped) if a.kind == Union: for constraint in a.types: if self.compare(constraint.kind, b) and constraint.match: @@ -589,9 +582,11 @@ proc toIntrinsic*(name: string): Type = elif name == "bool": return Type(kind: Bool, isBuiltin: true) elif name == "typevar": - return Type(kind: Typevar, isBuiltin: true) + return Type(kind: Typevar, isBuiltin: true, wrapped: Type(kind: Any)) elif name == "string": return Type(kind: String, isBuiltin: true) + elif name == "array": + return Type(kind: Array, isBuiltin: true, elements: @[]) proc infer*(self: Compiler, node: LiteralExpr): Type = @@ -632,33 +627,19 @@ proc infer*(self: Compiler, node: Expression): Type = ## returns it if node.isNil(): return nil + if node.isConst(): + return self.infer(LiteralExpr(node)) case node.kind: - of NodeKind.genericExpr: - result = self.makeConcrete(GenericExpr(node), compile=false) - of NodeKind.identExpr: - result = self.identifier(IdentExpr(node), compile=false, strict=false) - of NodeKind.unaryExpr: - result = self.unary(UnaryExpr(node), compile=false) - of NodeKind.binaryExpr: - result = self.binary(BinaryExpr(node), compile=false) - of {NodeKind.intExpr, NodeKind.hexExpr, NodeKind.binExpr, NodeKind.octExpr, - NodeKind.strExpr, NodeKind.falseExpr, NodeKind.trueExpr, NodeKind.floatExpr - }: - result = self.infer(LiteralExpr(node)) - of NodeKind.callExpr: - result = self.call(CallExpr(node), compile=false) of NodeKind.refExpr: result = Type(kind: Reference, value: self.infer(Ref(node).value)) of NodeKind.ptrExpr: result = Type(kind: Pointer, value: self.infer(Ptr(node).value)) of NodeKind.groupingExpr: result = self.infer(GroupingExpr(node).expression) - of NodeKind.getItemExpr: - result = self.getItemExpr(GetItemExpr(node), compile=false) - of NodeKind.lambdaExpr: - result = self.lambdaExpr(LambdaExpr(node), compile=false) else: - discard # TODO + # For most cases we can just dispatch to the target + # compiler which will tell us the type of the value + result = self.expression(node, compile=false) proc inferOrError*(self: Compiler, node: Expression): Type = @@ -682,11 +663,22 @@ proc stringify*(self: Compiler, typ: Type): string = TypeKind.Inf, Auto, Any: result &= ($typ.kind).toLowerAscii() of Typevar: - result = self.stringify(typ.wrapped) + result = &"typevar[{self.stringify(typ.wrapped)}]" of Pointer: result &= &"ptr {self.stringify(typ.value)}" of Reference: result &= &"ref {self.stringify(typ.value)}" + of CustomType: + result &= &"{typ.decl.name.token.lexeme}" + if typ.decl.generics.len() > 0: + result &= "[" + for i, gen in typ.decl.generics: + result &= &"{gen.name.token.lexeme}: {self.stringify(self.inferOrError(gen.cond))}" + if i < typ.decl.generics.len() - 1: + result &= ", " + result &= "]" + of Array: + result &= &"array[{self.stringify(typ.elemType)}, {typ.size}]" of Function: result &= "fn " if typ.fun.generics.len() > 0: @@ -746,6 +738,8 @@ proc findByName*(self: Compiler, name: string): seq[Name] = ## Looks for objects that have been already declared ## with the given name. Returns all objects that apply. for obj in reversed(self.names): + if obj.depth > self.depth: + continue if obj.ident.token.lexeme == name: if obj.owner.absPath != self.currentModule.absPath: if obj.isPrivate or self.currentModule notin obj.exportedTo: @@ -782,15 +776,6 @@ proc findByType*(self: Compiler, name: string, kind: Type): seq[Name] = result.add(obj) -proc findAtDepth*(self: Compiler, name: string, depth: int): seq[Name] {.used.} = - ## Looks for objects that have been already declared - ## with the given name at the given scope depth. - ## Returns all objects that apply - for obj in self.findByName(name): - if obj.depth == depth: - result.add(obj) - - 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 @@ -867,10 +852,11 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow else: msg &= " (compile with --showMismatches for more details)" self.error(msg, node) - # This is only true when we're called by self.patchForwardDeclarations() - if impl[0].valueType.forwarded and not allowFwd: - self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'") result = impl[0] + result.valueType = result.valueType.unwrap() + # This is only true when we're called by self.patchForwardDeclarations() + if result.valueType.forwarded and not allowFwd: + self.error(&"expecting an implementation for '{result.ident.token.lexeme}' declared in '{result.owner.ident.token.lexeme}' at line {result.ident.token.line} of type '{self.stringify(result.valueType)}'") result.resolved = true if result.kind == NameKind.Var and not result.valueType.nameObj.isNil(): # We found a function bound to a variable, @@ -887,34 +873,37 @@ proc beginScope*(self: Compiler) = inc(self.depth) -proc unpackTypes*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) = +proc unpackTypes*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: LiteralExpr]], accept: bool = true) = ## Recursively unpacks a type constraint - case condition.kind: - of identExpr: - var typ = self.inferOrError(condition) - if typ.kind != Typevar: - self.error(&"expecting a type name, got value of type {self.stringify(typ)} instead", condition) - typ = typ.wrapped - if typ.kind == Auto: - self.error("automatic types cannot be used within generics", condition) - list.add((accept, typ)) - of binaryExpr: - let condition = BinaryExpr(condition) - case condition.operator.lexeme: - of "|": - self.unpackTypes(condition.a, list) - self.unpackTypes(condition.b, list) - else: - self.error("invalid type constraint in generic declaration", condition) - of unaryExpr: - let condition = UnaryExpr(condition) - case condition.operator.lexeme: - of "~": - self.unpackTypes(condition.a, list, accept=false) - else: - self.error("invalid type constraint in generic declaration", condition) - else: - self.error("invalid type constraint in generic declaration", condition) + if condition.isConst(): + list.add((accept, self.inferOrError(condition), LiteralExpr(condition))) + else: + case condition.kind: + of identExpr: + var typ = self.inferOrError(condition) + if typ.kind != Typevar: + self.error(&"expecting a type name, got value of type {self.stringify(typ)} instead", condition) + typ = typ.unwrap() + if typ.kind == Auto: + self.error("automatic types cannot be used within generics", condition) + list.add((accept, typ, nil)) + of binaryExpr: + let condition = BinaryExpr(condition) + case condition.operator.lexeme: + of "|": + self.unpackTypes(condition.a, list) + self.unpackTypes(condition.b, list) + else: + self.error("invalid type constraint in generic declaration", condition) + of unaryExpr: + let condition = UnaryExpr(condition) + case condition.operator.lexeme: + of "~": + self.unpackTypes(condition.a, list, accept=false) + else: + self.error("invalid type constraint in generic declaration", condition) + else: + self.error("invalid type constraint in generic declaration", condition) proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = @@ -1010,10 +999,13 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = isPrivate: node.isPrivate, isReal: true, belongsTo: self.currentFunction, - valueType: Type(kind: CustomType) + valueType: Type(kind: CustomType, + decl: node) ) ) n = self.names[^1] + if node.generics.len() > 0: + n.isGeneric = true declaredName = node.name.token.lexeme if node.value.isNil(): discard # TODO: Fields diff --git a/src/frontend/compiler/targets/bytecode/opcodes.nim b/src/frontend/compiler/targets/bytecode/opcodes.nim index a98d578..060df83 100644 --- a/src/frontend/compiler/targets/bytecode/opcodes.nim +++ b/src/frontend/compiler/targets/bytecode/opcodes.nim @@ -14,10 +14,6 @@ ## Low level bytecode implementation details -import std/strutils -import std/strformat - - import util/multibyte @@ -69,13 +65,10 @@ type # to unary opcodes, while a and b # represent arguments to binary # opcodes. Other variable names (c, d, ...) - # may be used for more complex opcodes. If - # an opcode takes any arguments at runtime, - # they come from either the stack or the VM's - # closure array. Some other opcodes (e.g. - # jumps), take arguments in the form of 16 - # or 24 bit numbers that are defined statically - # at compilation time into the bytecode + # may be used for more complex opcodes. + # Some opcodes (e.g. jumps), take arguments in + # the form of 16 or 24 bit numbers that are defined + # statically at compilation time into the bytecode # These push a constant at position x in the # constant table onto the stack @@ -165,8 +158,7 @@ type PrintInf, PrintString, ## Basic stack operations - Pop, # Pops an element off the stack and discards it - PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode) + Pop, # Pops an element off the operand stack and discards it PopN, # Pops x elements off the call stack (optimization for exiting local scopes which usually pop many elements) ## Name resolution/handling LoadAttribute, # Pushes the attribute b of object a onto the stack @@ -196,8 +188,8 @@ type ## Misc Assert, # Raises an exception if x is false NoOp, # Just a no-op - PopC, # Pop off the call stack onto the operand stack - PushC, # Pop off the operand stack onto the call stack + PopC, # Pop a value off the call stack and discard it + PushC, # Pop a value off the operand stack and push it onto the call stack SysClock64, # Pushes the output of a monotonic clock on the stack LoadTOS, # Pushes the top of the call stack onto the operand stack DupTop, # Duplicates the top of the operand stack onto the operand stack diff --git a/src/frontend/compiler/targets/bytecode/target.nim b/src/frontend/compiler/targets/bytecode/target.nim index 38b3894..3c990b9 100644 --- a/src/frontend/compiler/targets/bytecode/target.nim +++ b/src/frontend/compiler/targets/bytecode/target.nim @@ -19,7 +19,6 @@ import std/algorithm import std/parseutils import std/strutils import std/sequtils -import std/sets import std/os @@ -80,12 +79,17 @@ type compilerProcs: TableRef[string, CompilerFunc] # Stores the position of all jumps jumps: seq[tuple[patched: bool, offset: int]] - # Metadata about function locations + # Metadata regarding function locations (used to construct + # the debugging fields in the resulting bytecode) functions: seq[tuple[start, stop, pos: int, fn: Name]] + # Metadata regarding forward declarations forwarded: seq[tuple[name: Name, pos: int]] # The topmost occupied stack slot # in the current frame (0-indexed) stackIndex: int + # All the lambdas we encountered (to know + # if we should compile them once we call + # them) lambdas: seq[LambdaExpr] @@ -106,10 +110,13 @@ method dispatchDelayedPragmas(self: BytecodeCompiler, name: Name) proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name) proc compileModule(self: BytecodeCompiler, module: Name) proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) +proc identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable.} +proc lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} +proc getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} # End of forward declarations -proc newBytecodeCompiler*(replMode: bool = false): BytecodeCompiler = +proc newBytecodeCompiler*: BytecodeCompiler = ## Initializes a new BytecodeCompiler object new(result) result.ast = @[] @@ -122,7 +129,6 @@ proc newBytecodeCompiler*(replMode: bool = false): BytecodeCompiler = result.modules = newTable[string, Name]() result.lambdas = @[] result.currentFunction = nil - result.replMode = replMode result.currentModule = nil result.compilerProcs = newTable[string, CompilerFunc]() result.compilerProcs["magic"] = CompilerFunc(kind: Immediate, handler: handleMagicPragma) @@ -153,7 +159,7 @@ proc emitBytes(self: BytecodeCompiler, bytarr: openarray[OpCode | uint8], line: for b in bytarr: self.emitByte(b, line) - +#[ proc printRepl(self: BytecodeCompiler, typ: Type, node: Expression) = ## Emits instruction to print ## peon types in REPL mode @@ -188,7 +194,7 @@ proc printRepl(self: BytecodeCompiler, typ: Type, node: Expression) = self.emitByte(PrintString, node.token.line) else: self.emitByte(PrintHex, node.token.line) - +]# proc makeConstant(self: BytecodeCompiler, val: Expression, typ: Type): array[3, uint8] = ## Adds a constant to the current chunk's constant table @@ -287,7 +293,7 @@ proc emitJump(self: BytecodeCompiler, opcode: OpCode, line: int): int = self.emitBytes(0.toTriple(), line) result = self.jumps.high() - +#[ proc fixFunctionOffsets(self: BytecodeCompiler, where, oldLen: int) = ## Fixes function offsets after the size of our ## bytecode has changed @@ -384,7 +390,7 @@ proc insertAt(self: BytecodeCompiler, where: int, opcode: OpCode, data: openarra self.fixLines(where, self.chunk.code.len() - oldLen, true) self.fixNames(where, oldLen) self.fixFunctionOffsets(oldLen, where) - +]# proc patchJump(self: BytecodeCompiler, offset: int) = @@ -463,7 +469,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio "LogicalNot": LogicalNot, "NegInf": LoadNInf, "Identity": Identity - }.to_table() + }.toTable() if fn.builtinOp == "print": var typ = self.inferOrError(args[0]).unwrap() case typ.kind: @@ -576,8 +582,6 @@ proc endScope(self: BytecodeCompiler) = var names: seq[Name] = @[] var popCount = 0 for name in self.names: - if self.replMode and name.depth == 0: - continue # We only pop names in scopes deeper than ours if name.depth > self.depth: if name.depth == 0 and not self.isMainModule: @@ -688,8 +692,7 @@ proc handleMagicPragma(self: BytecodeCompiler, pragma: Pragma, name: Name) = if name.valueType.kind == All: self.error("don't even think about it (compiler-chan is angry at you :/)", pragma) if name.valueType.isNil(): - self.error("'magic' pragma: wrong argument value", pragma.args[0]) - name.valueType.isBuiltin = true + self.error("'magic' pragma: wrong argument value", pragma.args[0]) else: self.error("'magic' pragma is not valid in this context") @@ -799,7 +802,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = ## its arguments and typechecking it # First we declare the function's generics, if it has any - var constraints: seq[tuple[match: bool, kind: Type]] = @[] + var constraints: seq[tuple[match: bool, kind: Type, value: LiteralExpr]] = @[] for gen in fn.node.generics: self.unpackTypes(gen.cond, constraints) self.names.add(Name(depth: fn.depth + 1, @@ -811,6 +814,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = belongsTo: fn, ident: gen.name, owner: self.currentModule, + kind: NameKind.CustomType, file: self.file)) constraints = @[] # We now declare and typecheck the function's @@ -825,14 +829,14 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = if self.names.high() > 16777215: self.error("cannot declare more than 16777215 variables at a time") inc(self.stackIndex) - typ = self.inferOrError(argument.valueType) + typ = self.inferOrError(argument.valueType).unwrap() # We can't use self.compare(), because it would # always just return true if typ.kind == Auto: fn.valueType.isAuto = true # Magic trick! We turn auto into any, just # to make our lives easier - typ = "any".toIntrinsic() + #typ = "any".toIntrinsic() self.names.add(Name(depth: fn.depth + 1, isPrivate: true, owner: fn.owner, @@ -858,7 +862,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) = fn.valueType.args.add((self.names[^1].ident.token.lexeme, typ, default)) # The function needs a return type too! if not node.returnType.isNil(): - fn.valueType.returnType = self.inferOrError(node.returnType) + fn.valueType.returnType = self.inferOrError(node.returnType).unwrap() if fn.valueType.returnType.kind == Auto: fn.valueType.isAuto = true # Here we don't bother changing the return type @@ -1042,7 +1046,7 @@ proc beginProgram(self: BytecodeCompiler): int = self.emitBytes(0.toTriple(), 1) -method literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} = +proc literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} = ## Emits instructions for literals such ## as singletons, strings and numbers case node.kind: @@ -1140,7 +1144,7 @@ method literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Typ self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)") -method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Type {.discardable.} = +proc unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Type {.discardable.} = ## Compiles all unary expressions var default: Expression let fn = Type(kind: Function, @@ -1158,7 +1162,7 @@ method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Typ self.generateCall(impl, @[node.a], impl.line) -method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} = +proc binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} = ## Compiles all binary expressions var default: Expression let fn = Type(kind: Function, returnType: "any".toIntrinsic(), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) @@ -1174,7 +1178,7 @@ method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): T self.generateCall(impl, @[node.a, node.b], impl.line) -method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable.} = +proc identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable.} = ## Compiles access to identifiers var s = name if s.isNil(): @@ -1223,7 +1227,7 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com self.emitBytes(s.position.toTriple(), s.ident.token.line) -method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} = +proc assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} = ## Compiles assignment expressions case node.kind: of assignExpr: @@ -1253,50 +1257,52 @@ method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): elif r.isLet: self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name) if r.valueType.kind != CustomType: - self.error("only types have fields", node) + self.error(&"cannot set attributes on object of type {self.stringify(r.valueType)}", node) else: self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)") -method makeConcrete(self: BytecodeCompiler, node: GenericExpr, compile: bool = true): Type = +proc makeConcrete(self: BytecodeCompiler, node: GenericExpr, compile: bool = true): Type = ## Builds a concrete type from the given generic ## instantiation var name = self.resolveOrError(node.ident) if not name.isGeneric: - self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required") - var fun = FunDecl(name.node) - if fun.generics.len() != node.args.len(): - self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()} instead)") + self.error(&"cannot instantiate a concrete type from non-generic type {self.stringify(name.valueType)}") + var decl = name.node + if decl.generics.len() != node.args.len(): + self.error(&"wrong number of types supplied for generic instantiation (expected {decl.generics.len()}, got {node.args.len()} instead)") var concrete = deepCopy(name.valueType) var types: seq[Type] = @[] var map = newTable[string, Type]() - for arg in node.args: - types.add(self.inferOrError(arg)) - if types[^1].kind != Typevar: - self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])} instead", arg) - for (gen, value) in zip(fun.generics, node.args): - map[gen.name.token.lexeme] = self.inferOrError(value) - for i, argument in concrete.args: - if argument.kind.kind != Generic: - continue - elif argument.name in map: - concrete.args[i].kind = map[argument.name] + for i, (gen, value) in zip(decl.generics, node.args): + self.check(value, self.inferOrError(gen.cond)) + types.add(self.inferOrError(value)) + if types[^1].kind != Typevar and not value.isConst(): + self.error(&"expecting type name or constant as generic parameter, got {self.stringify(types[^1])} instead", value) + map[decl.generics[i].name.token.lexeme] = types[^1] + case concrete.kind: + of Array: + concrete.size = parseInt(node.args[1].token.lexeme) + concrete.elemType = map["T"] + of Function: + for i, argument in concrete.args: + if argument.kind.kind != Generic: + continue + elif argument.kind.name in map: + concrete.args[i].kind = map[argument.kind.name] + else: + self.error(&"unknown generic argument name '{argument.kind.name}'", FunDecl(concrete.fun).arguments[i].name) + if not concrete.returnType.isNil() and concrete.returnType.kind == Generic: + if concrete.returnType.name in map: + concrete.returnType = map[concrete.returnType.name] + else: + self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun) else: - self.error(&"unknown generic argument name '{argument.name}'", concrete.fun) - if not concrete.returnType.isNil() and concrete.returnType.kind == Generic: - if concrete.returnType.name in map: - concrete.returnType = map[concrete.returnType.name] - else: - self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun) - if compile: - # Types don't exist at runtime, but if you want to - # assign them to variables then you need *something* - # to pop off the stack, so we just push a nil - self.emitByte(LoadNil, node.token.line) - result = concrete + discard + result = Type(kind: Typevar, wrapped: concrete) -method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type {.discardable.} = +proc call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type {.discardable.} = ## Compiles function calls var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[] var argExpr: seq[Expression] = @[] @@ -1354,6 +1360,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type self.generateCall(result, argExpr, node.token.line) result = result.returnType of NodeKind.getItemExpr: + # Calling stuff like a.b() let node = GetItemExpr(node.callee) result = self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))) var fn: Name @@ -1383,7 +1390,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type let node = GenericExpr(node.callee) let concrete = self.makeConcrete(node) var impl = self.resolve(node.ident).deepCopy() - impl.valueType = concrete + impl.valueType = concrete.unwrap() result = impl.valueType.returnType if compile: self.generateCall(impl, argExpr, node.token.line) @@ -1395,7 +1402,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type self.error(&"object of type '{self.stringify(typ)}' is not callable", node) -method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} = +proc getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} = ## Compiles accessing to fields of a type or ## module namespace. If the compile flag is set ## to false, no code is generated for resolving @@ -1445,7 +1452,7 @@ proc blockStmt(self: BytecodeCompiler, node: BlockStmt, compile: bool = true) = self.endScope() -method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} = +proc lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} = ## Compiles lambda functions as expressions result = Type(kind: Function, isLambda: true, fun: node, location: 0, compiled: true) let function = self.currentFunction @@ -1554,8 +1561,10 @@ method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} = ## Compiles all expressions case node.kind: + of NodeKind.arrayExpr: + discard # TODO of NodeKind.genericExpr: - return self.makeConcrete(GenericExpr(node)) + return self.makeConcrete(GenericExpr(node), compile) of NodeKind.callExpr: return self.call(CallExpr(node), compile) of NodeKind.getItemExpr: @@ -1573,12 +1582,12 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true of NodeKind.unaryExpr: # Unary expressions such as ~5 and -3 return self.unary(UnaryExpr(node), compile) + of NodeKind.binaryExpr: + # Binary expressions such as 2 ^ 5 and 0.66 * 3.14 + return self.binary(BinaryExpr(node), compile) of NodeKind.groupingExpr: # Grouping expressions like (2 + 1) return self.expression(GroupingExpr(node).expression, compile) - of NodeKind.binaryExpr: - # Binary expressions such as 2 ^ 5 and 0.66 * 3.14 - return self.binary(BinaryExpr(node)) of NodeKind.intExpr, NodeKind.hexExpr, NodeKind.binExpr, NodeKind.octExpr, NodeKind.strExpr, NodeKind.falseExpr, NodeKind.trueExpr, NodeKind.floatExpr: # Since all of these AST nodes share the @@ -1851,8 +1860,6 @@ proc statement(self: BytecodeCompiler, node: Statement) = # The expression has no type and produces no value, # so we don't have to pop anything discard - elif self.replMode: - self.printRepl(kind, expression) else: self.emitByte(Pop, node.token.line) of NodeKind.switchStmt: @@ -1906,7 +1913,6 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = if node.value.isNil(): # Variable has no value: the type declaration # takes over - # TODO: Implement T.default()! if self.compare(typ, "auto".toIntrinsic()): self.error("automatic types require initialization", node) typ = self.inferOrError(node.valueType) @@ -1915,15 +1921,17 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = # the typevar and compare the wrapped type, which is not what we want if typ.kind != Typevar: self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name) + # TODO: Implement T.default()! + self.error(&"cannot compute default value for {self.stringify(typ)}: please provide one explicitly") elif node.valueType.isNil(): # Variable has no type declaration: the type # of its value takes over - typ = self.inferOrError(node.value) + typ = self.inferOrError(node.value).unwrap() else: # Variable has both a type declaration and # a value: the value's type must match the # type declaration - let expected = self.inferOrError(node.valueType) + let expected = self.inferOrError(node.valueType).unwrap() if not self.compare(expected, "auto".toIntrinsic()): self.check(node.value, expected) # If this doesn't fail, then we're good @@ -1932,7 +1940,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) = # Let the compiler infer the type (this # is the default behavior already, but # some users may prefer to be explicit!) - typ = self.inferOrError(node.value) + typ = self.inferOrError(node.value).unwrap() self.expression(node.value) self.emitByte(AddVar, node.token.line) inc(self.stackIndex) @@ -2095,15 +2103,10 @@ proc compile*(self: BytecodeCompiler, ast: seq[Declaration], file: string, lines self.file = file self.depth = 0 self.currentFunction = nil - if self.replMode: - self.ast &= ast - self.source &= "\n" & source - self.lines &= lines - else: - self.ast = ast - self.current = 0 - self.lines = lines - self.source = source + self.ast = ast + self.current = 0 + self.lines = lines + self.source = source self.isMainModule = isMainModule self.disabledWarnings = disabledWarnings self.showMismatches = showMismatches @@ -2154,8 +2157,6 @@ proc compileModule(self: BytecodeCompiler, module: Name) = let currentModule = self.currentModule let mainModule = self.isMainModule let parentModule = self.parentModule - let replMode = self.replMode - self.replMode = false # Set the current module to the new module # and the current module as the parent module: # this is needed for export statements @@ -2189,7 +2190,6 @@ proc compileModule(self: BytecodeCompiler, module: Name) = self.currentModule = currentModule self.isMainModule = mainModule self.parentModule = parentModule - self.replMode = replMode self.lines = lines self.source = src self.modules[module.absPath] = module diff --git a/src/frontend/compiler/targets/bytecode/util/debugger.nim b/src/frontend/compiler/targets/bytecode/util/debugger.nim index 5d96303..6279ce7 100644 --- a/src/frontend/compiler/targets/bytecode/util/debugger.nim +++ b/src/frontend/compiler/targets/bytecode/util/debugger.nim @@ -17,7 +17,6 @@ import multibyte import std/strformat -import std/strutils import std/terminal @@ -223,8 +222,6 @@ proc disassembleInstruction*(self: Debugger) = self.current += 1 - - proc parseFunctions(self: Debugger) = ## Parses function information in the chunk var diff --git a/src/frontend/parsing/ast.nim b/src/frontend/parsing/ast.nim index e9604e6..afe438c 100644 --- a/src/frontend/parsing/ast.nim +++ b/src/frontend/parsing/ast.nim @@ -79,6 +79,7 @@ type pragmaExpr, refExpr, ptrExpr, + arrayExpr, genericExpr, switchStmt @@ -160,7 +161,6 @@ type ident*: IdentExpr args*: seq[Expression] - UnaryExpr* = ref object of Expression operator*: Token a*: Expression @@ -171,6 +171,11 @@ type # inherit from that and add a second operand b*: Expression + ArrayExpr* = ref object of Expression + elements*: seq[Expression] + startToken*: Token + endToken*: Token + YieldExpr* = ref object of Expression expression*: Expression @@ -335,6 +340,15 @@ proc newPragma*(name: IdentExpr, args: seq[LiteralExpr]): Pragma = result.token = name.token +proc newArrayExpr*(elements: seq[Expression], startToken, endToken: Token): ArrayExpr = + new(result) + result.kind = arrayExpr + result.elements = elements + result.startToken = startToken + result.endToken = endToken + result.token = startToken + + proc newRefExpr*(expression: Expression, token: Token): Ref = new(result) result.kind = refExpr @@ -801,6 +815,9 @@ proc `$`*(self: ASTNode): string = of genericExpr: var self = GenericExpr(self) result &= &"Generic(ident={self.ident}, args={self.args})" + of arrayExpr: + var self = ArrayExpr(self) + result &= &"Array(elements={self.elements})" else: discard @@ -865,6 +882,9 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] = if self.args.len() > 0: stop = getRelativeBoundaries(self.args[^1]).stop result = (ident.start, stop) + of arrayExpr: + var self = ArrayExpr(self) + result = (self.startToken.relPos.start, self.endToken.relPos.stop) else: result = (0, 0) \ No newline at end of file diff --git a/src/frontend/parsing/parser.nim b/src/frontend/parsing/parser.nim index 4c45b94..87d63e9 100644 --- a/src/frontend/parsing/parser.nim +++ b/src/frontend/parsing/parser.nim @@ -395,6 +395,16 @@ proc primary(self: Parser): Expression = discard self.step() result = newPtrExpr(self.expression(), self.peek(-1)) result.file = self.file + of TokenType.LeftBracket: + # Array + let start = self.step() + var elems: seq[Expression] = @[] + while not self.done() and not self.check(RightBracket): + elems.add(self.expression()) + if not self.match(Comma): + break + self.expect(RightBracket, "missing ']' in array construction") + result = newArrayExpr(elems, start, self.peek(-1)) else: self.error("invalid syntax") @@ -439,8 +449,7 @@ proc parseGenericArgs(self: Parser): Expression = var item = newIdentExpr(self.peek(-2), self.scopeDepth) var types: seq[Expression] = @[] while not self.check(RightBracket) and not self.done(): - self.expect(Identifier) - types.add(newIdentExpr(self.peek(-1), self.scopeDepth)) + types.add(self.expression()) if not self.match(Comma): break self.expect(RightBracket) @@ -962,15 +971,14 @@ proc varDecl(self: Parser, isLet: bool = false, var name = newIdentExpr(self.peek(-1), self.scopeDepth) let isPrivate = not self.match("*") self.checkDecl(isPrivate) - var valueType: IdentExpr + var valueType: Expression var hasInit = false var pragmas: seq[Pragma] = @[] if self.match(":"): # We don't enforce it here because # the compiler may be able to infer # the type later! - self.expect(Identifier, "expecting type name after ':'") - valueType = newIdentExpr(self.peek(-1), self.scopeDepth) + valueType = self.parseOr() if self.match("="): hasInit = true value = self.expression() diff --git a/src/main.nim b/src/main.nim index 643c833..8a53e68 100644 --- a/src/main.nim +++ b/src/main.nim @@ -146,6 +146,7 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints print(exc) except CompileError as exc: print(exc) + #raise except SerializationError as exc: var file = exc.file if file notin ["", ""]: diff --git a/src/peon/stdlib/builtins/values.pn b/src/peon/stdlib/builtins/values.pn index 73df018..baa4a06 100644 --- a/src/peon/stdlib/builtins/values.pn +++ b/src/peon/stdlib/builtins/values.pn @@ -74,6 +74,10 @@ type typevar* = object { } +type array*[T: any, S: int] = object { + #pragma[magic: "array"] +} + # Some convenience aliases type int* = int64; type float* = float64; diff --git a/src/peon/stdlib/std.pn b/src/peon/stdlib/std.pn index 906efb1..a2cc2c3 100644 --- a/src/peon/stdlib/std.pn +++ b/src/peon/stdlib/std.pn @@ -24,6 +24,6 @@ fn testGlobals*: bool { } -fn cast*[T: any](x: any): T { +fn cast*[T: typevar, D: any](x: T): D { #pragma[magic: "cast"] } \ No newline at end of file diff --git a/tests/cast.pn b/tests/cast.pn index 0912a0d..f900414 100644 --- a/tests/cast.pn +++ b/tests/cast.pn @@ -5,9 +5,5 @@ import std; # as the type I'm telling you, trust me bro". There is no data conversion # occurring whatsoever! For that, use converters (once they're implemented LoL) -print(cast[int](2.0) == 4611686018427387904); -print(cast[float](4611686018427387904) == 2.0); -# If that strikes your fancy, you can do this: -var x = int; -var caster = cast[x]; -print(caster(2.0) == 4611686018427387904); +print(cast[int, float](2.0) == 4611686018427387904); +print(cast[float, int](4611686018427387904) == 2.0);