diff --git a/src/config.nim b/src/config.nim index 8aca4c7..1fda235 100644 --- a/src/config.nim +++ b/src/config.nim @@ -19,15 +19,15 @@ const BYTECODE_MARKER* = "PEON_BYTECODE" const HEAP_GROW_FACTOR* = 2 # How much extra memory to allocate for dynamic arrays and garbage collection when resizing when HEAP_GROW_FACTOR <= 1: {.fatal: "Heap growth factor must be > 1".} -const PEON_VERSION* = (major: 0, minor: 4, patch: 0) +const PEON_VERSION* = (major: 0, minor: 1, patch: 0) const PEON_RELEASE* = "alpha" -const PEON_COMMIT_HASH* = "ed79385e2a93100331697f26a4a90157e60ad27a" +const PEON_COMMIT_HASH* = "231b7012a17e98c7bcb2b475289cfcdddaf4a7d8" when len(PEON_COMMIT_HASH) != 40: {.fatal: "The git commit hash must be exactly 40 characters long".} const PEON_BRANCH* = "master" when len(PEON_BRANCH) > 255: {.fatal: "The git branch name's length must be less than or equal to 255 characters".} -const DEBUG_TRACE_VM* = true # Traces VM execution +const DEBUG_TRACE_VM* = false # Traces VM execution const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO) const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation const DEBUG_TRACE_COMPILER* = false # Traces the compiler diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 797fc10..4b5c846 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -38,7 +38,12 @@ type Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32, Float64, Char, Byte, String, Function, CustomType, - Nil, Nan, Bool, Inf, Typedesc, Generic + Nil, Nan, Bool, Inf, Typedesc, Generic, + + Any # Any is used internally in a few cases, + # for example when looking for operators + # when only the type of the arguments is of + # interest Type* = ref object ## A wrapper around ## compile-time types @@ -404,7 +409,8 @@ proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool = if a.args.len() != b.args.len(): return false elif not self.compareTypes(a.returnType, b.returnType): - return false + if a.returnType.kind != Any and b.returnType.kind != Any: + return false for (argA, argB) in zip(a.args, b.args): if not self.compareTypes(argA, argB): return false @@ -419,7 +425,7 @@ proc compareTypes(self: Compiler, a, b: Type): bool = if a == nil: return b == nil elif b == nil: - return a == nil + return a == nil if a.kind != b.kind: return false case a.kind: @@ -433,12 +439,15 @@ proc compareTypes(self: Compiler, a, b: Type): bool = let a = FunDecl(a.node) b = FunDecl(b.node) + typeOfA = self.inferType(a.returnType) + typeOfB = self.inferType(b.returnType) if a.name.token.lexeme != b.name.token.lexeme: return false elif a.arguments.len() != b.arguments.len(): return false - elif not self.compareTypes(self.inferType(a.returnType), self.inferType(b.returnType)): - return false + elif not self.compareTypes(typeOfA, typeOfB): + if typeOfA.kind != Any and typeOfB.kind != Any: + return false for (argA, argB) in zip(a.arguments, b.arguments): if argA.mutable != argB.mutable: return false @@ -722,41 +731,75 @@ 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 matchImpl(self: Compiler, name: string, kind: Type): Name = + ## Tries to find a matching function implementation + ## compatible with the given type and returns its + ## name object + + let impl = self.findByType(name, kind) + if impl.len() == 0: + var msg = &"cannot find a suitable implementation for '{name}'" + let names = self.findByName(name) + if names.len() > 0: + msg &= &", found {len(names)} candidates:" + for name in names: + echo name[] + msg &= &"- '{name.name.token.lexeme}' of type {self.typeToStr(name.valueType)}\n" + self.error(msg) + elif impl.len() > 1: + var msg = &"multiple matching implementations of '{name}' found:\n" + for fn in reversed(impl): + var node = FunDecl(fn.valueType.node) + msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n" + self.error(msg) + + +proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) = + ## Emits the code to call a unary operator + # Pushes the return address + self.emitByte(LoadUInt32) + # We patch it later! + let idx = self.chunk.consts.len() + self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad())) + self.expression(op.a) # Pushes the arguments onto the stack + self.emitByte(Call) # Creates a stack frame + self.emitBytes(fn.codePos.toTriple()) + self.emitBytes(1.toTriple()) + self.patchReturnAddress(idx) + + +proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) = + ## Emits the code to call a binary operator + # Pushes the return address + self.emitByte(LoadUInt32) + # We patch it later! + let idx = self.chunk.consts.len() + self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad())) + self.expression(op.a) # Pushes the arguments onto the stack + self.expression(op.b) + self.emitByte(Call) # Creates a stack frame + self.emitBytes(fn.codePos.toTriple()) + self.emitBytes(1.toTriple()) + self.patchReturnAddress(idx) + + proc unary(self: Compiler, node: UnaryExpr) = ## Compiles unary expressions such as decimal ## and bitwise negation let valueType = self.inferType(node.a) - let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil, args: @[valueType])) - if impl.len() == 0: - self.error(&"cannot find a suitable implementation for '{node.token.lexeme}'") - elif impl.len() > 2: - var msg = &"multiple matching implementations of '{node.token.lexeme}' found:\n" - for fn in reversed(impl): - var node = FunDecl(fn.valueType.node) - discard self.typeToStr(fn.valueType) - msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n" - self.error(msg) - else: - # Pushes the return address - self.emitByte(LoadUInt32) - # We patch it later! - let idx = self.chunk.consts.len() - self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad())) - self.expression(node.a) # Pushes the operand onto the stack - self.emitByte(Call) # Creates a stack frame - self.emitBytes(impl[0].codePos.toTriple()) - self.emitBytes(1.toTriple()) - self.patchReturnAddress(idx) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), node: nil, args: @[valueType])) + self.callUnaryOp(funct, node) proc binary(self: Compiler, node: BinaryExpr) = ## Compiles all binary expressions - # These two lines prepare the stack by pushing the - # opcode's operands onto it - self.expression(node.a) - self.expression(node.b) - # TODO: Find implementation of - # the given operator and call it + let typeOfA = self.inferType(node.a) + let typeOfB = self.inferType(node.b) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), node: nil, args: @[typeOfA, typeOfB])) + self.callBinaryOp(funct, node) + + # TODO: Get rid of old code + #[ case node.operator.kind: of NoMatch: # a and b @@ -777,10 +820,16 @@ proc binary(self: Compiler, node: BinaryExpr) = self.patchJump(jump) else: self.error(&"invalid AST node of kind {node.kind} at binary(): {node} (This is an internal error and most likely a bug!)") + ]# proc declareName(self: Compiler, node: Declaration) = - ## Statically declares a name into the current scope + ## 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() + ## correctly return them. There is no code to actually + ## declare a variable at runtime: the value is already + ## there on the stack case node.kind: of NodeKind.varDecl: var node = VarDecl(node)