Updated config.nim and changed mechanism for finding operators (also added binary operator lookup)

This commit is contained in:
Mattia Giambirtone 2022-05-24 09:55:08 +02:00
parent b02e2c3d02
commit 15f412bcac
2 changed files with 85 additions and 36 deletions

View File

@ -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 const HEAP_GROW_FACTOR* = 2 # How much extra memory to allocate for dynamic arrays and garbage collection when resizing
when HEAP_GROW_FACTOR <= 1: when HEAP_GROW_FACTOR <= 1:
{.fatal: "Heap growth factor must be > 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_RELEASE* = "alpha"
const PEON_COMMIT_HASH* = "ed79385e2a93100331697f26a4a90157e60ad27a" const PEON_COMMIT_HASH* = "231b7012a17e98c7bcb2b475289cfcdddaf4a7d8"
when len(PEON_COMMIT_HASH) != 40: when len(PEON_COMMIT_HASH) != 40:
{.fatal: "The git commit hash must be exactly 40 characters long".} {.fatal: "The git commit hash must be exactly 40 characters long".}
const PEON_BRANCH* = "master" const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255: when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".} {.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_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler const DEBUG_TRACE_COMPILER* = false # Traces the compiler

View File

@ -38,7 +38,12 @@ type
Int8, UInt8, Int16, UInt16, Int32, Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64, UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Function, CustomType, 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 Type* = ref object
## A wrapper around ## A wrapper around
## compile-time types ## compile-time types
@ -404,7 +409,8 @@ proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
if a.args.len() != b.args.len(): if a.args.len() != b.args.len():
return false return false
elif not self.compareTypes(a.returnType, b.returnType): 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): for (argA, argB) in zip(a.args, b.args):
if not self.compareTypes(argA, argB): if not self.compareTypes(argA, argB):
return false return false
@ -419,7 +425,7 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
if a == nil: if a == nil:
return b == nil return b == nil
elif b == nil: elif b == nil:
return a == nil return a == nil
if a.kind != b.kind: if a.kind != b.kind:
return false return false
case a.kind: case a.kind:
@ -433,12 +439,15 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
let let
a = FunDecl(a.node) a = FunDecl(a.node)
b = FunDecl(b.node) b = FunDecl(b.node)
typeOfA = self.inferType(a.returnType)
typeOfB = self.inferType(b.returnType)
if a.name.token.lexeme != b.name.token.lexeme: if a.name.token.lexeme != b.name.token.lexeme:
return false return false
elif a.arguments.len() != b.arguments.len(): elif a.arguments.len() != b.arguments.len():
return false return false
elif not self.compareTypes(self.inferType(a.returnType), self.inferType(b.returnType)): elif not self.compareTypes(typeOfA, typeOfB):
return false if typeOfA.kind != Any and typeOfB.kind != Any:
return false
for (argA, argB) in zip(a.arguments, b.arguments): for (argA, argB) in zip(a.arguments, b.arguments):
if argA.mutable != argB.mutable: if argA.mutable != argB.mutable:
return false 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!)") 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) = proc unary(self: Compiler, node: UnaryExpr) =
## Compiles unary expressions such as decimal ## Compiles unary expressions such as decimal
## and bitwise negation ## and bitwise negation
let valueType = self.inferType(node.a) let valueType = self.inferType(node.a)
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil, args: @[valueType])) let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), node: nil, args: @[valueType]))
if impl.len() == 0: self.callUnaryOp(funct, node)
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)
proc binary(self: Compiler, node: BinaryExpr) = proc binary(self: Compiler, node: BinaryExpr) =
## Compiles all binary expressions ## Compiles all binary expressions
# These two lines prepare the stack by pushing the let typeOfA = self.inferType(node.a)
# opcode's operands onto it let typeOfB = self.inferType(node.b)
self.expression(node.a) let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), node: nil, args: @[typeOfA, typeOfB]))
self.expression(node.b) self.callBinaryOp(funct, node)
# TODO: Find implementation of
# the given operator and call it # TODO: Get rid of old code
#[
case node.operator.kind: case node.operator.kind:
of NoMatch: of NoMatch:
# a and b # a and b
@ -777,10 +820,16 @@ proc binary(self: Compiler, node: BinaryExpr) =
self.patchJump(jump) self.patchJump(jump)
else: else:
self.error(&"invalid AST node of kind {node.kind} at binary(): {node} (This is an internal error and most likely a bug!)") 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) = 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: case node.kind:
of NodeKind.varDecl: of NodeKind.varDecl:
var node = VarDecl(node) var node = VarDecl(node)