preparation for type system overhaul (please send help)

This commit is contained in:
Mattia Giambirtone 2023-11-22 21:16:20 +01:00
parent 83051d67f8
commit 838fc3d5a1
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
7 changed files with 316 additions and 426 deletions

View File

@ -52,7 +52,7 @@ proc newBytecodeGenerator*: BytecodeGenerator =
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr)
proc error(self: BytecodeGenerator, msg: string, typedNode: TypedNode = nil) {.raises: [CodeGenError].} =
proc error(self: BytecodeGenerator, msg: string, typedNode: TypedNode = nil) =
## Raises a generic peon exception
var typedNode = typedNode
var file = self.currentFile
@ -208,7 +208,11 @@ proc patchJump(self: BytecodeGenerator, offset: int) =
proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: seq[TypedExpr], line: int) =
## Emits instructions for builtin functions
## such as addition or subtraction
if fn.decl.name.valueType.builtinOp notin ["LogicalOr", "LogicalAnd"]:
var builtinOp: string
for pragma in FunDecl(fn.decl.node).pragmas:
if pragma.name.token.lexeme == "magic":
builtinOp = pragma.args[0].token.lexeme
if builtinOp notin ["LogicalOr", "LogicalAnd"]:
if len(args) == 2:
self.generateExpression(args[1])
self.generateExpression(args[0])
@ -268,7 +272,7 @@ proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: s
"NegInf": LoadNInf,
"Identity": Identity
}.toTable()
if fn.decl.name.valueType.builtinOp == "print":
if builtinOp == "print":
let typ = args[0].kind
case typ.kind:
of Integer:
@ -316,19 +320,19 @@ proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: s
if typ.isLambda:
str = &"anonymous function at 0x{loc}"
else:
str = &"function '{FunDecl(typ.fun).name.token.lexeme}' at 0x{loc}"
str = &"function '{FunDecl(fn.decl.node).name.token.lexeme}' at 0x{loc}"
self.emitBytes(str.len().toTriple(), line)
self.emitBytes(self.chunk.writeConstant(str.toBytes()), line)
self.emitByte(PrintString, line)
else:
self.error(&"invalid type {self.typechecker.stringify(typ)} for built-in 'print'", args[0])
return
if fn.decl.name.valueType.builtinOp in codes:
self.emitByte(codes[fn.decl.name.valueType.builtinOp], line)
if builtinOp in codes:
self.emitByte(codes[builtinOp], line)
return
# Some builtin operations are slightly more complex
# so we handle them separately
case fn.decl.name.valueType.builtinOp:
case builtinOp:
of "LogicalOr":
self.generateExpression(args[0])
let jump = self.emitJump(JumpIfTrue, line)
@ -347,7 +351,7 @@ proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: s
# make it so that the peon stub can have no body
discard
else:
self.error(&"unknown built-in: '{fn.decl.name.valueType.builtinOp}'")
self.error(&"unknown built-in: '{builtinOp}'")
proc patchReturnAddress(self: BytecodeGenerator, pos: int) =
@ -364,25 +368,46 @@ proc patchReturnAddress(self: BytecodeGenerator, pos: int) =
self.chunk.consts[pos + 7] = address[7]
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr) =
## Emits code for expressions
proc generateLiteral(self: BytecodeGenerator, literal: TypedExpr) =
## Emits code for literals
let
typ = expression.kind
node = expression.node
typ = literal.kind
node = literal.node
case typ.kind:
of Integer, Float:
# No need to do any input validation here: the typechecker
# has graciously done all the work for us! :)
self.emitConstant(expression)
self.emitConstant(literal)
of Infinity:
if typ.positive:
self.emitByte(LoadInf, node.token.line)
else:
self.emitByte(LoadNInf, node.token.line)
of NaN:
self.emitByte(LoadNaN, node.token.line)
self.emitByte(LoadNaN, node.token.line)
else:
discard # TODO
self.error(&"Unknown typed node of type {node.kind} at generateLiteral()")
proc generateUnary(self: BytecodeGenerator, expression: TypedExpr) =
## Emits code for unary expressions
echo expression[]
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr) =
## Emits code for expressions
if expression.node.isConst():
self.generateLiteral(expression)
else:
let node = expression.node
case node.kind:
of binaryExpr:
self.generateUnary(expression)
of unaryExpr:
discard
else:
self.error(&"Unknown typed node of type {node.kind} at generateExpression()")
proc beginProgram(self: BytecodeGenerator): int =
@ -420,13 +445,13 @@ proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode], typeChecker: T
self.currentNode = typedNode
let currentFile = self.currentFile
if self.currentNode.node.isDecl():
self.currentFile = TypedDecl(typedNode).name.ident.token.lexeme
self.currentFile = TypedDecl(typedNode).name.owner.ident.token.lexeme
case typedNode.node.kind:
of exprStmt:
self.generateExpression(TypedExprStmt(typedNode).expression)
self.emitByte(Pop, typedNode.node.token.line)
else:
discard # TODO
self.error(&"Unknown typed node of type {typedNode.node.kind} at generate()")
self.currentFile = currentFile
self.endProgram(offset)
result = self.chunk

View File

@ -49,7 +49,7 @@ type
## peon objects
String, List,
Dict, Tuple,
CustomType,
Structure,
HeapObject* = object
## A tagged box for a heap-allocated
## peon object

View File

@ -144,19 +144,13 @@ proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "magic" pragma. Assumes the given name is already
## declared
if pragma.args.len() != 1:
self.error(&"'magic' pragma: wrong number of arguments (expected 1, got {len(pragma.args)})")
self.error(&"'magic' pragma: wrong number of arguments (expected 1, got {len(pragma.args)} instead)")
elif pragma.args[0].kind != strExpr:
self.error(&"'magic' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))})")
elif name.node.kind == NodeKind.funDecl:
name.valueType.intrinsic = true
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
self.error(&"'magic' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))} instead)")
elif name.node.kind == NodeKind.typeDecl:
name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
if name.valueType.isNil():
let typ = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
if typ.isNil():
self.error("'magic' pragma: wrong argument value", pragma.args[0])
if TypeDecl(name.node).isRef:
name.valueType = name.valueType.toRef()
name.valueType = name.valueType.wrapType()
name.valueType.intrinsic = true
else:
self.error("'magic' pragma is not valid in this context")
@ -186,10 +180,8 @@ proc handleWarnPragma(self: TypeChecker, pragma: Pragma, name: Name) =
proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "pure" pragma
case name.node.kind:
of NodeKind.funDecl:
FunDecl(name.node).isPure = true
of NodeKind.lambdaExpr:
LambdaExpr(name.node).isPure = true
of NodeKind.funDecl, lambdaExpr:
discard # TODO
else:
self.error("'pure' pragma is not valid in this context")
@ -244,19 +236,18 @@ proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name =
proc wrapType(self: Type): Type {.inline.} =
## Wraps a type in a typevar if it's not already
## wrapped
if self.kind != Typevar:
return Type(kind: Typevar, wrapped: self)
return self
## Wraps a type in a typevar
result = Type(kind: Typevar, wrapped: self)
proc unwrapType(self: Type): Type {.inline.} =
## Unwraps a typevar if it's not already
## unwrapped
if self.kind == Typevar:
return self.wrapped
return self
case self.kind:
of Typevar:
result = self.wrapped
else:
result = self
proc toRef(self: Type): Type {.inline.} =
@ -285,54 +276,55 @@ proc toIntrinsic(name: string): Type =
## Converts a string to an intrinsic
## type if it is valid and returns nil
## otherwise
if name == "any":
return Type(kind: Any, intrinsic: true)
elif name == "auto":
return Type(kind: Auto, intrinsic: true)
elif name in ["int", "int64", "i64"]:
return Type(kind: Integer, size: LongLong, signed: true, intrinsic: true)
elif name in ["uint64", "u64", "uint"]:
return Type(kind: Integer, size: LongLong, signed: false, intrinsic: true)
elif name in ["int32", "i32"]:
return Type(kind: Integer, size: Long, signed: true, intrinsic: true)
elif name in ["uint32", "u32"]:
return Type(kind: Integer, size: Long, signed: false, intrinsic: true)
elif name in ["int16", "i16", "short"]:
return Type(kind: Integer, size: Short, signed: true, intrinsic: true)
elif name in ["uint16", "u16"]:
return Type(kind: Integer, size: Short, signed: false, intrinsic: true)
elif name in ["int8", "i8"]:
return Type(kind: Integer, size: Tiny, signed: true, intrinsic: true)
elif name in ["uint8", "u8"]:
return Type(kind: Integer, size: Tiny, signed: false, intrinsic: true)
elif name in ["f64", "float", "float64"]:
return Type(kind: Float, width: Full, intrinsic: true)
elif name in ["f32", "float32"]:
return Type(kind: Float, width: Half, intrinsic: true)
elif name in ["byte", "b"]:
return Type(kind: Byte, intrinsic: true)
elif name in ["char", "c"]:
return Type(kind: Char, intrinsic: true)
elif name == "NaN":
return Type(kind: TypeKind.Nan, intrinsic: true)
elif name == "Inf":
return Type(kind: Infinity, intrinsic: true, positive: true)
elif name == "NegInf":
return Type(kind: Infinity, intrinsic: true)
elif name == "bool":
return Type(kind: Boolean, intrinsic: true)
elif name == "typevar":
return Type(kind: Typevar, intrinsic: true, wrapped: "any".toIntrinsic())
elif name == "string":
return Type(kind: String, intrinsic: true)
elif name == "pointer":
return Type(kind: Pointer, intrinsic: true, value: "any".toIntrinsic())
elif name == "lent":
return Type(kind: TypeKind.Lent, intrinsic: true, value: "any".toIntrinsic())
elif name == "const":
return Type(kind: TypeKind.Const, intrinsic: true, value: "any".toIntrinsic())
elif name == "ref":
return Type(kind: Reference, intrinsic: true, value: "any".toIntrinsic())
case name:
of "any":
return Type(kind: Any)
of "auto":
return Type(kind: Auto)
of "int64":
return Type(kind: Integer, size: LongLong, signed: true)
of "uint64":
return Type(kind: Integer, size: LongLong, signed: false)
of "int32":
return Type(kind: Integer, size: Long, signed: true)
of "uint32":
return Type(kind: Integer, size: Long, signed: false)
of "int16":
return Type(kind: Integer, size: Short, signed: true)
of "uint16":
return Type(kind: Integer, size: Short, signed: false)
of "int8":
return Type(kind: Integer, size: Tiny, signed: true)
of "uint8":
return Type(kind: Integer, size: Tiny, signed: false)
of "float64":
return Type(kind: Float, width: Full)
of "float32":
return Type(kind: Float, width: Half)
of "byte":
return Type(kind: Byte)
of "char":
return Type(kind: Char)
of "NaN":
return Type(kind: TypeKind.Nan)
of "Inf":
return Type(kind: Infinity, positive: true)
of "NegInf":
return Type(kind: Infinity)
of "bool":
return Type(kind: Boolean)
of "typevar":
return Type(kind: Typevar, wrapped: "any".toIntrinsic())
of "string":
return Type(kind: String)
of "pointer":
return Type(kind: Pointer, value: "any".toIntrinsic())
of "lent":
return Type(kind: TypeKind.Lent, value: "any".toIntrinsic())
of "const":
return Type(kind: TypeKind.Const, value: "any".toIntrinsic())
of "ref":
return Type(kind: Reference, value: "any".toIntrinsic())
@ -407,6 +399,9 @@ proc matchGeneric(self: TypeChecker, a: Type, b: seq[tuple[match: bool, kind: Ty
return true
proc isType(typ: Type): bool =
return typ.kind in [Structure, Typevar]
proc isAny(typ: Type): bool =
## Returns true if the given type is
@ -415,10 +410,6 @@ proc isAny(typ: Type): bool =
case typ.kind:
of Any:
return true
of Generic:
for condition in typ.cond:
if condition.kind.isAny():
return true
of Union:
for condition in typ.types:
if condition.kind.isAny():
@ -428,45 +419,16 @@ proc isAny(typ: Type): bool =
proc compare(self: TypeChecker, a, b: Type): bool =
if a.isNil() or b.isNil():
raise newException(NilAccessDefect, "what the fuck are you doing")
if a.isAny() or b.isAny():
return true
if a.kind == b.kind:
case a.kind:
# TODO: Take interfaces into account
of CustomType:
# First, compare their names (only if both types have names)
if a.typeName != "" and a.typeName != b.typeName:
return false
# Then, compare the number of fields
if a.fields.len() != b.fields.len():
return false
# a has a parent and b doesn't
if b.parent.isNil() and not a.parent.isNil():
return false
# b has a parent and a doesn't
if a.parent.isNil() and not b.parent.isNil():
return false
if not a.parent.isNil() and not b.parent.isNil():
if not self.compare(a.parent, b.parent):
# Different parent
return false
# Then the field names
var fields: HashSet[string] = initHashSet[string]()
for fieldName in a.fields.keys():
fields.incl(fieldName)
for fieldName in b.fields.keys():
if not fields.contains(fieldName):
return false
# From here on out, we know that both a and b have
# the same number of fields and that they share field
# names. All we gotta now do is compare the types of the
# fields
for fieldName in a.fields.keys():
if not self.compare(a.fields[fieldName], b.fields[fieldName]):
return false
# All checks passed, types are equal as
# far as the type system is concerned
return true
of Structure:
# TODO
return false
of Boolean, Infinity, Any,
Auto, Char, Byte, String:
return true
@ -481,27 +443,8 @@ proc compare(self: TypeChecker, a, b: Type): bool =
of Typevar:
return self.compare(a.wrapped, b.wrapped)
of Function:
# Are arities the same?
if a.args.len() != b.args.len():
return false
# Is it a coroutine/generator?
if a.isCoroutine != b.isCoroutine or a.isGenerator != b.isGenerator:
return false
# Do they have compatible return types?
if not self.compare(b.returnType, a.returnType):
return false
# Do they have compatible arguments?
var i = 0
for (argA, argB) in zip(a.args, b.args):
if not self.compare(argA.kind, argB.kind):
return false
if argA.name == "":
continue
if argB.name == "":
continue
if argA.name != argB.name:
return false
return true
# TODO
return false
else:
# TODO
return false
@ -509,16 +452,6 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return self.matchUnion(b, a.types)
if b.kind == Union:
return self.matchUnion(a, b.types)
if a.kind == Generic:
if a.asUnion:
return self.matchUnion(b, a.cond)
else:
return self.matchGeneric(b, a.cond)
if b.kind == Generic:
if b.asUnion:
return self.matchUnion(a, b.cond)
else:
return self.matchGeneric(a, b.cond)
return false
@ -637,6 +570,8 @@ proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Na
if self.compare(obj.valueType, kind):
result = obj
break
echo "DIOSTRONZO"
if not result.isNil():
break
dec(depth)
@ -695,14 +630,23 @@ proc stringify*(self: TypeChecker, typ: Type): string =
of Char, Byte, String, TypeKind.Nan,
Auto, Any:
result &= ($typ.kind).toLowerAscii()
of CustomType:
result &= typ.typeName
if typ.generics.len() > 0:
of Structure:
result &= typ.name
if typ.genericTypes.len() > 0:
result &= "<"
var i = 0
for gen in typ.genericTypes.keys():
result &= &"{gen}: {self.stringify(typ.genericTypes[gen])}"
if i < typ.genericTypes.len() - 1:
result &= ", "
inc(i)
result &= ">"
if typ.genericValues.len() > 0:
result &= "["
var i = 0
for gen in typ.generics.keys():
result &= &"{gen}: {self.stringify(typ.generics[gen])}"
if i < typ.generics.len() - 1:
for gen in typ.genericValues.keys():
result &= &"{gen}: {self.stringify(typ.genericValues[gen])}"
if i < typ.genericValues.len() - 1:
result &= ", "
inc(i)
result &= "]"
@ -731,15 +675,17 @@ proc stringify*(self: TypeChecker, typ: Type): string =
result &= &"const {self.stringify(typ.value)}"
of Function:
result &= "fn "
if typ.fun.generics.len() > 0:
result &= "["
for i, gen in typ.fun.generics:
result &= &"{gen.name.token.lexeme}: {self.stringify(typ.genericArgs[gen.name.token.lexeme])}"
if i < typ.fun.generics.len() - 1:
if typ.genericTypes.len() > 0:
result &= "<"
var i = 0
for gen in typ.genericTypes.keys():
result &= &"{gen}: {self.stringify(typ.genericTypes[gen])}"
if i < typ.genericTypes.len() - 1:
result &= ", "
inc(i)
result &= "]"
result &= "("
for i, (argName, argType, argDefault) in typ.args:
for i, (argName, argType, argDefault) in typ.parameters:
result &= &"{argName}: "
if argType.kind == Generic:
result &= argType.name
@ -747,25 +693,27 @@ proc stringify*(self: TypeChecker, typ: Type): string =
result &= self.stringify(argType)
if not argDefault.isNil():
result &= &" = {argDefault.kind}"
if i < typ.args.len() - 1:
if i < typ.parameters.len() - 1:
result &= ", "
result &= ")"
if not self.compare(typ.returnType, "nil".toIntrinsic()):
result &= &": {self.stringify(typ.returnType)}"
if typ.fun.pragmas.len() > 0:
if typ.pragmas.len() > 0:
result &= " {"
for i, pragma in typ.fun.pragmas:
result &= &"{pragma.name.token.lexeme}"
var i = 0
for name, pragma in typ.pragmas:
result &= &"{name}"
if pragma.args.len() > 0:
result &= ": "
for j, arg in pragma.args:
result &= arg.token.lexeme
if j < pragma.args.high():
result &= ", "
if i < typ.fun.pragmas.high():
if i < typ.pragmas.len() - 1:
result &= ", "
else:
result &= "}"
inc(i)
of TypeKind.Lent:
result &= &"lent {self.stringify(typ.value)}"
of Union:
@ -775,13 +723,6 @@ proc stringify*(self: TypeChecker, typ: Type): string =
if not condition.match:
result &= "~"
result &= self.stringify(condition.kind)
of Generic:
for i, condition in typ.cond:
if i > 0:
result &= " | "
if not condition.match:
result &= "~"
result &= self.stringify(condition.kind)
else:
discard # TODO(?)
@ -814,32 +755,32 @@ proc endScope(self: TypeChecker) =
assert self.scopeDepth == self.names.high()
proc check(self: TypeChecker, term, expected: Type, node: ASTNode = nil): Type {.inline, discardable.} =
## Like the other check(), but works with two type objects.
## The node is passed in to error() in case of a failure
result = term
if not self.compare(result, expected):
self.error(&"expecting expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", node=node)
if result.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", node)
proc check(self: TypeChecker, term: Expression, expected: Type): TypedExpr {.inline, discardable.} =
## Checks the type of the given expression against a known one.
## Raises an error if appropriate and returns the typed expression
## otherwise
result = self.inferOrError(term)
if not self.compare(result.kind, expected):
self.error(&"expecting value of type {self.stringify(expected)}, got {self.stringify(result)}", term)
if not result.kind.isAny() and expected.isAny():
self.error(&"expecting expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", term)
if result.kind.isAny() and not expected.isAny():
self.error("any is not a valid type in this context", term)
proc check(self: TypeChecker, term, expected: Type, node: ASTNode = nil): Type {.inline, discardable.} =
## Like the other check(), but works with two type objects.
## The node is passed in to error() in case of a failure
result = term
if not self.compare(result, expected):
self.error(&"expecting value of type {self.stringify(expected)}, got {self.stringify(result)}", node=node)
if not result.isAny() and expected.isAny():
self.error("any is not a valid type in this context", node=node)
proc getTypeDistance(self: TypeChecker, a, b: Type): int =
## Gets the type distance of two Peon types. Assumes
## a and b are already compatible (i.e. compare(a, b)
## returns true). For more info, check out self.match()
if a.kind != CustomType or b.kind != CustomType:
if a.kind != Structure or b.kind != Structure:
# TODO: Test
return 0
var parent = b.parent
@ -869,9 +810,9 @@ proc calcTypeDistance(self: TypeChecker, typ: Type, sig: TypeSignature): int =
## that the types are already compatible
case typ.kind:
of TypeKind.Function:
for (argA, argB) in zip(typ.args, sig):
for (argA, argB) in zip(typ.parameters, sig):
result += self.getTypeDistance(argA.kind, argB.kind)
of TypeKind.CustomType:
of TypeKind.Structure:
discard
else:
self.error(&"cannot compute type distance for object of type {self.stringify(typ)}")
@ -883,7 +824,7 @@ proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool
## the given type signature
case typ.kind:
of TypeKind.Function:
if typ.args.len() < sig.len():
if typ.parameters.len() < sig.len():
# If the function has less arguments than
# we have in our signature, then we surely
# can't match to it. This is different from
@ -900,7 +841,7 @@ proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool
return false
# We construct a new signature, without the function's default arguments
var args: TypeSignature = @[]
for argument in typ.args:
for argument in typ.parameters:
if argument.default.isNil():
args.add(argument)
else:
@ -915,7 +856,7 @@ proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool
elif argA.name != argB.name:
return false
return true
of TypeKind.CustomType:
of TypeKind.Structure:
if sig.len() != typ.fields.len():
# For now, we require that all fields of an object
# be explicitly initialized
@ -944,7 +885,7 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
# Find all matching implementations and record their
# type distance relative to our input type signature,
# as well as the smallest one found so far
for n in filterIt(self.findAll(name), it.valueType.kind in [TypeKind.Function, TypeKind.CustomType]):
for n in filterIt(self.findAll(name), it.valueType.kind in [TypeKind.Function, TypeKind.Structure]):
if self.checkTypeSignature(n.valueType, sig):
impl.add(n)
dst = self.calcTypeDistance(n.valueType, sig)
@ -1010,10 +951,10 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
# Extra checks
case result.valueType.kind:
of Function:
for (a, b) in zip(result.valueType.args, sig):
for (a, b) in zip(result.valueType.parameters, sig):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
of CustomType:
of Structure:
for (a, b) in zip(result.valueType.fields.values().toSeq(), sig):
if not a.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
@ -1036,16 +977,16 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
msg &= ":"
for name in names:
msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.stringify(name.valueType)}"
if name.valueType.kind notin [Function, CustomType]:
if name.valueType.kind notin [Function, Structure]:
msg &= ": not callable"
elif sig.len() != name.valueType.args.len():
msg &= &": wrong number of arguments (expected {name.valueType.args.len()}, got {sig.len()})"
elif sig.len() != name.valueType.parameters.len():
msg &= &": wrong number of arguments (expected {name.valueType.parameters.len()}, got {sig.len()} instead)"
else:
for i, arg in sig:
if arg.name != "" and name.valueType.args[i].name != "" and arg.name != name.valueType.args[i].name:
if arg.name != "" and name.valueType.parameters[i].name != "" and arg.name != name.valueType.parameters[i].name:
msg &= &": unexpected argument '{arg.name}' at position {i + 1}"
if not self.compare(arg.kind, name.valueType.args[i].kind):
msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.valueType.args[i].kind)}, got {self.stringify(arg.kind)})"
if not self.compare(arg.kind, name.valueType.parameters[i].kind):
msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.valueType.parameters[i].kind)}, got {self.stringify(arg.kind)} instead)"
break
else:
msg &= " (compile with --showMismatches for more details)"
@ -1064,46 +1005,25 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr]): Type =
## Instantiates a generic type
if not name.isGeneric:
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
case name.node.kind:
of NodeKind.funDecl:
var fun = FunDecl(name.node)
if fun.generics.len() != args.len():
self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {args.len()})")
var concrete = deepCopy(name.valueType)
var map = newTable[string, Type]()
for arg in args:
if arg.kind.kind != Typevar:
self.error(&"expecting type name during generic instantiation, got {self.stringify(arg.kind)}", arg.node)
for (gen, value) in zip(fun.generics, args):
map[gen.name.token.lexeme] = value.kind
for i, argument in concrete.args:
if argument.kind.kind != Generic:
continue
elif argument.name in map:
concrete.args[i].kind = map[argument.name]
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)
result = concrete
## Instantiates a generic type
let typ = name.valueType
case typ.kind:
of TypeKind.Structure:
discard
else:
self.error(&"cannot instantiate concrete type for object of type {self.stringify(name.valueType)}")
proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true, isGeneric: bool = false) =
proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true, expectTypes: bool = false) =
## Recursively unpacks a type constraint
case condition.kind:
of identExpr:
of identExpr, genericExpr:
var typ = self.inferOrError(condition).kind
if not isGeneric and not self.compare(typ, "typevar".toIntrinsic()):
self.error(&"expecting a type name, got value of type {self.stringify(typ)}", condition)
typ = typ.unwrapType()
if expectTypes:
if not typ.isType():
self.error(&"expecting a type, got a value of type {self.stringify(typ)} instead", condition)
typ = typ.unwrapType()
if self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types cannot be used within type constraints", condition)
list.add((accept, typ, condition))
@ -1170,10 +1090,10 @@ proc addName(self: TypeChecker, name: Name) =
var scope = self.names[self.scopeDepth]
if scope.hasKey(name.ident.token.lexeme):
for obj in scope[name.ident.token.lexeme]:
if name.kind == NameKind.Function:
if name.valueType.kind == TypeKind.Function:
# We don't check for name clashes for functions because self.match() does that
continue
if obj.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum, NameKind.Function] and name.owner == obj.owner:
if (obj.kind in [NameKind.Var, NameKind.Module] or obj.valueType.kind in [TypeKind.Structure, TypeKind.EnumEntry]) and name.owner == obj.owner:
self.error(&"re-declaration of '{obj.ident.token.lexeme}' is not allowed (previously declared in {obj.owner.ident.token.lexeme}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", name.node)
else:
scope[name.ident.token.lexeme] = @[]
@ -1195,70 +1115,30 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
isPrivate: node.isPrivate,
owner: self.currentModule,
file: self.file,
valueType: "nil".toIntrinsic(), # Done later in varDecl (for better semantics)
valueType: nil, # Done later in varDecl (for better semantics)
line: node.token.line,
belongsTo: self.currentFunction,
kind: NameKind.Var,
node: node,
isReal: true
)
self.addName(name)
of NodeKind.funDecl:
var node = FunDecl(node)
declaredName = node.name.token.lexeme
name = Name(depth: self.scopeDepth,
isPrivate: node.isPrivate,
owner: self.currentModule,
file: self.file,
valueType: Type(kind: Function,
returnType: "nil".toIntrinsic(), # We check it later
args: @[],
fun: node,
forwarded: node.body.isNil(),
isAuto: false,
genericArgs: newTable[string, Type]()),
ident: node.name,
node: node,
line: node.token.line,
kind: NameKind.Function,
belongsTo: self.currentFunction,
isReal: true,
)
if node.generics.len() > 0:
name.isGeneric = true
self.addName(name)
discard
of NodeKind.importStmt:
var node = ImportStmt(node)
# We change the name of the module internally so that
# if you import /path/to/mod, then doing mod.f() will
# still work without any extra work on our end. Note how
# we don't change the metadata about the identifier's
# position so that error messages still highlight the
# full path
let path = node.moduleName.token.lexeme
node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename()
self.addName(Name(depth: self.scopeDepth,
owner: self.currentModule,
file: "", # The file of the module isn't known until it's compiled!
path: path,
ident: node.moduleName,
line: node.moduleName.token.line,
kind: NameKind.Module,
isPrivate: false,
isReal: true
))
name = scope[node.moduleName.token.lexeme][^1]
declaredName = name.ident.token.lexeme
discard
of NodeKind.typeDecl:
var node = TypeDecl(node)
declaredName = node.name.token.lexeme
var kind: Type = Type(kind: CustomType, typeName: declaredName, generics: newTable[string, Type](), fields: newTable[string, Type](),
interfaces: @[], isEnum: node.isEnum)
var kind: Type = Type(kind: Structure,
name: declaredName,
genericTypes: newTable[string, Type](),
genericValues: newTable[string, Type](),
fields: newTable[string, Type](),
interfaces: @[],
isEnum: node.isEnum)
if node.isRef:
kind = kind.toRef()
kind = kind.wrapType()
self.addName(Name(kind: NameKind.CustomType,
depth: self.scopeDepth,
self.addName(Name(depth: self.scopeDepth,
owner: self.currentModule,
node: node,
ident: node.name,
@ -1266,7 +1146,6 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
isPrivate: node.isPrivate,
belongsTo: self.currentFunction,
valueType: kind,
isReal: true,
))
name = scope[declaredName][^1]
else:
@ -1277,41 +1156,37 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
proc identifier(self: TypeChecker, node: IdentExpr): TypedExpr =
## Compiles name resolution
## Typechecks name resolution
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme, node=node))
proc unary(self: TypeChecker, node: UnaryExpr): TypedUnaryExpr =
## Compiles unary expressions
## Typechecks unary expressions
var
default: TypedExpr
typeOfA = self.infer(node.a)
let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA.kind, default)])
var name = self.match(node.token.lexeme, fn.args, node)
var impl = name.valueType
if name.isGeneric:
impl = self.specialize(name, @[self.expression(node.a)])
let fn = Type(kind: Function, returnType: Type(kind: Any), parameters: @[("", typeOfA.kind, default)])
let name = self.match(node.token.lexeme, fn.parameters, node)
let impl = self.specialize(name, @[self.expression(node.a)])
result = newTypedUnaryExpr(node, impl.returnType, typeOfA)
proc binary(self: TypeChecker, node: BinaryExpr): TypedBinaryExpr =
## Compiles unary expressions
## Typechecks binary expressions
var
default: TypedExpr
typeOfA = self.infer(node.a)
typeOfB = self.infer(node.b)
let fn = Type(kind: Function,
returnType: Type(kind: Any),
args: @[("", typeOfA.kind, default), ("", typeOfB.kind, default)])
var name = self.match(node.token.lexeme, fn.args, node)
var impl = name.valueType
if name.isGeneric:
impl = self.specialize(name, @[self.expression(node.a)])
parameters: @[("", typeOfA.kind, default), ("", typeOfB.kind, default)])
let name = self.match(node.token.lexeme, fn.parameters, node)
let impl = self.specialize(name, @[self.expression(node.a)])
result = newTypedBinaryExpr(node, impl.returnType, typeOfA, typeOfB)
proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
## Compiles generic instantiation
## Typechecks generic instantiation
var args: seq[TypedExpr] = @[]
for arg in node.args:
args.add(self.expression(arg))
@ -1319,7 +1194,7 @@ proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
proc call(self: TypeChecker, node: CallExpr): TypedExpr =
## Compiles call expressions. This includes
## Typechecks call expressions. This includes
## things like object and enum construction
var args: TypeSignature = @[]
var argExpr: seq[TypedExpr] = @[]
@ -1338,11 +1213,9 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
# Calls like hi()
var impl = self.match(IdentExpr(node.callee).name.lexeme, args, node)
self.dispatchDelayedPragmas(impl)
var typ = impl.valueType
if impl.isGeneric:
typ = self.specialize(impl, argExpr)
var typ = self.specialize(impl, argExpr)
case typ.kind:
of CustomType:
of Structure:
# TODO
result = newTypedExpr(node, typ)
of Function:
@ -1451,7 +1324,7 @@ method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true):
self.error(&"cannot assign to '{name.token.lexeme}' (value is a constant)", name)
elif r.mutable:
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
if r.valueType.kind != CustomType:
if r.valueType.kind != Structure:
self.error("only types have fields", node)
else:
self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)")
@ -1500,27 +1373,27 @@ proc blockStmt(self: TypeChecker, node: BlockStmt): TypedBlockStmt =
proc ifStmt(self: TypeChecker, node: IfStmt): TypedNode =
## Typechecks if/else statements for conditional
## execution of code
## Typechecks if/else statements
# Check that the condition is a boolean and record it
# Check the condition
let condition = self.check(node.condition, "bool".toIntrinsic())
# Compile the "then" part of "if-then-else"
# Check the "then" part of "if-then-else"
let then = TypedBlockStmt(self.validate(node.thenBranch))
# Compile the "else" part
# Check the "else" part
let otherwise = TypedBlockStmt(self.validate(node.elseBranch))
# Note: Peon enforces the body of loops and conditionals to
# always be a block statement (for a variety of very good reasons),
# so the conversion here is safe
# always be a block statement (for a variety of very good reasons,
# as it avoids mistakes like the infamous GOTO fail), so the
# conversion here is safe
return newTypedIfStmt(node, then, otherwise, condition)
proc whileStmt(self: TypeChecker, node: WhileStmt): TypedNode =
## Typechecks C-style while loops
# Compile and check the condition
# Check the condition
let condition = self.check(node.condition, "bool".toIntrinsic())
# Compile the body
# Check the body
return newTypedWhileStmt(node, TypedBlockStmt(self.validate(node.body)), condition)
@ -1539,19 +1412,19 @@ proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
if node.constant and not node.value.isConst():
self.error("constant initializer is not a constant", node.value)
init = TypedExpr(self.validate(node.value))
typ = init.kind.wrapType()
typ = init.kind
if not node.valueType.isNil():
# Explicit type declaration always takes over
typ = self.inferOrError(node.valueType).kind
# Check that the inferred expression represents a type
# and not a value. This is to guard against things
# like "var x: 1 = 1;". We unwrap it immediately
# because we don't want to assign a typevar to the
# valueType field of the variable-- it would just
# be redundant
typ = self.check(typ, "typevar".toIntrinsic(), node.valueType)
if typ.isNil():
self.error("expecting either a type declaration or an initializer value, but neither was found", node)
# Check that the inferred expression represents a type
# and not a value. This is to guard against things
# like "var x: 1 = 1;". We unwrap it immediately
# because we don't want to assign a typevar to the
# valueType field of the variable-- it would just
# be redundant
typ = self.check(typ, "typevar".toIntrinsic(), node.valueType).unwrapType()
# Now check that the type of the initializer, if it exists,
# matches the type of the variable
if not init.isNil():
@ -1586,11 +1459,7 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
name.valueType.isAuto = true
if node.body.isNil():
# Forward declaration
name.valueType.forwarded = true
self.endScope()
return
if name.valueType.intrinsic:
# Builtins are handled at call time
# TODO
self.endScope()
return
# We store the current function to restore
@ -1610,27 +1479,17 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
proc declareGenerics(self: TypeChecker, name: Name) =
## Helper to declare the generic arguments of the
## given name, if it has any
if name.valueType.kind != TypeKind.Structure:
return
var
constraints: seq[tuple[match: bool, kind: Type, value: Expression]] = @[]
value: Expression
for gen in name.node.generics:
for gen in name.node.genericTypes:
if gen.cond.isNil():
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
else:
self.unpackTypes(gen.cond, constraints)
self.addName(Name(depth: self.scopeDepth,
isPrivate: true,
valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints),
line: name.node.token.line,
belongsTo: name,
ident: gen.name,
owner: self.currentModule,
file: self.file,
isReal: true))
if name.valueType.wrapped.generics.hasKey(gen.name.token.lexeme):
self.error(&"cannot re-declare generic argument '{gen.name.token.lexeme}'", node=gen.name)
name.valueType.wrapped.generics[gen.name.token.lexeme] = self.names[self.scopeDepth][gen.name.token.lexeme][^1].valueType
constraints = @[]
self.unpackTypes(gen.cond, constraints, expectTypes=true)
# TODO
proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDecl =
@ -1651,7 +1510,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
for field in node.fields:
fieldType = self.infer(field.valueType)
if not node.isRef:
# Check for self-recursion of non-ref (aka managed pointer) types (which would require
# Check for self-recursion of non-ref types (which would require
# infinite memory)
n = fieldType.getName()
if n.isNil():
@ -1660,6 +1519,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
if name == n:
self.error(&"illegal self-recursion in member '{field.name.token.lexeme}' for non-ref type '{name.ident.token.lexeme}'", fieldType.node)
result.fields[field.name.token.lexeme] = fieldType
name.valueType.fields[field.name.token.lexeme] = fieldType.kind
else:
# Type is a variant type (aka enum). We'll only declare a single
# object (the enum type itself) so that we don't pollute the
@ -1706,7 +1566,6 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
self.endScope()
proc pragmaExpr(self: TypeChecker, pragma: Pragma) =
## Validates pragma expressions (not bound to a name)
if pragma.name.token.lexeme notin self.pragmas:
@ -1714,7 +1573,6 @@ proc pragmaExpr(self: TypeChecker, pragma: Pragma) =
self.pragmas[pragma.name.token.lexeme].handler(self, pragma, nil)
proc validate(self: TypeChecker, node: ASTNode): TypedNode =
## Dispatches typeless AST nodes to typecheck them and turn
## them into typed ones
@ -1725,8 +1583,7 @@ proc validate(self: TypeChecker, node: ASTNode): TypedNode =
result = self.expression(Expression(node))
of exprStmt:
let statement = ExprStmt(node)
result = self.expression(statement.expression)
result = TypedExprStmt(node: statement, expression: TypedExpr(result))
result = TypedExprStmt(node: statement, expression: TypedExpr(self.validate(statement.expression)))
of NodeKind.whileStmt:
result = self.whileStmt(WhileStmt(node))
of NodeKind.blockStmt:
@ -1781,10 +1638,9 @@ proc validate*(self: TypeChecker, tree: ParseTree, file, source: string, showMis
file: self.file,
valueType: Type(kind: Function,
returnType: "nil".toIntrinsic(),
args: @[],
parameters: @[],
),
ident: newIdentExpr(Token(lexeme: "", kind: Identifier)),
kind: NameKind.Function,
line: 1)
self.addName(main)
while not self.done():

View File

@ -35,7 +35,7 @@ type
Auto,
Byte,
Char,
CustomType,
Structure,
EnumEntry,
Reference,
Pointer,
@ -48,12 +48,17 @@ type
Type* = ref object
## A compile-time type
# Is this type a compiler intrinsic?
intrinsic*: bool
# Is this a type constant?
constant*: bool
# Can this value be mutated?
# Can it be mutated?
mutable*: bool
# Is it a compiler intrinsic?
intrinsic*: bool
# Mapping of generic names to types
genericTypes*: TableRef[string, Type]
genericValues*: TableRef[string, Type]
# Type pragmas
pragmas*: TableRef[string, Pragma]
case kind*: TypeKind
of Integer:
signed*: bool
@ -67,31 +72,19 @@ type
isGenerator*: bool
isCoroutine*: bool
isAuto*: bool
args*: TypeSignature
parameters*: TypeSignature
returnType*: Type
builtinOp*: string
fun*: Declaration
forwarded*: bool
genericArgs*: TableRef[string, Type]
safe*: bool
of Typevar:
wrapped*: Type
of CustomType:
typeName*: string
generics*: TableRef[string, Type]
of Structure:
name*: string
fields*: TableRef[string, Type]
parent*: Type
interfaces*: seq[Type]
isEnum*: bool
of Reference, Pointer, Lent, Const:
value*: Type
of Generic:
# cond represents a type constraint. For
# example, fn foo[T*: int & ~uint](...) {...}
# would map to [(true, int), (false, uint)]
cond*: seq[tuple[match: bool, kind: Type, value: Expression]]
asUnion*: bool # If this is true, the constraint is treated like a type union
name*: string
of Union:
types*: seq[tuple[match: bool, kind: Type, value: Expression]]
else:
@ -103,7 +96,7 @@ type
NameKind* {.pure.} = enum
## A name enumeration type
None, Module, Argument, Var, Function, CustomType, Enum
Default, Var, Module
Name* = ref object
## A generic name object
@ -138,8 +131,6 @@ type
depth*: int
# Is this name private?
isPrivate*: bool
# Is this name a generic type?
isGeneric*: bool
# The type of the name's associated
# value
valueType*: Type
@ -155,8 +146,6 @@ type
# Who is this name exported to? (Only makes sense if isPrivate
# equals false)
exportedTo*: seq[Name]
# Is this name generated by user code or internally by the type checker?
isReal*: bool
TypeSignature* = seq[tuple[name: string, kind: Type, default: TypedExpr]]

View File

@ -108,7 +108,9 @@ type
## A declaration
isPrivate*: bool
pragmas*: seq[Pragma]
generics*: seq[tuple[name: IdentExpr, cond: Expression]]
genericTypes*: seq[tuple[name: IdentExpr, cond: Expression]]
genericValues*: seq[tuple[name: IdentExpr, cond: Expression]]
Statement* = ref object of Declaration
## A statement
@ -176,7 +178,6 @@ type
defaults*: seq[Expression]
isGenerator*: bool
isAsync*: bool
isPure*: bool
returnType*: Expression
depth*: int
@ -257,9 +258,7 @@ type
defaults*: seq[Expression]
isAsync*: bool
isGenerator*: bool
isPure*: bool
returnType*: Expression
depth*: int
TypeDecl* = ref object of Declaration
name*: IdentExpr
@ -434,7 +433,8 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
token: Token, depth: int, pragmas: seq[Pragma] = @[],
returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[]): LambdaExpr =
returnType: Expression, genericTypes: seq[tuple[name: IdentExpr, cond: Expression]] = @[],
genericValues: seq[tuple[name: IdentExpr, cond: Expression]] = @[]): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr)
result.body = body
result.arguments = arguments
@ -443,9 +443,9 @@ proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]
result.isAsync = isAsync
result.token = token
result.returnType = returnType
result.isPure = false
result.pragmas = pragmas
result.generics = generics
result.genericTypes = genericTypes
result.genericValues = genericValues
result.depth = depth
@ -640,10 +640,9 @@ proc newVarDecl*(name: IdentExpr, valueType, value: Expression, token: Token,
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
isPrivate: bool, token: Token, depth: int,
pragmas: seq[Pragma] = @[], returnType: Expression,
generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[]): FunDecl =
body: Statement, isAsync, isGenerator: bool, isPrivate: bool, token: Token, pragmas: seq[Pragma] = @[],
returnType: Expression, genericTypes: seq[tuple[name: IdentExpr, cond: Expression]] = @[],
genericValues: seq[tuple[name: IdentExpr, cond: Expression]] = @[]): FunDecl =
result = FunDecl(kind: funDecl)
result.name = name
result.arguments = arguments
@ -655,21 +654,23 @@ proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueTyp
result.token = token
result.pragmas = pragmas
result.returnType = returnType
result.isPure = false
result.generics = generics
result.depth = depth
result.genericTypes = genericTypes
result.genericValues = genericValues
proc newTypeDecl*(name: IdentExpr, fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool, default: Expression]],
defaults: seq[Expression], isPrivate: bool, token: Token, pragmas: seq[Pragma],
generics: seq[tuple[name: IdentExpr, cond: Expression]], parent: IdentExpr, isEnum: bool, isRef: bool): TypeDecl =
defaults: seq[Expression], isPrivate: bool, token: Token, pragmas: seq[Pragma],
genericTypes: seq[tuple[name: IdentExpr, cond: Expression]],
genericValues: seq[tuple[name: IdentExpr, cond: Expression]],
parent: IdentExpr, isEnum: bool, isRef: bool): TypeDecl =
result = TypeDecl(kind: typeDecl)
result.name = name
result.fields = fields
result.isPrivate = isPrivate
result.token = token
result.pragmas = pragmas
result.generics = generics
result.genericTypes = genericTypes
result.genericValues = genericValues
result.parent = parent
result.isEnum = isEnum
result.isRef = isRef
@ -766,10 +767,10 @@ proc `$`*(self: ASTNode): string =
result &= &"Var(name={self.name}, type={self.valueType}, value={self.value}, mutable={self.mutable}, constant={self.constant}, private={self.isPrivate}, pragmas={self.pragmas})"
of funDecl:
var self = FunDecl(self)
result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generics=[{self.generics.join(", ")}], async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate}, pragmas={self.pragmas})"""
result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], genericTypes={self.genericTypes}, genericValues={self.genericValues}, async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate}, pragmas={self.pragmas})"""
of typeDecl:
var self = TypeDecl(self)
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, members={self.members}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, parent={self.parent}, ref={self.isRef}, enum={self.isEnum}, value={self.value})"""
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, members={self.members}, private={self.isPrivate}, pragmas={self.pragmas}, genericTypes={self.genericTypes}, genericValues={self.genericValues}, parent={self.parent}, ref={self.isRef}, enum={self.isEnum}, value={self.value})"""
of lambdaExpr:
var self = LambdaExpr(self)
result &= &"""Lambda(body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generator={self.isGenerator}, async={self.isAsync}, pragmas={self.pragmas})"""

View File

@ -451,8 +451,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)
@ -890,10 +889,11 @@ proc parsePragmas(self: Parser): seq[Pragma] =
names.add(self.peek(-1).lexeme)
name = newIdentExpr(self.peek(-1), self.scopeDepth)
name.file = self.file
if not self.match(":"):
if self.match("]"):
result.add(newPragma(name, @[]))
break
if self.match("]"):
result.add(newPragma(name, @[]))
break
# Pragma takes more than one argument, so they need
# to be parenthesized to avoid ambiguity
elif self.match("("):
while not self.match(")") and not self.done():
exp = self.primary()
@ -903,7 +903,7 @@ proc parsePragmas(self: Parser): seq[Pragma] =
if not self.match(","):
break
self.expect(LeftParen, "unterminated parenthesis in pragma arguments")
else:
elif self.match(":"):
exp = self.primary()
if not exp.isConst():
self.error("pragma arguments can only be literals", exp.token)
@ -1004,14 +1004,14 @@ proc parseFunExpr(self: Parser): LambdaExpr =
result.file = self.file
proc parseGenericConstraint(self: Parser): Expression =
proc parseGenericConstraint(self: Parser, endToken: TokenType or string): Expression =
## Recursively parses a generic constraint
## and returns it as an expression
result = self.expression()
if not self.check(RightBracket):
if not self.check(endToken):
case self.peek().lexeme:
of "|":
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint(endToken))
result.file = self.file
of "~":
result = newUnaryExpr(self.step(), result)
@ -1025,18 +1025,31 @@ proc parseGenericConstraint(self: Parser): Expression =
proc parseGenerics(self: Parser, decl: Declaration) =
## Parses generics in declarations
var gen: tuple[name: IdentExpr, cond: Expression]
while not self.check(RightBracket) and not self.done():
while not self.check(">") and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
if self.match(":"):
gen.cond = self.parseGenericConstraint()
gen.cond = self.parseGenericConstraint(">")
else:
gen.cond = nil
decl.generics.add(gen)
decl.genericTypes.add(gen)
if not self.match(Comma):
break
self.expect(RightBracket, "unterminated generic declaration")
self.expect(">", "unterminated generic declaration")
if self.match(LeftBracket):
while not self.check(RightBracket) and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
gen.name.file = self.file
if self.match(":"):
gen.cond = self.parseGenericConstraint(RightBracket)
else:
gen.cond = nil
decl.genericValues.add(gen)
if not self.match(Comma):
break
self.expect(RightBracket, "unterminated generic declaration")
proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
@ -1061,12 +1074,11 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
isGenerator=isGenerator,
isPrivate=true,
token=tok,
returnType=nil,
depth=self.scopeDepth)
returnType=nil)
if self.match("*"):
FunDecl(self.currentFunction).isPrivate = false
self.checkDecl(FunDecl(self.currentFunction).isPrivate)
if self.match(LeftBracket):
if self.match("<"):
self.parseGenerics(self.currentFunction)
elif not isLambda and (self.check([LeftBrace, LeftParen]) or self.check(":")):
# We do a bit of hacking to pretend we never
@ -1262,12 +1274,12 @@ proc typeDecl(self: Parser): TypeDecl =
let token = self.peek(-1)
self.expect(Identifier, "expecting type name after 'type'")
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
let isPrivate = not self.match("*")
self.checkDecl(isPrivate)
result = newTypeDecl(name, @[], @[], isPrivate, token, @[], @[], nil, false, false)
result = newTypeDecl(name, @[], @[], true, token, @[], @[], @[], nil, false, false)
result.file = self.file
if self.match(LeftBracket):
if self.match("<"):
self.parseGenerics(result)
result.isPrivate = not self.match("*")
self.checkDecl(result.isPrivate)
self.expect("=", "expecting '=' after type name")
var hasNone = false
case self.peek().kind:
@ -1305,7 +1317,7 @@ proc typeDecl(self: Parser): TypeDecl =
else:
var variant: TypeDecl
while not self.done():
variant = newTypeDecl(nil, @[], @[], true, nil, @[], @[], nil, false, false)
variant = newTypeDecl(nil, @[], @[], true, nil, @[], @[], @[], nil, false, false)
self.expect(Identifier, "expecting variant name")
variant.name = newIdentExpr(self.peek(-1))
variant.token = variant.name.token

View File

@ -86,6 +86,8 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
if fromString:
input = filename
filename = "<string>"
else:
input = readFile(filename)
try:
if not isBinary:
tokens = tokenizer.lex(input, filename)
@ -182,6 +184,11 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
print(exc)
except TypeCheckError as exc:
print(exc)
except CodeGenError as exc:
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(file, getCurrentDir())
stderr.styledWriteLine(fgRed, styleBright, "Error while generating code for ", fgYellow, file, fgDefault, &": {exc.msg}")
except SerializationError as exc:
var file = exc.file
if file notin ["<string>", ""]: