Refactored the type system which no longer relies on AST node objects. Added types for ref, ptr and mutable types
This commit is contained in:
parent
a8345d065a
commit
b0515d3573
|
@ -14,8 +14,9 @@
|
||||||
|
|
||||||
## The Peon runtime environment
|
## The Peon runtime environment
|
||||||
import types
|
import types
|
||||||
import strformat
|
|
||||||
import ../config
|
import ../config
|
||||||
|
when DEBUG_TRACE_VM:
|
||||||
|
import strformat
|
||||||
import ../frontend/meta/bytecode
|
import ../frontend/meta/bytecode
|
||||||
import ../util/multibyte
|
import ../util/multibyte
|
||||||
|
|
||||||
|
|
|
@ -32,27 +32,29 @@ export multibyte
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TypeKind* = enum
|
TypeKind = enum
|
||||||
## An enumeration of compile-time
|
## An enumeration of compile-time
|
||||||
## types
|
## types
|
||||||
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,
|
||||||
|
Mutable, Reference, Pointer
|
||||||
Any # Any is used internally in a few cases,
|
Any # Any is used internally in a few cases,
|
||||||
# for example when looking for operators
|
# for example when looking for operators
|
||||||
# when only the type of the arguments is of
|
# when only the type of the arguments is of
|
||||||
# interest
|
# interest
|
||||||
Type* = ref object
|
Type = ref object
|
||||||
## A wrapper around
|
## A wrapper around
|
||||||
## compile-time types
|
## compile-time types
|
||||||
node*: ASTNode
|
case kind: TypeKind:
|
||||||
case kind*: TypeKind:
|
|
||||||
of Function:
|
of Function:
|
||||||
|
name: string
|
||||||
isLambda: bool
|
isLambda: bool
|
||||||
args*: seq[Type]
|
args: seq[tuple[name: string, kind: Type]]
|
||||||
returnType*: Type
|
returnType: Type
|
||||||
|
of Mutable, Reference, Pointer:
|
||||||
|
value: Type
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
@ -81,10 +83,13 @@ type
|
||||||
# The name's type
|
# The name's type
|
||||||
valueType: Type
|
valueType: Type
|
||||||
# For functions, this marks where the function's
|
# For functions, this marks where the function's
|
||||||
# code begins. For variables, this stores their
|
# code begins. For variables, this stores where
|
||||||
# position in the stack (used for closures)
|
# their StoreVar/StoreHeap instruction was emitted
|
||||||
codePos: int
|
codePos: int
|
||||||
|
# Is the name closed over (i.e. used in a closure)?
|
||||||
isClosedOver: bool
|
isClosedOver: bool
|
||||||
|
# Where is this node declared in the file?
|
||||||
|
line: int
|
||||||
Loop = object
|
Loop = object
|
||||||
## A "loop object" used
|
## A "loop object" used
|
||||||
## by the compiler to emit
|
## by the compiler to emit
|
||||||
|
@ -124,7 +129,7 @@ type
|
||||||
# The current function being compiled
|
# The current function being compiled
|
||||||
currentFunction: FunDecl
|
currentFunction: FunDecl
|
||||||
# Are optimizations turned on?
|
# Are optimizations turned on?
|
||||||
enableOptimizations*: bool
|
enableOptimizations: bool
|
||||||
# The current loop being compiled (used to
|
# The current loop being compiled (used to
|
||||||
# keep track of where to jump)
|
# keep track of where to jump)
|
||||||
currentLoop: Loop
|
currentLoop: Loop
|
||||||
|
@ -254,11 +259,15 @@ proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
|
||||||
|
|
||||||
|
|
||||||
proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
||||||
## Emits a LoadConstant instruction along
|
## Emits a constant instruction along
|
||||||
## with its operand
|
## with its operand
|
||||||
case self.inferType(obj).kind:
|
case self.inferType(obj).kind:
|
||||||
of Int64:
|
of Int64:
|
||||||
self.emitByte(LoadInt64)
|
self.emitByte(LoadInt64)
|
||||||
|
of UInt64:
|
||||||
|
self.emitByte(LoadUInt64)
|
||||||
|
of Int32:
|
||||||
|
self.emitByte(LoadInt32)
|
||||||
else:
|
else:
|
||||||
discard # TODO
|
discard # TODO
|
||||||
self.emitBytes(self.makeConstant(obj, kind))
|
self.emitBytes(self.makeConstant(obj, kind))
|
||||||
|
@ -267,11 +276,11 @@ proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
||||||
proc emitJump(self: Compiler, opcode: OpCode): int =
|
proc emitJump(self: Compiler, opcode: OpCode): int =
|
||||||
## Emits a dummy jump offset to be patched later. Assumes
|
## Emits a dummy jump offset to be patched later. Assumes
|
||||||
## the largest offset (emits 4 bytes, one for the given jump
|
## the largest offset (emits 4 bytes, one for the given jump
|
||||||
## opcode, while the other 3 are for the jump offset which is set
|
## opcode, while the other 3 are for the jump offset, which
|
||||||
## to the maximum unsigned 24 bit integer). If the shorter
|
## is set to the maximum unsigned 24 bit integer). If the shorter
|
||||||
## 16 bit alternative is later found to be better suited, patchJump
|
## 16 bit alternative is later found to be better suited, patchJump
|
||||||
## will fix this. This function returns the absolute index into the
|
## will fix this. Returns the absolute index into the chunk's
|
||||||
## chunk's bytecode array where the given placeholder instruction was written
|
## bytecode array where the given placeholder instruction was written
|
||||||
self.emitByte(opcode)
|
self.emitByte(opcode)
|
||||||
self.emitBytes((0xffffff).toTriple())
|
self.emitBytes((0xffffff).toTriple())
|
||||||
result = self.chunk.code.len() - 4
|
result = self.chunk.code.len() - 4
|
||||||
|
@ -281,9 +290,10 @@ proc patchJump(self: Compiler, offset: int) =
|
||||||
## Patches a previously emitted relative
|
## Patches a previously emitted relative
|
||||||
## jump using emitJump. Since emitJump assumes
|
## jump using emitJump. Since emitJump assumes
|
||||||
## a long jump, this also shrinks the jump
|
## a long jump, this also shrinks the jump
|
||||||
## offset and changes the bytecode instruction if possible
|
## offset and changes the bytecode instruction
|
||||||
## (i.e. jump is in 16 bit range), but the converse is also
|
## if possible (i.e. jump is in 16 bit range),
|
||||||
## true (i.e. it might change a regular jump into a long one)
|
## but the converse is also true (i.e. it might
|
||||||
|
## change a regular jump into a long one)
|
||||||
var jump: int = self.chunk.code.len() - offset
|
var jump: int = self.chunk.code.len() - offset
|
||||||
if jump > 16777215:
|
if jump > 16777215:
|
||||||
self.error("cannot jump more than 16777216 bytecode instructions")
|
self.error("cannot jump more than 16777216 bytecode instructions")
|
||||||
|
@ -291,6 +301,10 @@ proc patchJump(self: Compiler, offset: int) =
|
||||||
case OpCode(self.chunk.code[offset]):
|
case OpCode(self.chunk.code[offset]):
|
||||||
of LongJumpForwards:
|
of LongJumpForwards:
|
||||||
self.chunk.code[offset] = JumpForwards.uint8()
|
self.chunk.code[offset] = JumpForwards.uint8()
|
||||||
|
# We do this because a relative jump
|
||||||
|
# does not take its argument into account
|
||||||
|
# because it is hardcoded in the bytecode
|
||||||
|
# itself
|
||||||
jump -= 4
|
jump -= 4
|
||||||
of LongJumpBackwards:
|
of LongJumpBackwards:
|
||||||
self.chunk.code[offset] = JumpBackwards.uint8()
|
self.chunk.code[offset] = JumpBackwards.uint8()
|
||||||
|
@ -302,14 +316,10 @@ proc patchJump(self: Compiler, offset: int) =
|
||||||
of LongJumpIfFalseOrPop:
|
of LongJumpIfFalseOrPop:
|
||||||
self.chunk.code[offset] = JumpIfFalseOrPop.uint8()
|
self.chunk.code[offset] = JumpIfFalseOrPop.uint8()
|
||||||
of JumpForwards, JumpBackwards:
|
of JumpForwards, JumpBackwards:
|
||||||
# We do this because a relative jump
|
|
||||||
# does not normally take into account
|
|
||||||
# its argument, which is hardcoded in
|
|
||||||
# the bytecode itself
|
|
||||||
jump -= 3
|
jump -= 3
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
|
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
|
||||||
let offsetArray = (jump - 1).toDouble() # -1 since we got rid of 1 byte!
|
let offsetArray = (jump - 1).toDouble() # -1 since we got rid of 1 byte!
|
||||||
self.chunk.code[offset + 1] = offsetArray[0]
|
self.chunk.code[offset + 1] = offsetArray[0]
|
||||||
self.chunk.code[offset + 2] = offsetArray[1]
|
self.chunk.code[offset + 2] = offsetArray[1]
|
||||||
|
@ -328,10 +338,6 @@ proc patchJump(self: Compiler, offset: int) =
|
||||||
of JumpIfFalseOrPop:
|
of JumpIfFalseOrPop:
|
||||||
self.chunk.code[offset] = LongJumpIfFalseOrPop.uint8()
|
self.chunk.code[offset] = LongJumpIfFalseOrPop.uint8()
|
||||||
of LongJumpForwards, LongJumpBackwards:
|
of LongJumpForwards, LongJumpBackwards:
|
||||||
# We do this because a relative jump
|
|
||||||
# does not normally take into account
|
|
||||||
# its argument, which is hardcoded in
|
|
||||||
# the bytecode itself
|
|
||||||
jump -= 4
|
jump -= 4
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
@ -414,133 +420,48 @@ proc detectClosureVariable(self: Compiler, name: Name,
|
||||||
name.isClosedOver = true
|
name.isClosedOver = true
|
||||||
|
|
||||||
|
|
||||||
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
|
proc compareTypes(self: Compiler, a, b: Type): bool =
|
||||||
## Compares two types without using information from
|
## Compares two type objects
|
||||||
## AST nodes
|
## for equality (works with nil!)
|
||||||
|
|
||||||
|
# The nil code here is for void functions (when
|
||||||
|
# we compare their return types)
|
||||||
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:
|
elif a.kind != b.kind:
|
||||||
|
# Next, we see the type discriminant:
|
||||||
|
# If they're different, then they can't
|
||||||
|
# be the same type!
|
||||||
return false
|
return false
|
||||||
case a.kind:
|
case a.kind:
|
||||||
|
# If all previous checks pass, it's time
|
||||||
|
# to go through each possible type peon
|
||||||
|
# supports and compare it
|
||||||
|
of Int8, UInt8, Int16, UInt16, Int32,
|
||||||
|
UInt32, Int64, UInt64, Float32, Float64,
|
||||||
|
Char, Byte, String, Nil, Nan, Bool, Inf:
|
||||||
|
# A value type's type is always equal to
|
||||||
|
# another one's
|
||||||
|
return true
|
||||||
|
of Reference, Pointer, Mutable:
|
||||||
|
# Here we already know that both
|
||||||
|
# a and b are of either of the three
|
||||||
|
# types in this branch, so we just need
|
||||||
|
# to compare their values
|
||||||
|
return self.compareTypes(a.value, b.value)
|
||||||
of Function:
|
of Function:
|
||||||
|
# Functions are a bit trickier
|
||||||
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):
|
||||||
if a.returnType.kind != Any and b.returnType.kind != Any:
|
if a.returnType.kind != Any and b.returnType.kind != Any:
|
||||||
return false
|
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.kind, argB.kind):
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
else:
|
|
||||||
discard
|
|
||||||
|
|
||||||
|
|
||||||
proc compareTypes(self: Compiler, a, b: Type): bool =
|
|
||||||
## Compares two type objects
|
|
||||||
## for equality (works with nil!)
|
|
||||||
if a == nil:
|
|
||||||
return b == nil
|
|
||||||
elif b == nil:
|
|
||||||
return a == nil
|
|
||||||
if a.kind != b.kind:
|
|
||||||
return false
|
|
||||||
case a.kind:
|
|
||||||
of Int8, UInt8, Int16, UInt16, Int32,
|
|
||||||
UInt32, Int64, UInt64, Float32, Float64,
|
|
||||||
Char, Byte, String, Nil, Nan, Bool, Inf:
|
|
||||||
return true
|
|
||||||
of Function:
|
|
||||||
if a.node == nil or b.node == nil:
|
|
||||||
return self.compareTypesWithNullNode(a, b)
|
|
||||||
if not a.isLambda and not b.isLambda:
|
|
||||||
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(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
|
|
||||||
elif argA.isRef != argB.isRef:
|
|
||||||
return false
|
|
||||||
elif argA.isPtr != argB.isPtr:
|
|
||||||
return false
|
|
||||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
elif a.isLambda and not b.isLambda:
|
|
||||||
let
|
|
||||||
a = LambdaExpr(a.node)
|
|
||||||
b = FunDecl(b.node)
|
|
||||||
typeOfA = self.inferType(a.returnType)
|
|
||||||
typeOfB = self.inferType(b.returnType)
|
|
||||||
if a.arguments.len() != b.arguments.len():
|
|
||||||
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
|
|
||||||
elif argA.isRef != argB.isRef:
|
|
||||||
return false
|
|
||||||
elif argA.isPtr != argB.isPtr:
|
|
||||||
return false
|
|
||||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
elif b.isLambda and not a.isLambda:
|
|
||||||
let
|
|
||||||
a = FunDecl(a.node)
|
|
||||||
b = LambdaExpr(b.node)
|
|
||||||
typeOfA = self.inferType(a.returnType)
|
|
||||||
typeOfB = self.inferType(b.returnType)
|
|
||||||
if a.arguments.len() != b.arguments.len():
|
|
||||||
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
|
|
||||||
elif argA.isRef != argB.isRef:
|
|
||||||
return false
|
|
||||||
elif argA.isPtr != argB.isPtr:
|
|
||||||
return false
|
|
||||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
let
|
|
||||||
a = LambdaExpr(a.node)
|
|
||||||
b = LambdaExpr(b.node)
|
|
||||||
typeOfA = self.inferType(a.returnType)
|
|
||||||
typeOfB = self.inferType(b.returnType)
|
|
||||||
if a.arguments.len() != b.arguments.len():
|
|
||||||
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
|
|
||||||
elif argA.isRef != argB.isRef:
|
|
||||||
return false
|
|
||||||
elif argA.isPtr != argB.isPtr:
|
|
||||||
return false
|
|
||||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
@ -587,47 +508,6 @@ proc toIntrinsic(name: string): Type =
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|
||||||
proc inferType(self: Compiler, node: LiteralExpr): Type =
|
|
||||||
## Infers the type of a given literal expression
|
|
||||||
if node == nil:
|
|
||||||
return nil
|
|
||||||
case node.kind:
|
|
||||||
of intExpr, binExpr, octExpr, hexExpr:
|
|
||||||
let size = node.token.lexeme.split("'")
|
|
||||||
if len(size) notin 1..2:
|
|
||||||
self.error("invalid state: inferValueType -> invalid size specifier (This is an internal error and most likely a bug!)")
|
|
||||||
if size.len() == 1:
|
|
||||||
return Type(node: node, kind: Int64)
|
|
||||||
let typ = size[1].toIntrinsic()
|
|
||||||
if not self.compareTypes(typ, nil):
|
|
||||||
return typ
|
|
||||||
else:
|
|
||||||
self.error(&"invalid type specifier '{size[1]}' for int")
|
|
||||||
of floatExpr:
|
|
||||||
let size = node.token.lexeme.split("'")
|
|
||||||
if len(size) notin 1..2:
|
|
||||||
self.error("invalid state: inferValueType -> invalid size specifier (This is an internal error and most likely a bug!)")
|
|
||||||
if size.len() == 1 or size[1] == "f64":
|
|
||||||
return Type(node: node, kind: Float64)
|
|
||||||
let typ = size[1].toIntrinsic()
|
|
||||||
if not self.compareTypes(typ, nil):
|
|
||||||
return typ
|
|
||||||
else:
|
|
||||||
self.error(&"invalid type specifier '{size[1]}' for float")
|
|
||||||
of nilExpr:
|
|
||||||
return Type(node: node, kind: Nil)
|
|
||||||
of trueExpr:
|
|
||||||
return Type(node: node, kind: Bool)
|
|
||||||
of falseExpr:
|
|
||||||
return Type(node: node, kind: Bool)
|
|
||||||
of nanExpr:
|
|
||||||
return Type(node: node, kind: TypeKind.Nan)
|
|
||||||
of infExpr:
|
|
||||||
return Type(node: node, kind: TypeKind.Inf)
|
|
||||||
else:
|
|
||||||
discard # TODO
|
|
||||||
|
|
||||||
|
|
||||||
proc toIntrinsic(self: Compiler, typ: Expression): Type =
|
proc toIntrinsic(self: Compiler, typ: Expression): Type =
|
||||||
## Gets an expression's intrinsic type, if
|
## Gets an expression's intrinsic type, if
|
||||||
## possible
|
## possible
|
||||||
|
@ -645,6 +525,47 @@ proc toIntrinsic(self: Compiler, typ: Expression): Type =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
proc inferType(self: Compiler, node: LiteralExpr): Type =
|
||||||
|
## Infers the type of a given literal expression
|
||||||
|
if node == nil:
|
||||||
|
return nil
|
||||||
|
case node.kind:
|
||||||
|
of intExpr, binExpr, octExpr, hexExpr:
|
||||||
|
let size = node.token.lexeme.split("'")
|
||||||
|
if len(size) notin 1..2:
|
||||||
|
self.error("invalid state: inferValueType -> invalid size specifier (This is an internal error and most likely a bug!)")
|
||||||
|
if size.len() == 1:
|
||||||
|
return Type(kind: Int64)
|
||||||
|
let typ = size[1].toIntrinsic()
|
||||||
|
if not self.compareTypes(typ, nil):
|
||||||
|
return typ
|
||||||
|
else:
|
||||||
|
self.error(&"invalid type specifier '{size[1]}' for int")
|
||||||
|
of floatExpr:
|
||||||
|
let size = node.token.lexeme.split("'")
|
||||||
|
if len(size) notin 1..2:
|
||||||
|
self.error("invalid state: inferValueType -> invalid size specifier (This is an internal error and most likely a bug!)")
|
||||||
|
if size.len() == 1 or size[1] == "f64":
|
||||||
|
return Type(kind: Float64)
|
||||||
|
let typ = size[1].toIntrinsic()
|
||||||
|
if not self.compareTypes(typ, nil):
|
||||||
|
return typ
|
||||||
|
else:
|
||||||
|
self.error(&"invalid type specifier '{size[1]}' for float")
|
||||||
|
of nilExpr:
|
||||||
|
return Type(kind: Nil)
|
||||||
|
of trueExpr:
|
||||||
|
return Type(kind: Bool)
|
||||||
|
of falseExpr:
|
||||||
|
return Type(kind: Bool)
|
||||||
|
of nanExpr:
|
||||||
|
return Type(kind: TypeKind.Nan)
|
||||||
|
of infExpr:
|
||||||
|
return Type(kind: TypeKind.Inf)
|
||||||
|
else:
|
||||||
|
discard # TODO
|
||||||
|
|
||||||
|
|
||||||
proc inferType(self: Compiler, node: Expression): Type =
|
proc inferType(self: Compiler, node: Expression): Type =
|
||||||
## Infers the type of a given expression and
|
## Infers the type of a given expression and
|
||||||
## returns it
|
## returns it
|
||||||
|
@ -658,8 +579,6 @@ proc inferType(self: Compiler, node: Expression): Type =
|
||||||
return name.valueType
|
return name.valueType
|
||||||
else:
|
else:
|
||||||
result = node.name.lexeme.toIntrinsic()
|
result = node.name.lexeme.toIntrinsic()
|
||||||
if result != nil:
|
|
||||||
result.node = node
|
|
||||||
of unaryExpr:
|
of unaryExpr:
|
||||||
return self.inferType(UnaryExpr(node).a)
|
return self.inferType(UnaryExpr(node).a)
|
||||||
of binaryExpr:
|
of binaryExpr:
|
||||||
|
@ -676,49 +595,15 @@ proc inferType(self: Compiler, node: Expression): Type =
|
||||||
return self.inferType(LiteralExpr(node))
|
return self.inferType(LiteralExpr(node))
|
||||||
of lambdaExpr:
|
of lambdaExpr:
|
||||||
var node = LambdaExpr(node)
|
var node = LambdaExpr(node)
|
||||||
result = Type(kind: Function, returnType: nil, node: node, args: @[], isLambda: true)
|
result = Type(kind: Function, returnType: nil, args: @[], isLambda: true)
|
||||||
if node.returnType != nil:
|
if node.returnType != nil:
|
||||||
result.returnType = self.inferType(node.returnType)
|
result.returnType = self.inferType(node.returnType)
|
||||||
for argument in node.arguments:
|
for argument in node.arguments:
|
||||||
result.args.add(self.inferType(argument.valueType))
|
result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType)))
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
|
|
||||||
|
|
||||||
proc typeToStr(self: Compiler, typ: Type): string =
|
|
||||||
## Returns the string representation of a
|
|
||||||
## type object
|
|
||||||
case typ.kind:
|
|
||||||
of Int8, UInt8, Int16, UInt16, Int32,
|
|
||||||
UInt32, Int64, UInt64, Float32, Float64,
|
|
||||||
Char, Byte, String, Nil, TypeKind.Nan, Bool,
|
|
||||||
TypeKind.Inf:
|
|
||||||
return ($typ.kind).toLowerAscii()
|
|
||||||
of Function:
|
|
||||||
result = "function ("
|
|
||||||
case typ.node.kind:
|
|
||||||
of funDecl:
|
|
||||||
var node = FunDecl(typ.node)
|
|
||||||
for i, argument in node.arguments:
|
|
||||||
result &= &"{argument.name.token.lexeme}: {self.typeToStr(self.inferType(argument.valueType))}"
|
|
||||||
if i < node.arguments.len() - 1:
|
|
||||||
result &= ", "
|
|
||||||
result &= ")"
|
|
||||||
of lambdaExpr:
|
|
||||||
var node = LambdaExpr(typ.node)
|
|
||||||
for i, argument in node.arguments:
|
|
||||||
result &= &"{argument.name.token.lexeme}: {self.typeToStr(self.inferType(argument.name))}"
|
|
||||||
if i < node.arguments.len() - 1:
|
|
||||||
result &= ", "
|
|
||||||
result &= ")"
|
|
||||||
else:
|
|
||||||
discard # Unreachable
|
|
||||||
if typ.returnType != nil:
|
|
||||||
result &= &": {self.typeToStr(typ.returnType)}"
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
|
|
||||||
|
|
||||||
proc inferType(self: Compiler, node: Declaration): Type =
|
proc inferType(self: Compiler, node: Declaration): Type =
|
||||||
## Infers the type of a given declaration
|
## Infers the type of a given declaration
|
||||||
## and returns it
|
## and returns it
|
||||||
|
@ -740,6 +625,35 @@ proc inferType(self: Compiler, node: Declaration): Type =
|
||||||
else:
|
else:
|
||||||
return # Unreachable
|
return # Unreachable
|
||||||
|
|
||||||
|
|
||||||
|
proc typeToStr(self: Compiler, typ: Type): string =
|
||||||
|
## Returns the string representation of a
|
||||||
|
## type object
|
||||||
|
case typ.kind:
|
||||||
|
of Int8, UInt8, Int16, UInt16, Int32,
|
||||||
|
UInt32, Int64, UInt64, Float32, Float64,
|
||||||
|
Char, Byte, String, Nil, TypeKind.Nan, Bool,
|
||||||
|
TypeKind.Inf:
|
||||||
|
return ($typ.kind).toLowerAscii()
|
||||||
|
of Pointer:
|
||||||
|
return &"ptr {self.typeToStr(typ.value)}"
|
||||||
|
of Reference:
|
||||||
|
return &"ref {self.typeToStr(typ.value)}"
|
||||||
|
of Mutable:
|
||||||
|
return &"var {self.typeToStr(typ.value)}"
|
||||||
|
of Function:
|
||||||
|
result = "fn ("
|
||||||
|
for i, (argName, argType) in typ.args:
|
||||||
|
result &= &"{argName}: {self.typeToStr(argType)}"
|
||||||
|
if i < typ.args.len() - 1:
|
||||||
|
result &= ", "
|
||||||
|
result &= ")"
|
||||||
|
if typ.returnType != nil:
|
||||||
|
result &= &": {self.typeToStr(typ.returnType)}"
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
## End of utility functions
|
## End of utility functions
|
||||||
|
|
||||||
|
|
||||||
|
@ -845,14 +759,13 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name =
|
||||||
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
||||||
else:
|
else:
|
||||||
for i, arg in kind.args:
|
for i, arg in kind.args:
|
||||||
if not self.compareTypes(arg, name.valueType.args[i]):
|
if not self.compareTypes(arg.kind, name.valueType.args[i].kind):
|
||||||
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i])}', got '{self.typeToStr(arg)}' instead"
|
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead"
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
elif impl.len() > 1:
|
elif impl.len() > 1:
|
||||||
var msg = &"multiple matching implementations of '{name}' found:\n"
|
var msg = &"multiple matching implementations of '{name}' found:\n"
|
||||||
for fn in reversed(impl):
|
for fn in reversed(impl):
|
||||||
var node = FunDecl(fn.valueType.node)
|
msg &= &"- '{fn.name}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||||
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
|
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
return impl[0]
|
return impl[0]
|
||||||
|
|
||||||
|
@ -890,7 +803,7 @@ 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 funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), node: nil, args: @[valueType]))
|
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]))
|
||||||
self.callUnaryOp(funct, node)
|
self.callUnaryOp(funct, node)
|
||||||
|
|
||||||
|
|
||||||
|
@ -898,7 +811,7 @@ proc binary(self: Compiler, node: BinaryExpr) =
|
||||||
## Compiles all binary expressions
|
## Compiles all binary expressions
|
||||||
let typeOfA = self.inferType(node.a)
|
let typeOfA = self.inferType(node.a)
|
||||||
let typeOfB = self.inferType(node.b)
|
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]))
|
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]))
|
||||||
self.callBinaryOp(funct, node)
|
self.callBinaryOp(funct, node)
|
||||||
|
|
||||||
# TODO: Get rid of old code
|
# TODO: Get rid of old code
|
||||||
|
@ -942,67 +855,74 @@ proc declareName(self: Compiler, node: Declaration) =
|
||||||
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
|
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
|
||||||
self.error("cannot declare more than 16777216 variables at a time")
|
self.error("cannot declare more than 16777216 variables at a time")
|
||||||
for name in self.findByName(node.name.token.lexeme):
|
for name in self.findByName(node.name.token.lexeme):
|
||||||
if name.name.token.lexeme == node.name.token.lexeme and name.depth == self.scopeDepth and name.valueType.node.kind == varDecl:
|
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, CustomType}:
|
||||||
self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.valueType.node.token.line}")
|
# Trying to redeclare a variable in the same module is an error!
|
||||||
|
self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}")
|
||||||
self.names.add(Name(depth: self.scopeDepth,
|
self.names.add(Name(depth: self.scopeDepth,
|
||||||
name: node.name,
|
name: node.name,
|
||||||
isPrivate: node.isPrivate,
|
isPrivate: node.isPrivate,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
isConst: node.isConst,
|
isConst: node.isConst,
|
||||||
valueType: Type(kind: self.inferType(node.value).kind, node: node),
|
valueType: Type(kind: self.inferType(node.value).kind),
|
||||||
codePos: self.chunk.code.len(),
|
codePos: self.chunk.code.len(),
|
||||||
isLet: node.isLet,
|
isLet: node.isLet,
|
||||||
isClosedOver: false))
|
isClosedOver: false,
|
||||||
|
line: node.token.line))
|
||||||
# We emit 4 No-Ops because they may become a
|
# We emit 4 No-Ops because they may become a
|
||||||
# StoreHeap instruction. If not, they'll be
|
# StoreHeap instruction. If not, they'll be
|
||||||
# removed before the compiler is finished
|
# removed before the compiler is finished
|
||||||
|
# TODO: This may break CFI offsets
|
||||||
self.emitBytes([NoOp, NoOp, NoOp, NoOp])
|
self.emitBytes([NoOp, NoOp, NoOp, NoOp])
|
||||||
of NodeKind.funDecl:
|
of NodeKind.funDecl:
|
||||||
var node = FunDecl(node)
|
var node = FunDecl(node)
|
||||||
# TODO: Emit some optional debugging
|
|
||||||
# metadata to let the VM know where a function's
|
|
||||||
# code begins and ends (similar to what gcc does with
|
|
||||||
# CFI in object files) to build stack traces
|
|
||||||
self.names.add(Name(depth: self.scopeDepth,
|
self.names.add(Name(depth: self.scopeDepth,
|
||||||
isPrivate: node.isPrivate,
|
isPrivate: node.isPrivate,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
valueType: Type(kind: Function, node: node,
|
valueType: Type(kind: Function,
|
||||||
returnType: self.inferType(
|
returnType: self.inferType(
|
||||||
node.returnType),
|
node.returnType),
|
||||||
args: @[]),
|
args: @[]),
|
||||||
codePos: self.chunk.code.high(),
|
codePos: self.chunk.code.high(),
|
||||||
name: node.name,
|
name: node.name,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
isClosedOver: false))
|
isClosedOver: false,
|
||||||
|
line: node.token.line))
|
||||||
let fn = self.names[^1]
|
let fn = self.names[^1]
|
||||||
|
var name: Name
|
||||||
for argument in node.arguments:
|
for argument in node.arguments:
|
||||||
if self.names.high() > 16777215:
|
if self.names.high() > 16777215:
|
||||||
self.error("cannot declare more than 16777216 variables at a time")
|
self.error("cannot declare more than 16777216 variables at a time")
|
||||||
# wait, no LoadVar?? Yes! That's because when calling functions,
|
# wait, no LoadVar?? Yes! That's because when calling functions,
|
||||||
# arguments will already be on the stack so there's no need to
|
# arguments will already be on the stack so there's no need to
|
||||||
# load them here
|
# load them here
|
||||||
self.names.add(Name(depth: self.scopeDepth + 1,
|
name = Name(depth: self.scopeDepth + 1,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
name: argument.name,
|
name: argument.name,
|
||||||
valueType: nil,
|
valueType: nil,
|
||||||
codePos: self.chunk.code.len(),
|
codePos: self.chunk.code.len(),
|
||||||
isLet: false,
|
isLet: false,
|
||||||
isClosedOver: false))
|
isClosedOver: false)
|
||||||
self.names[^1].valueType = self.inferType(argument.valueType)
|
self.names.add(name)
|
||||||
|
name.valueType = self.inferType(argument.valueType)
|
||||||
|
if argument.mutable:
|
||||||
|
name.valueType = Type(kind: Mutable, value: name.valueType)
|
||||||
|
elif argument.isRef:
|
||||||
|
name.valueType = Type(kind: Reference, value: name.valueType)
|
||||||
|
elif argument.isPtr:
|
||||||
|
name.valueType = Type(kind: Pointer, value: name.valueType)
|
||||||
# We check if the argument's type is a generic
|
# We check if the argument's type is a generic
|
||||||
if self.names[^1].valueType == nil and argument.valueType.kind == identExpr:
|
if name.valueType == nil and argument.valueType.kind == identExpr:
|
||||||
for gen in node.generics:
|
for gen in node.generics:
|
||||||
if gen.name == IdentExpr(argument.valueType):
|
if gen.name == IdentExpr(argument.valueType):
|
||||||
self.names[^1].valueType = Type(kind: Generic)
|
name.valueType = Type(kind: Generic)
|
||||||
break
|
break
|
||||||
# If it's still nil, it's an error!
|
# If it's still nil, it's an error!
|
||||||
if self.names[^1].valueType == nil:
|
if name.valueType == nil:
|
||||||
self.error(&"cannot determine the type of argument '{self.names[^1].name.token.lexeme}'")
|
self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'")
|
||||||
self.names[^1].valueType.node = argument.name
|
fn.valueType.args.add((argument.name.token.lexeme, name.valueType))
|
||||||
fn.valueType.args.add(self.names[^1].valueType)
|
|
||||||
else:
|
else:
|
||||||
discard # TODO: Types, enums
|
discard # TODO: Types, enums
|
||||||
|
|
||||||
|
@ -1418,8 +1338,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
# the same function with the same name! Error!
|
# the same function with the same name! Error!
|
||||||
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
||||||
for fn in reversed(impl):
|
for fn in reversed(impl):
|
||||||
var node = FunDecl(fn.valueType.node)
|
msg &= &"- '{fn.name}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||||
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
|
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
# We store the current function
|
# We store the current function
|
||||||
self.currentFunction = node
|
self.currentFunction = node
|
||||||
|
|
Loading…
Reference in New Issue