Bug fixes to the parser, typechecker cleanup. Added more CLI options
This commit is contained in:
parent
e7aa19835e
commit
f321644c40
|
@ -47,7 +47,7 @@ const PeonRelease* = "alpha"
|
|||
const PeonCommitHash* = staticExec("git rev-parse HEAD")
|
||||
const PeonBranch* = staticExec("git symbolic-ref HEAD 2>/dev/null | cut -f 3 -d /")
|
||||
const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch}, {CompileDate}, {CompileTime}, {PeonCommitHash[0..PeonCommitHash.high() mod 8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
||||
const HelpMessage* = """The peon programming language, Copyright (C) 2023 Mattia Giambirtone & All Contributors
|
||||
const HelpMessage* = """The peon programming language, Copyright (C) 2024 Mattia Giambirtone & All Contributors
|
||||
|
||||
This program is free software, see the license distributed with this program or check
|
||||
http://www.apache.org/licenses/LICENSE-2.0 for more info.
|
||||
|
@ -71,6 +71,9 @@ Options
|
|||
yes/on and no/off
|
||||
--noWarn Disable a specific warning (example: --noWarn:UserWarning)
|
||||
--noGen Don't generate any code (i.e. stop at the typechecking stage)
|
||||
--noParse Don't parse the code (i.e. stop at the tokenization stage)
|
||||
--noTC, --noTypeCheck Don't typecheck the code (i.e. stop at the parsing stage)
|
||||
|
||||
--showMismatches Show all mismatches when function dispatching fails (output is really verbose)
|
||||
--debugLexer Show the lexer's output
|
||||
--debugParser Show the parser's output
|
||||
|
|
|
@ -80,7 +80,7 @@ proc stringify*(self: TypeChecker, typ: TypedNode): string
|
|||
proc inferOrError*(self: TypeChecker, node: Expression): TypedExpr
|
||||
proc compare(self: TypeChecker, a, b: Type): bool
|
||||
proc expression(self: TypeChecker, node: Expression): TypedExpr
|
||||
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type
|
||||
proc concretize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type
|
||||
proc declareGenerics(self: TypeChecker, name: Name)
|
||||
proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl
|
||||
proc getTypeDistance(self: TypeChecker, a, b: Type): int
|
||||
|
@ -290,6 +290,8 @@ proc toIntrinsic(name: string): Type =
|
|||
## type if it is valid and returns nil
|
||||
## otherwise
|
||||
case name:
|
||||
of "typevar":
|
||||
return Type(kind: Typevar, wrapped: "any".toIntrinsic(), intrinsic: true)
|
||||
of "any":
|
||||
return Type(kind: Any, intrinsic: true)
|
||||
of "auto":
|
||||
|
@ -336,6 +338,8 @@ proc toIntrinsic(name: string): Type =
|
|||
return Type(kind: TypeKind.Const, value: "any".toIntrinsic(), intrinsic: true)
|
||||
of "ref":
|
||||
return Type(kind: Reference, value: "any".toIntrinsic(), intrinsic: true)
|
||||
else:
|
||||
raise newException(ValueError, &"invalid intrinsic '{name}'")
|
||||
|
||||
|
||||
proc infer(self: TypeChecker, node: LiteralExpr): TypedExpr =
|
||||
|
@ -428,6 +432,8 @@ proc isAny(typ: Type): bool =
|
|||
|
||||
|
||||
proc compare(self: TypeChecker, a, b: Type): bool =
|
||||
## Compares two types and returns whether they are
|
||||
## compatible with each other
|
||||
if a.isAny() or b.isAny():
|
||||
return true
|
||||
if a.kind == b.kind:
|
||||
|
@ -527,7 +533,7 @@ proc compare(self: TypeChecker, a, b: Name): bool =
|
|||
## Compares two names. In addition to checking
|
||||
## that their types match, this function makes
|
||||
## sure the two given objects actually come from
|
||||
## the same module (and so are literally the same
|
||||
## the same module (and so are represent *the same*
|
||||
## exact object)
|
||||
if not self.compare(a.valueType, b.valueType):
|
||||
return false
|
||||
|
@ -535,6 +541,7 @@ proc compare(self: TypeChecker, a, b: Name): bool =
|
|||
|
||||
|
||||
proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
|
||||
## Typechecks literal expressions
|
||||
case node.kind:
|
||||
of trueExpr, falseExpr, strExpr, infExpr, nanExpr:
|
||||
result = self.infer(node)
|
||||
|
@ -605,9 +612,11 @@ proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
|
|||
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
|
||||
|
||||
|
||||
proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Name =
|
||||
proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic(), skip: int = 0): Name =
|
||||
## Looks up a name in all scopes starting from the current
|
||||
## one. Optionally matches it to the given type
|
||||
## one. Optionally matches it to the given type. The skip
|
||||
## argument determines how many matching references are
|
||||
## ignored before returning a match (defaults to 0)
|
||||
|
||||
## A note about how namespaces are implemented. At each scope depth lies
|
||||
## a dictionary which maps strings to a list of names. Why a list of names
|
||||
|
@ -616,6 +625,7 @@ proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Na
|
|||
## of re-declaration, though (for example, shadowing a function with a variable in the
|
||||
## same scope and module) because it would just be confusing
|
||||
var depth = self.scopeDepth
|
||||
var skip = skip
|
||||
while depth >= 0:
|
||||
if self.names[depth].hasKey(name):
|
||||
for obj in reversed(self.names[depth][name]):
|
||||
|
@ -636,6 +646,9 @@ proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Na
|
|||
continue
|
||||
# If we got here, we can access the name
|
||||
if self.compare(obj.valueType, kind):
|
||||
if skip > 0:
|
||||
dec(skip)
|
||||
continue
|
||||
result = obj
|
||||
break
|
||||
|
||||
|
@ -654,29 +667,13 @@ proc findOrError(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic
|
|||
proc findAll(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): seq[Name] =
|
||||
## Like find(), but doesn't stop at the first match. Returns
|
||||
## a list of matches
|
||||
var depth = self.scopeDepth
|
||||
while depth >= 0:
|
||||
if self.names[depth].hasKey(name):
|
||||
for obj in self.names[depth][name]:
|
||||
if obj.module.absPath != self.currentModule.absPath:
|
||||
# We don't own this name, but we
|
||||
# may still have access to it
|
||||
if obj.isPrivate or self.currentModule notin obj.exportedTo:
|
||||
# The name is either private in its owner
|
||||
# module, so we definitely can't use it, or
|
||||
# said module has not explicitly exported it
|
||||
# to us. If the name is public but not exported
|
||||
# from its owner module, then we act as if it's
|
||||
# private. This is to avoid namespace pollution
|
||||
# from imports (i.e. if module A imports modules
|
||||
# C and D and module B imports module A, then B
|
||||
# might not want to also have access to C's and D's
|
||||
# names as they might clash with its own stuff)
|
||||
continue
|
||||
# If we got here, we can access the name
|
||||
if self.compare(obj.valueType, kind):
|
||||
result.add(obj)
|
||||
dec(depth)
|
||||
var skip = 0
|
||||
while true:
|
||||
var found = self.find(name, kind, skip)
|
||||
if found.isNil():
|
||||
break
|
||||
result.add(found)
|
||||
inc(skip)
|
||||
|
||||
|
||||
proc inferOrError(self: TypeChecker, node: Expression): TypedExpr =
|
||||
|
@ -690,7 +687,7 @@ proc inferOrError(self: TypeChecker, node: Expression): TypedExpr =
|
|||
|
||||
proc stringify*(self: TypeChecker, typ: Type): string =
|
||||
## Returns the string representation of a
|
||||
## type object
|
||||
## type
|
||||
if typ.isNil():
|
||||
return "void"
|
||||
case typ.kind:
|
||||
|
@ -791,6 +788,8 @@ proc stringify*(self: TypeChecker, typ: Type): string =
|
|||
|
||||
|
||||
proc stringify*(self: TypeChecker, typ: TypedNode): string =
|
||||
## Returns a string represesentation of the given
|
||||
## typed node
|
||||
if typ.node.isConst():
|
||||
return self.stringify(TypedExpr(typ).kind)
|
||||
case typ.node.kind:
|
||||
|
@ -806,29 +805,34 @@ proc stringify*(self: TypeChecker, typ: TypedNode): string =
|
|||
|
||||
proc beginScope(self: TypeChecker) =
|
||||
## Begins a new lexical scope
|
||||
assert self.scopeDepth == self.names.high()
|
||||
inc(self.scopeDepth)
|
||||
self.names.add(newTable[string, seq[Name]]())
|
||||
|
||||
|
||||
proc endScope(self: TypeChecker) =
|
||||
## Closes the current lexical
|
||||
## scope and reverts to the one
|
||||
## scope and reverts to the outer
|
||||
## one
|
||||
discard self.names.pop()
|
||||
dec(self.scopeDepth)
|
||||
assert self.scopeDepth == self.names.high()
|
||||
|
||||
|
||||
func isType(self: Type): bool = self.kind in [Structure, ]
|
||||
proc isTypevar(self: Type): bool =
|
||||
proc isTypevar(self: Type): bool =
|
||||
## Returns whether the given type object
|
||||
## represents a type variable
|
||||
return self.unwrapType() != self
|
||||
|
||||
|
||||
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
|
||||
## Checks the type of the given expression against a known one.
|
||||
## Raises an error if appropriate and returns the typed expression
|
||||
## otherwise. The node is passed in to error() in case of a failure
|
||||
## for more precise error reporting
|
||||
result = term
|
||||
if not self.compare(result, expected):
|
||||
self.error(&"expecting an expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", node=node)
|
||||
self.error(&"expecting an expression of type {self.stringify(expected)}, got {self.stringify(result)} instead", node)
|
||||
if result.isAny() and not expected.isAny():
|
||||
self.error("any is not a valid type in this context", node)
|
||||
|
||||
|
@ -838,7 +842,7 @@ proc check(self: TypeChecker, term: Expression, expected: Type): TypedExpr {.inl
|
|||
## Raises an error if appropriate and returns the typed expression
|
||||
## otherwise
|
||||
result = self.inferOrError(term)
|
||||
self.check(result.kind, expected)
|
||||
self.check(result.kind, expected, term)
|
||||
|
||||
|
||||
proc getTypeDistance(self: TypeChecker, a, b: Type): int =
|
||||
|
@ -853,7 +857,7 @@ proc getTypeDistance(self: TypeChecker, a, b: Type): int =
|
|||
# We already know that the inheritance
|
||||
# chain is correct (i.e. that a is at the
|
||||
# root of it) because we assume a and b are
|
||||
# already compatible, so we really just need
|
||||
# already compatible, so we really just need
|
||||
# to walk the tree backwards and keep track of
|
||||
# how many times we go up one node
|
||||
while not parent.isNil():
|
||||
|
@ -884,9 +888,9 @@ proc calcTypeDistance(self: TypeChecker, typ: Type, sig: TypeSignature): int =
|
|||
|
||||
|
||||
proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool =
|
||||
## Helper for to check type signatures.
|
||||
## Returns true if the given type matches
|
||||
## the given type signature
|
||||
## Helper to check type signatures against a known
|
||||
## one. Returns true if the given type matches the
|
||||
## given type signature
|
||||
case typ.kind:
|
||||
of TypeKind.Function:
|
||||
if typ.parameters.len() < sig.len():
|
||||
|
@ -895,16 +899,15 @@ proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool
|
|||
# can't match to it. This is different from
|
||||
# the code in self.compare() (which checks that
|
||||
# both signatures have the same length) because
|
||||
# of how we handle default arguments; This is to
|
||||
# address a problem that arises that when we have
|
||||
# a function that looks like (a, b, c = someDefault) -> z
|
||||
# and we're looking for a signature (a, b) -> y: if
|
||||
# we enforced that the length of the signatures must
|
||||
# be equal for them to match, we'd consider that function
|
||||
# to not be a good match, even though it is because the
|
||||
# third argument has a default value
|
||||
# of how we handle default arguments; This addresses
|
||||
# a problem that arises that when we have a function
|
||||
# that looks like (a, b, c = someDefault) -> z and we're
|
||||
# looking for a signature (a, b) -> y: if we enforced that
|
||||
# the length of the signatures must be the same, we'd consider
|
||||
# that function to not be a good match, even though it is
|
||||
# because the third argument has a default value
|
||||
return false
|
||||
# We construct a new signature, without the function's default arguments
|
||||
# We construct a new signature, without the default arguments
|
||||
var args: TypeSignature = @[]
|
||||
for argument in typ.parameters:
|
||||
if argument.default.isNil():
|
||||
|
@ -930,7 +933,6 @@ proc checkTypeSignature(self: TypeChecker, typ: Type, sig: TypeSignature): bool
|
|||
var names: HashSet[string]
|
||||
for fieldName in typ.fields.keys():
|
||||
fields.add(typ.fields[fieldName])
|
||||
|
||||
else:
|
||||
self.error(&"cannot check type signature for object of type {self.stringify(typ)}")
|
||||
|
||||
|
@ -940,10 +942,10 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
|
|||
## and returns it. In this context, "type signature" means an ordered list of
|
||||
## tuples (paramName, paramType, paramDefault) that represents the arguments we
|
||||
## want to instantiate a given named object with, be it a function, a type or an
|
||||
## enumeration. The optional node parameter is passed to error() for reporting purposes
|
||||
## in case of failure
|
||||
## enumeration. The optional node parameter is passed to error() for error reporting
|
||||
## purposes in case of failure
|
||||
var
|
||||
impl: seq[Name] = @[]
|
||||
candidates: seq[Name] = @[]
|
||||
matches: seq[Name] = @[]
|
||||
distances: seq[int] = @[]
|
||||
dst: int = 0
|
||||
|
@ -951,10 +953,10 @@ 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.Structure]):
|
||||
if self.checkTypeSignature(n.valueType, sig):
|
||||
impl.add(n)
|
||||
dst = self.calcTypeDistance(n.valueType, sig)
|
||||
for candidate in filterIt(self.findAll(name), it.valueType.kind in [TypeKind.Function, TypeKind.Structure]):
|
||||
if self.checkTypeSignature(candidate.valueType, sig):
|
||||
candidates.add(candidate)
|
||||
dst = self.calcTypeDistance(candidate.valueType, sig)
|
||||
if dst < minDst:
|
||||
minDst = dst
|
||||
distances.add(dst)
|
||||
|
@ -998,15 +1000,15 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
|
|||
# still be an ambiguity error, but at least the compiler isn't completely clueless now. The way type
|
||||
# distance is measured is quite simple: it's simply the relative distance between the two types in their
|
||||
# inheritance tree, hence a value of 0 indicates a perfect match (i.e. they're the EXACT same type, structurally
|
||||
# speaking at least), while larger values indicate "less precise" (or "further") ones
|
||||
for i, n in impl:
|
||||
# speaking at least), while larger values indicate "less precise" ones (you could say they're further apart)
|
||||
for i, candidate in candidates:
|
||||
# Grab all the matches with the smallest type distance
|
||||
if distances[i] == minDst:
|
||||
matches.add(n)
|
||||
matches.add(candidate)
|
||||
case matches.len():
|
||||
of 1:
|
||||
# There's just one match. We're done
|
||||
result = impl[0]
|
||||
result = matches[0]
|
||||
if result.kind == NameKind.Var:
|
||||
# Variables bound to other names must always
|
||||
# have this field set
|
||||
|
@ -1064,7 +1066,7 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
|
|||
msg &= &"multiple matches found for '{name}'"
|
||||
if self.showMismatches:
|
||||
msg &= ":"
|
||||
for fn in reversed(impl):
|
||||
for fn in reversed(candidates):
|
||||
msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.stringify(fn.valueType)}"
|
||||
else:
|
||||
msg &= " (compile with --showMismatches for more details)"
|
||||
|
@ -1091,8 +1093,9 @@ proc replaceGenerics(self: TypeChecker, typ: Type, generics: TableRef[string, Ty
|
|||
|
||||
|
||||
|
||||
proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type =
|
||||
## Instantiates a generic type
|
||||
proc concretize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNode = nil): Type =
|
||||
## Takes in a generic type and its arguments, returning a concrete instantiation
|
||||
## of it
|
||||
let
|
||||
typ = name.valueType.unwrapType()
|
||||
expectedCount = typ.genericTypes.len() + typ.genericValues.len()
|
||||
|
@ -1118,7 +1121,7 @@ proc specialize(self: TypeChecker, name: Name, args: seq[TypedExpr], node: ASTNo
|
|||
|
||||
|
||||
|
||||
proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true) =
|
||||
proc expandTypeConstraints(self: TypeChecker, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true) =
|
||||
## Recursively unpacks a type constraint
|
||||
case condition.kind:
|
||||
of identExpr, genericExpr:
|
||||
|
@ -1131,15 +1134,15 @@ proc unpackTypes(self: TypeChecker, condition: Expression, list: var seq[tuple[m
|
|||
let condition = BinaryExpr(condition)
|
||||
case condition.operator.lexeme:
|
||||
of "|":
|
||||
self.unpackTypes(condition.a, list)
|
||||
self.unpackTypes(condition.b, list)
|
||||
self.expandTypeConstraints(condition.a, list)
|
||||
self.expandTypeConstraints(condition.b, list)
|
||||
else:
|
||||
self.error("invalid type constraint", condition)
|
||||
of unaryExpr:
|
||||
let condition = UnaryExpr(condition)
|
||||
case condition.operator.lexeme:
|
||||
of "~":
|
||||
self.unpackTypes(condition.a, list, accept=false)
|
||||
self.expandTypeConstraints(condition.a, list, accept=false)
|
||||
else:
|
||||
self.error("invalid type constraint in", condition)
|
||||
else:
|
||||
|
@ -1254,7 +1257,9 @@ proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
|
|||
|
||||
|
||||
proc identifier(self: TypeChecker, node: IdentExpr): TypedExpr =
|
||||
## Typechecks name resolution
|
||||
## Typechecks name resolution and returns
|
||||
## a typed expression representing the resolved
|
||||
## name associated to the given identifier
|
||||
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme, node=node))
|
||||
|
||||
|
||||
|
@ -1265,7 +1270,7 @@ proc unary(self: TypeChecker, node: UnaryExpr): TypedUnaryExpr =
|
|||
typeOfA = self.infer(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)])
|
||||
let impl = self.concretize(name, @[self.expression(node.a)])
|
||||
result = newTypedUnaryExpr(node, impl.returnType, typeOfA)
|
||||
|
||||
|
||||
|
@ -1279,7 +1284,7 @@ proc binary(self: TypeChecker, node: BinaryExpr): TypedBinaryExpr =
|
|||
returnType: Type(kind: Any),
|
||||
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)])
|
||||
let impl = self.concretize(name, @[self.expression(node.a)])
|
||||
result = newTypedBinaryExpr(node, impl.returnType, typeOfA, typeOfB)
|
||||
|
||||
|
||||
|
@ -1288,7 +1293,7 @@ proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
|
|||
var args: seq[TypedExpr] = @[]
|
||||
for arg in node.args:
|
||||
args.add(self.expression(arg))
|
||||
result = newTypedExpr(node, self.specialize(self.findOrError(node.ident.token.lexeme), args, node))
|
||||
result = newTypedExpr(node, self.concretize(self.findOrError(node.ident.token.lexeme), args, node))
|
||||
|
||||
|
||||
proc call(self: TypeChecker, node: CallExpr): TypedExpr =
|
||||
|
@ -1311,7 +1316,7 @@ 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 = self.specialize(impl, argExpr)
|
||||
var typ = self.concretize(impl, argExpr)
|
||||
case typ.kind:
|
||||
of Structure:
|
||||
# TODO
|
||||
|
@ -1395,21 +1400,21 @@ proc expression(self: TypeChecker, node: Expression): TypedExpr =
|
|||
result = self.literal(LiteralExpr(node))
|
||||
return
|
||||
case node.kind:
|
||||
of callExpr:
|
||||
of NodeKind.callExpr:
|
||||
result = self.call(CallExpr(node))
|
||||
of identExpr:
|
||||
of NodeKind.identExpr:
|
||||
result = self.identifier(IdentExpr(node))
|
||||
of groupingExpr:
|
||||
of NodeKind.groupingExpr:
|
||||
result = self.expression(GroupingExpr(node).expression)
|
||||
of unaryExpr:
|
||||
of NodeKind.unaryExpr:
|
||||
result = self.unary(UnaryExpr(node))
|
||||
of binaryExpr:
|
||||
of NodeKind.binaryExpr:
|
||||
result = self.binary(BinaryExpr(node))
|
||||
of NodeKind.genericExpr:
|
||||
result = self.genericExpr(GenericExpr(node))
|
||||
of NodeKind.refExpr:
|
||||
result = self.refExpr(Ref(node))
|
||||
of ptrExpr:
|
||||
of NodeKind.ptrExpr:
|
||||
result = self.check(Ref(node).value, "typevar".toIntrinsic())
|
||||
result.kind = result.kind.toPtr()
|
||||
of NodeKind.lentExpr:
|
||||
|
@ -1430,19 +1435,27 @@ proc blockStmt(self: TypeChecker, node: BlockStmt): TypedBlockStmt =
|
|||
result = newTypedBlockStmt(node, body)
|
||||
|
||||
|
||||
proc ensureBoolean(self: TypeChecker, node: Expression): TypedExpr {.inline.} =
|
||||
## Shorthand for checking that a given expression is
|
||||
## of type bool
|
||||
return self.check(node, "bool".toIntrinsic())
|
||||
|
||||
|
||||
proc ifStmt(self: TypeChecker, node: IfStmt): TypedNode =
|
||||
## Typechecks if/else statements
|
||||
|
||||
# Check the condition
|
||||
let condition = self.check(node.condition, "bool".toIntrinsic())
|
||||
# Check the "then" part of "if-then-else"
|
||||
let then = TypedBlockStmt(self.validate(node.thenBranch))
|
||||
# Check the "else" part
|
||||
let otherwise = TypedBlockStmt(self.validate(node.elseBranch))
|
||||
let condition = self.ensureBoolean(node.condition)
|
||||
# Check the body
|
||||
let then = TypedBlockStmt(self.validate(node.thenBranch))
|
||||
var otherwise: TypedBlockStmt = nil
|
||||
if not node.elseBranch.isNil():
|
||||
# Check the else clause, if it exists
|
||||
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,
|
||||
# as it avoids mistakes like the infamous GOTO fail), so the
|
||||
# conversion here is safe
|
||||
# conversions are always safe
|
||||
return newTypedIfStmt(node, then, otherwise, condition)
|
||||
|
||||
|
||||
|
@ -1450,7 +1463,7 @@ proc whileStmt(self: TypeChecker, node: WhileStmt): TypedNode =
|
|||
## Typechecks C-style while loops
|
||||
|
||||
# Check the condition
|
||||
let condition = self.check(node.condition, "bool".toIntrinsic())
|
||||
let condition = self.ensureBoolean(node.condition)
|
||||
# Check the body
|
||||
return newTypedWhileStmt(node, TypedBlockStmt(self.validate(node.body)), condition)
|
||||
|
||||
|
@ -1476,11 +1489,10 @@ proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
|
|||
|
||||
# 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)
|
||||
# like "var x: 1 = 1;". We unwrap it immediately because
|
||||
# we don't actually want the valueType field of the variable
|
||||
# to be a wrapped type, we just do this step as a safety check
|
||||
typ = self.check(node.valueType, "typevar".toIntrinsic()).kind.unwrapType()
|
||||
if typ.isNil():
|
||||
self.error("expecting either a type declaration or an initializer value, but neither was found", node)
|
||||
# Now check that the type of the initializer, if it exists,
|
||||
|
@ -1510,9 +1522,7 @@ proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedFunDecl =
|
|||
# arguments
|
||||
if not node.returnType.isNil():
|
||||
# The function needs a return type too!
|
||||
name.valueType.returnType = self.inferOrError(node.returnType).kind
|
||||
# TODO
|
||||
# name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind
|
||||
name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind.unwrapType()
|
||||
if not name.valueType.isAuto and self.compare(name.valueType.returnType, "auto".toIntrinsic()):
|
||||
name.valueType.isAuto = true
|
||||
if node.body.isNil():
|
||||
|
@ -1546,7 +1556,7 @@ proc declareGenerics(self: TypeChecker, name: Name) =
|
|||
if gen.constr.isNil():
|
||||
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
|
||||
else:
|
||||
self.unpackTypes(gen.constr, constraints)
|
||||
self.expandTypeConstraints(gen.constr, constraints)
|
||||
let generic = Name(kind: Default,
|
||||
ident: gen.ident,
|
||||
module: self.currentModule,
|
||||
|
@ -1565,7 +1575,7 @@ proc declareGenerics(self: TypeChecker, name: Name) =
|
|||
if gen.constr.isNil():
|
||||
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
|
||||
else:
|
||||
self.unpackTypes(gen.constr, constraints)
|
||||
self.expandTypeConstraints(gen.constr, constraints)
|
||||
let generic = Name(kind: Default,
|
||||
ident: gen.ident,
|
||||
module: self.currentModule,
|
||||
|
@ -1622,19 +1632,16 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
|
|||
discard
|
||||
else:
|
||||
case node.value.kind:
|
||||
of identExpr:
|
||||
of NodeKind.identExpr:
|
||||
# Type alias
|
||||
name.valueType = self.inferOrError(node.value).kind
|
||||
of binaryExpr, unaryExpr:
|
||||
of NodeKind.binaryExpr, NodeKind.unaryExpr:
|
||||
# Untagged type union
|
||||
name.valueType = Type(kind: Union, types: @[])
|
||||
self.unpackTypes(node.value, name.valueType.types)
|
||||
self.expandTypeConstraints(node.value, name.valueType.types)
|
||||
else:
|
||||
# Unreachable. Nim should *really* stop enforcing that
|
||||
# all case statements have an else branch (or otherwise
|
||||
# enumerate all cases), at least by default. Maybe a warning
|
||||
# convertible to an error?
|
||||
discard
|
||||
# Unreachable due to how we parse things. Still, better be safe
|
||||
self.error(&"got node of unexpected type {node.value.kind} at typeDecl()")
|
||||
if not node.parent.isNil():
|
||||
# Ensure parent is actually a type
|
||||
var subtype = self.check(node.parent, "typevar".toIntrinsic())
|
||||
|
@ -1669,9 +1676,11 @@ proc validate(self: TypeChecker, node: ASTNode): TypedNode =
|
|||
## Dispatches typeless AST nodes to typecheck them and turn
|
||||
## them into typed ones
|
||||
case node.kind:
|
||||
of binaryExpr, unaryExpr, NodeKind.genericExpr, identExpr,
|
||||
groupingExpr, callExpr, intExpr, floatExpr, octExpr,
|
||||
binExpr, hexExpr, trueExpr, falseExpr, nanExpr, infExpr:
|
||||
of NodeKind.binaryExpr, NodeKind.unaryExpr, NodeKind.genericExpr,
|
||||
NodeKind.identExpr, NodeKind.groupingExpr, NodeKind.callExpr,
|
||||
NodeKind.intExpr, NodeKind.floatExpr, NodeKind.octExpr,
|
||||
NodeKind.binExpr, NodeKind.hexExpr, NodeKind.trueExpr, NodeKind.falseExpr,
|
||||
NodeKind.nanExpr, NodeKind.infExpr:
|
||||
result = self.expression(Expression(node))
|
||||
of exprStmt:
|
||||
let statement = ExprStmt(node)
|
||||
|
@ -1729,7 +1738,7 @@ proc validate*(self: TypeChecker, tree: ParseTree, file, source: string, showMis
|
|||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
valueType: Type(kind: Function,
|
||||
returnType: "nil".toIntrinsic(),
|
||||
returnType: nil,
|
||||
parameters: @[],
|
||||
),
|
||||
ident: newIdentExpr(Token(lexeme: "", kind: Identifier)),
|
||||
|
|
|
@ -407,6 +407,8 @@ proc primary(self: Parser): Expression =
|
|||
result = newBinExpr(self.step())
|
||||
of String:
|
||||
result = newStrExpr(self.step())
|
||||
of Char:
|
||||
result = newCharExpr(self.step())
|
||||
of TokenType.Inf:
|
||||
result = newInfExpr(self.step())
|
||||
of TokenType.Nan:
|
||||
|
@ -931,6 +933,8 @@ proc parseDeclParams(self: Parser, parameters: Parameters) =
|
|||
valueType: Expression
|
||||
default: Expression
|
||||
metDefaults = false
|
||||
i = 0
|
||||
params: seq[Parameter] = @[]
|
||||
while not self.check(RightParen):
|
||||
if parameters.len() > 255:
|
||||
self.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
|
||||
|
@ -939,6 +943,14 @@ proc parseDeclParams(self: Parser, parameters: Parameters) =
|
|||
ident.file = self.file
|
||||
if self.match(":"):
|
||||
valueType = self.expression()
|
||||
# This makes it so that a, b: int becomes a: int, b: int
|
||||
var n = 0
|
||||
while n < i:
|
||||
if params[n].valueType.isNil():
|
||||
params[n].valueType = valueType
|
||||
else:
|
||||
break
|
||||
inc(n)
|
||||
if ident.token.lexeme in parameters:
|
||||
self.error("duplicate parameter name in function declaration is not allowed", ident.token)
|
||||
if self.match("="):
|
||||
|
@ -949,8 +961,10 @@ proc parseDeclParams(self: Parser, parameters: Parameters) =
|
|||
if default.isNil() and metDefaults:
|
||||
self.error("positional argument cannot follow default argument in function declaration", ident.token)
|
||||
parameters[ident.token.lexeme] = Parameter(ident: ident, valueType: valueType, default: default)
|
||||
params.add(parameters[ident.token.lexeme])
|
||||
if not self.match(Comma):
|
||||
break
|
||||
inc(i)
|
||||
self.expect(RightParen)
|
||||
for parameter in parameters.values():
|
||||
if parameter.valueType.isNil():
|
||||
|
@ -1026,12 +1040,13 @@ proc parseGenerics(self: Parser, decl: Declaration) =
|
|||
|
||||
proc funDecl(self: Parser, isOperator: bool = false): FunDecl =
|
||||
## Parses named function declarations
|
||||
let tok = self.peek(-1)
|
||||
self.expect(Identifier, "expecting function name")
|
||||
let name = self.peek(-1)
|
||||
var
|
||||
parameters: Parameters = newTable[string, Parameter]()
|
||||
returnType: Expression
|
||||
pragmas: seq[Pragma] = @[]
|
||||
function = newFunDecl(newIdentExpr(self.peek(-1)), parameters, nil, true, tok, pragmas, returnType)
|
||||
function = newFunDecl(newIdentExpr(name), parameters, nil, true, name, pragmas, returnType)
|
||||
function.file = self.file
|
||||
if self.match("*"):
|
||||
function.isPrivate = true
|
||||
|
@ -1072,10 +1087,8 @@ proc funDecl(self: Parser, isOperator: bool = false): FunDecl =
|
|||
if isOperator:
|
||||
if parameters.len() == 0:
|
||||
self.error("cannot declare operator without arguments")
|
||||
for name in parameters.keys():
|
||||
let parameter = parameters[name]
|
||||
if parameter.valueType == nil:
|
||||
self.error(&"missing type declaration for '{name}' in function declaration")
|
||||
if function.returnType.isNil():
|
||||
self.error("cannot declare void operator")
|
||||
result.file = self.file
|
||||
|
||||
|
||||
|
|
15
src/peon.nim
15
src/peon.nim
|
@ -66,7 +66,8 @@ proc `$`(self: TypedNode): string =
|
|||
result = &"{self.node}: ? ({self.node.kind})"
|
||||
|
||||
|
||||
proc runFile(filename: string, fromString: bool = false, dump: bool = true, generate: bool = true, breakpoints: seq[uint64] = @[],
|
||||
proc runFile(filename: string, fromString: bool = false, dump: bool = true, generate: bool = true,
|
||||
parse: bool = true, typeCheck: bool = true, breakpoints: seq[uint64] = @[],
|
||||
disabledWarnings: seq[WarningKind] = @[], mismatches: bool = false, run: bool = true,
|
||||
backend: PeonBackend = PeonBackend.Bytecode, output: string, cacheDir: string) =
|
||||
var
|
||||
|
@ -101,6 +102,8 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, gene
|
|||
break
|
||||
styledEcho fgGreen, "\t", $token
|
||||
echo ""
|
||||
if not parse:
|
||||
return
|
||||
tree = parser.parse(tokens, filename, tokenizer.getLines(), input)
|
||||
if tree.len() == 0:
|
||||
return
|
||||
|
@ -109,6 +112,8 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, gene
|
|||
for node in tree:
|
||||
styledEcho fgGreen, "\t", $node
|
||||
echo ""
|
||||
if not typeCheck:
|
||||
return
|
||||
typedNodes = typeChecker.validate(tree, filename, tokenizer.getSource(), mismatches, disabledWarnings)
|
||||
if debugTypeChecker:
|
||||
styledEcho fgCyan, "Typechecker output:"
|
||||
|
@ -281,6 +286,8 @@ when isMainModule:
|
|||
#mode: CompileMode = CompileMode.Debug
|
||||
run = true
|
||||
generateCode = true
|
||||
parseCode = true
|
||||
typeCheck = true
|
||||
backend: PeonBackend
|
||||
output: string
|
||||
breakpoints: seq[uint64]
|
||||
|
@ -311,6 +318,10 @@ when isMainModule:
|
|||
fromString = true
|
||||
of "noGen":
|
||||
generateCode = false
|
||||
of "noTC", "noTypeCheck":
|
||||
typeCheck = false
|
||||
of "noParse":
|
||||
parseCode = false
|
||||
of "noDump":
|
||||
dump = false
|
||||
of "warnings":
|
||||
|
@ -410,4 +421,4 @@ when isMainModule:
|
|||
echo "Sorry, the REPL is broken :("
|
||||
# repl(warnings, showMismatches, backend, dump)
|
||||
else:
|
||||
runFile(file, fromString, dump, generateCode, breakpoints, warnings, showMismatches, run, backend, output, cachePath)
|
||||
runFile(file, fromString, dump, generateCode, parseCode, typeCheck, breakpoints, warnings, showMismatches, run, backend, output, cachePath)
|
|
@ -28,9 +28,12 @@ import std/strutils
|
|||
import std/strformat
|
||||
|
||||
|
||||
proc formatError*(outFile = stderr, file, line: string, lineNo: int, pos: tuple[start, stop: int], fn: Declaration, msg: string, includeSource = true) =
|
||||
proc formatError*(errKind: string = "", outFile = stderr, file, line: string, lineNo: int, pos: tuple[start, stop: int], fn: Declaration, msg: string, includeSource = true) =
|
||||
## Helper to write a formatted error message to the given file object
|
||||
outFile.styledWrite(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
|
||||
if errKind == "":
|
||||
outFile.styledWrite(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
|
||||
else:
|
||||
outFile.styledWrite(fgRed, styleBright, &"{errKind} error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
|
||||
if not fn.isNil() and fn.kind == funDecl:
|
||||
# Error occurred inside a (named) function
|
||||
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
|
||||
|
@ -55,7 +58,7 @@ proc print*(exc: TypeCheckError, includeSource = true) =
|
|||
of -1: discard
|
||||
of 0: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line]
|
||||
else: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
|
||||
formatError(stderr, file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function, exc.msg, includeSource)
|
||||
formatError("Type", stderr, file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function, exc.msg, includeSource)
|
||||
|
||||
|
||||
proc print*(exc: ParseError, includeSource = true) =
|
||||
|
@ -69,7 +72,7 @@ proc print*(exc: ParseError, includeSource = true) =
|
|||
contents = exc.parser.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
|
||||
else:
|
||||
contents = ""
|
||||
formatError(stderr, file, contents, exc.line, exc.token.relPos, nil, exc.msg, includeSource)
|
||||
formatError("Parsing", stderr, file, contents, exc.line, exc.token.relPos, nil, exc.msg, includeSource)
|
||||
|
||||
|
||||
proc print*(exc: LexingError, includeSource = true) =
|
||||
|
@ -83,5 +86,5 @@ proc print*(exc: LexingError, includeSource = true) =
|
|||
contents = exc.lexer.getSource().splitLines()[exc.line - 1]
|
||||
else:
|
||||
contents = ""
|
||||
formatError(stderr, file, contents, exc.line, exc.pos, nil, exc.msg, includeSource)
|
||||
formatError("Parsing", stderr, file, contents, exc.line, exc.pos, nil, exc.msg, includeSource)
|
||||
|
||||
|
|
Loading…
Reference in New Issue