Bug fixes to the parser, typechecker cleanup. Added more CLI options

This commit is contained in:
Mattia Giambirtone 2024-03-05 16:56:09 +01:00
parent e7aa19835e
commit f321644c40
5 changed files with 160 additions and 121 deletions

View File

@ -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

View File

@ -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)),

View File

@ -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

View File

@ -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)

View File

@ -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)