Minor refactoring in preparation for additional modules

This commit is contained in:
Mattia Giambirtone 2023-10-09 10:37:57 +02:00
parent f5d091bb9b
commit 525a11adad
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
5 changed files with 145 additions and 135 deletions

View File

@ -1,3 +1,11 @@
# peon-rewrite
Work in progress for Peon 0.2.x
## What changed
- Peon will no longer use a runtime GC. Instead, the memory model will use lifetimes with regions. Ownership/uniqueness (a la Rust),
might be added in the future, too
- The compiler has been completely overhauled and no longer handles any code generation (in fact, currently there is no code generation
at all, just a parser and a type checker). This is to allow for true multi-backend support as well as to improve separation of concerns

View File

@ -48,8 +48,8 @@ http://www.apache.org/licenses/LICENSE-2.0 for more info.
Basic Usage
-----------
$ peon file.pn Run the given Peon source file
$ peon file.pbc Run the given Peon bytecode file
$ peon [options] file.pn Run the given Peon source file
$ peon [options] file.pbc Run the given Peon bytecode file
Options
@ -58,8 +58,6 @@ Options
-h, --help Show this help text and exit
-v, --version Print the current peon version and exit
-s, --string Execute the passed string as if it was a file
-n, --noDump Don't dump the result of compilation to a file.
Note that no dump is created when using -s/--string
-m, --mode Set the compilation mode. Acceptable values are 'debug' and
'release'. Defaults to 'debug'
-c, --compile Compile the code, but do not execute it. Useful along with -d
@ -72,5 +70,5 @@ Options
-o, --output Rename the output file to this value
--debug-lexer Show the lexer's output
--debug-parser Show the parser's output
--debug-compiler Show the compiler's output
--debug-tc Show the typechecker's output
"""

View File

@ -109,11 +109,13 @@ type
UnreachableCode, UnusedName, ShadowOuterScope,
MutateOuterScope
CompileError* = ref object of PeonException
## A compilation error with location information
TypeCheckError* = ref object of PeonException
## A typechecking error with location information
node*: ASTNode
function*: Declaration
compiler*: PeonCompiler
# The instance of the typechecker that
# raised the error
instance*: TypeChecker
NameKind* {.pure.} = enum
## A name enumeration type
@ -166,14 +168,14 @@ type
# Where is this node declared in its file?
line*: int
# The AST node associated with this node. This
# is needed because we compile function and type
# is needed because we tyoecheck function and type
# declarations only if, and when, they're actually
# used
node*: Declaration
# 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 compiler?
# Is this name generated by user code or internally by the type checker?
isReal: bool
TypeSignature = seq[tuple[name: string, kind: Type, default: Expression]]
@ -247,19 +249,19 @@ type
body*: TypedBlockStmt
condition*: TypedExpr
CompilerFunc* = object
## An internal compiler function called
PragmaFunc* = object
## An internal function called
## by pragmas
kind: PragmaKind
handler: proc (self: PeonCompiler, pragma: Pragma, name: Name)
handler: proc (self: TypeChecker, pragma: Pragma, name: Name)
PragmaKind* = enum
## An enumeration of pragma types
Immediate,
Delayed
PeonCompiler* = ref object
## The Peon compiler
TypeChecker* = ref object
## The Peon type checker
current: int # The current node we're looking at
tree: seq[Declaration] # The AST for the current module
scopeDepth*: int # The current scope depth (0 == global, > 0 == local)
@ -270,12 +272,12 @@ type
source: string # The module's raw source code
file: string # The module's filename
isMainModule: bool # Are we the main module?
currentFunction: Name # The current function we're compiling
currentModule: Name # The current module we're compiling
disabledWarnings: seq[WarningKind] # List of disabled compiler warnings
currentFunction: Name # The current function we're checking
currentModule: Name # The current module we're checking
disabledWarnings: seq[WarningKind] # List of disabled warnings
names: seq[TableRef[string, seq[Name]]] # Maps scope depths to namespaces
# Compiler procedures called by pragmas
compilerProcs: TableRef[string, CompilerFunc]
# Internal procedures called by pragmas
pragmas: TableRef[string, PragmaFunc]
# Show full info about type mismatches when dispatching
# function calls fails (we hide this under a boolean flag
# because the output is quite verbose)
@ -283,33 +285,33 @@ type
## Forward declarations
proc compile(self: PeonCompiler, node: ASTNode): TypedNode
proc validate(self: TypeChecker, node: ASTNode): TypedNode
proc toIntrinsic(name: string): Type
proc done(self: PeonCompiler): bool {.inline.}
proc done(self: TypeChecker): bool {.inline.}
proc wrapType(self: Type): Type {.inline.}
proc unwrapType(self: Type): Type {.inline.}
proc toRef(self: Type): Type {.inline.}
proc toConst(self: Type): Type {.inline.}
proc toPtr(self: Type): Type {.inline.}
proc toLent(self: Type): Type {.inline.}
proc handleErrorPragma(self: PeonCompiler, pragma: Pragma, name: Name)
proc handlePurePragma(self: PeonCompiler, pragma: Pragma, name: Name)
proc handleMagicPragma(self: PeonCompiler, pragma: Pragma, name: Name)
proc handleNullablePragma(self: PeonCompiler, pragma: Pragma, name: Name)
proc warning(self: PeonCompiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil)
proc stringify*(self: PeonCompiler, typ: Type): string
proc stringify*(self: PeonCompiler, typ: TypedNode): string
proc inferOrError*(self: PeonCompiler, node: Expression): TypedExpr
proc compare(self: PeonCompiler, a, b: Type): bool
proc expression(self: PeonCompiler, node: Expression): TypedExpr
proc specialize(self: PeonCompiler, name: Name, args: seq[Expression]): Type
proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl
proc getTypeDistance(self: PeonCompiler, a, b: Type): int
proc handleErrorPragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name)
proc handleNullablePragma(self: TypeChecker, pragma: Pragma, name: Name)
proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil)
proc stringify*(self: TypeChecker, typ: Type): string
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[Expression]): Type
proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedDecl
proc getTypeDistance(self: TypeChecker, a, b: Type): int
## Public getters for nicer error formatting
proc getCurrentNode*(self: PeonCompiler): ASTNode = (if self.done(): self.tree[^1] else: self.tree[self.current - 1])
proc getCurrentFunction*(self: PeonCompiler): Declaration {.inline.} = (if self.currentFunction.isNil(): nil else: self.currentFunction.node)
proc getSource*(self: PeonCompiler): string {.inline.} = self.source
proc getCurrentNode*(self: TypeChecker): ASTNode = (if self.done(): self.tree[^1] else: self.tree[self.current - 1])
proc getCurrentFunction*(self: TypeChecker): Declaration {.inline.} = (if self.currentFunction.isNil(): nil else: self.currentFunction.node)
proc getSource*(self: TypeChecker): string {.inline.} = self.source
proc newTypedNode(node: ASTNode): TypedNode =
@ -398,7 +400,7 @@ proc getName(self: TypedNode): Name =
result = nil # TODO
proc newPeonCompiler*: PeonCompiler =
proc newTypeChecker*: TypeChecker =
## Initializes a new compiler instance
new(result)
result.current = 0
@ -412,20 +414,20 @@ proc newPeonCompiler*: PeonCompiler =
result.currentFunction = nil
result.disabledWarnings = @[]
result.names = @[]
result.compilerProcs = newTable[string, CompilerFunc]()
result.compilerProcs["magic"] = CompilerFunc(kind: Immediate, handler: handleMagicPragma)
result.compilerProcs["pure"] = CompilerFunc(kind: Immediate, handler: handlePurePragma)
result.compilerProcs["error"] = CompilerFunc(kind: Delayed, handler: handleErrorPragma)
result.compilerProcs["nullable"] = CompilerFunc(kind: Immediate, handler: handleNullablePragma)
result.pragmas = newTable[string, PragmaFunc]()
result.pragmas["magic"] = PragmaFunc(kind: Immediate, handler: handleMagicPragma)
result.pragmas["pure"] = PragmaFunc(kind: Immediate, handler: handlePurePragma)
result.pragmas["error"] = PragmaFunc(kind: Delayed, handler: handleErrorPragma)
result.pragmas["nullable"] = PragmaFunc(kind: Immediate, handler: handleNullablePragma)
result.showMismatches = false
proc done(self: PeonCompiler): bool {.inline.} = self.current == self.tree.len()
proc done(self: TypeChecker): bool {.inline.} = self.current == self.tree.len()
proc `$`(self: Name): string = $(self[])
proc `$`(self: Type): string = $(self[])
proc peek(self: PeonCompiler): ASTNode {.inline.} =
proc peek(self: TypeChecker): ASTNode {.inline.} =
if self.tree.len() == 0:
return nil
if self.done():
@ -433,7 +435,7 @@ proc peek(self: PeonCompiler): ASTNode {.inline.} =
return self.tree[self.current]
proc step(self: PeonCompiler): ASTNode {.inline.} =
proc step(self: TypeChecker): ASTNode {.inline.} =
if self.tree.len() == 0:
return nil
if self.done():
@ -442,13 +444,13 @@ proc step(self: PeonCompiler): ASTNode {.inline.} =
inc(self.current)
proc error(self: PeonCompiler, message: string, node: ASTNode = nil) {.inline.} =
## Raises a CompileError exception
proc error(self: TypeChecker, message: string, node: ASTNode = nil) {.inline.} =
## Raises a TypeCheckError exception
let node = if node.isNil(): self.getCurrentNode() else: node
raise CompileError(msg: message, node: node, line: node.token.line, file: node.file, compiler: self)
raise TypeCheckError(msg: message, node: node, line: node.token.line, file: node.file, instance: self)
proc handleMagicPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
proc handleMagicPragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "magic" pragma. Assumes the given name is already
## declared
if pragma.args.len() != 1:
@ -470,7 +472,7 @@ proc handleMagicPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
self.error("'magic' pragma is not valid in this context")
proc handleNullablePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
proc handleNullablePragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "nullable" pragma. Assumes the given name is already
## declared
if pragma.args.len() > 0:
@ -483,7 +485,7 @@ proc handleNullablePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
self.error("'nullable' pragma is not valid in this context")
proc handleErrorPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
proc handleErrorPragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "error" pragma
if pragma.args.len() != 1:
self.error("'error' pragma: wrong number of arguments")
@ -494,7 +496,7 @@ proc handleErrorPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
self.error(pragma.args[0].token.lexeme[1..^2])
proc handlePurePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
proc handlePurePragma(self: TypeChecker, pragma: Pragma, name: Name) =
## Handles the "pure" pragma
case name.node.kind:
of NodeKind.funDecl:
@ -505,7 +507,7 @@ proc handlePurePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
self.error("'pure' pragma is not valid in this context")
proc warning(self: PeonCompiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
proc warning(self: TypeChecker, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
## Raises a warning
if kind in self.disabledWarnings:
return
@ -640,7 +642,7 @@ proc toIntrinsic(name: string): Type =
proc infer(self: PeonCompiler, node: LiteralExpr): TypedExpr =
proc infer(self: TypeChecker, node: LiteralExpr): TypedExpr =
case node.kind:
of trueExpr, falseExpr:
return newTypedExpr(node, "bool".toIntrinsic())
@ -664,13 +666,13 @@ proc infer(self: PeonCompiler, node: LiteralExpr): TypedExpr =
discard
proc infer(self: PeonCompiler, node: Expression): TypedExpr =
proc infer(self: TypeChecker, node: Expression): TypedExpr =
if node.isConst():
return self.infer(LiteralExpr(node))
result = self.expression(node)
proc compareUnions*(self: PeonCompiler, a, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
proc compareUnions*(self: TypeChecker, a, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
## Compares type unions between each other
var
long = a
@ -687,7 +689,7 @@ proc compareUnions*(self: PeonCompiler, a, b: seq[tuple[match: bool, kind: Type,
return i >= short.len()
proc matchUnion(self: PeonCompiler, a: Type, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
proc matchUnion(self: TypeChecker, a: Type, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
## Returns whether a non-union type a matches
## the given untagged union b
assert a.kind != Union
@ -697,7 +699,7 @@ proc matchUnion(self: PeonCompiler, a: Type, b: seq[tuple[match: bool, kind: Typ
return false
proc matchGeneric(self: PeonCompiler, a: Type, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
proc matchGeneric(self: TypeChecker, a: Type, b: seq[tuple[match: bool, kind: Type, value: Expression]]): bool =
## Returns whether a concrete type matches the
## given generic type b
assert a.kind != Generic
@ -727,7 +729,7 @@ proc isAny(typ: Type): bool =
return false
proc compare(self: PeonCompiler, a, b: Type): bool =
proc compare(self: TypeChecker, a, b: Type): bool =
if a.isAny() or b.isAny():
return true
if a.kind == b.kind:
@ -832,7 +834,7 @@ proc compare(self: PeonCompiler, a, b: Type): bool =
return false
proc literal(self: PeonCompiler, node: LiteralExpr): TypedExpr =
proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
case node.kind:
of trueExpr, falseExpr:
result = self.infer(node)
@ -907,7 +909,7 @@ proc literal(self: PeonCompiler, 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: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): Name =
proc find(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Name =
## Looks up a name in all scopes starting from the current
## one. Optionally matches it to the given type
@ -945,14 +947,14 @@ proc find(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): N
dec(depth)
proc findOrError(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): Name =
proc findOrError(self: TypeChecker, name: string, kind: Type = "any".toIntrinsic()): Name =
## Like find(), but raises an error if the name is not found
result = self.find(name, kind)
if result.isNil():
self.error(&"reference to undefined name '{name}'")
proc findAll(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic()): seq[Name] =
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
@ -980,7 +982,7 @@ proc findAll(self: PeonCompiler, name: string, kind: Type = "any".toIntrinsic())
dec(depth)
proc inferOrError(self: PeonCompiler, node: Expression): TypedExpr =
proc inferOrError(self: TypeChecker, node: Expression): TypedExpr =
## Attempts to infer the type of
## the given expression and raises an
## error if it fails
@ -989,7 +991,7 @@ proc inferOrError(self: PeonCompiler, node: Expression): TypedExpr =
self.error("expression has no type", node)
proc stringify*(self: PeonCompiler, typ: Type): string =
proc stringify*(self: TypeChecker, typ: Type): string =
## Returns the string representation of a
## type object
if typ.isNil():
@ -1089,7 +1091,7 @@ proc stringify*(self: PeonCompiler, typ: Type): string =
discard # TODO(?)
proc stringify*(self: PeonCompiler, typ: TypedNode): string =
proc stringify*(self: TypeChecker, typ: TypedNode): string =
if typ.node.isConst():
return self.stringify(TypedExpr(typ).kind)
case typ.node.kind:
@ -1102,13 +1104,13 @@ proc stringify*(self: PeonCompiler, typ: TypedNode): string =
discard # TODO
proc beginScope(self: PeonCompiler) =
proc beginScope(self: TypeChecker) =
## Begins a new lexical scope
inc(self.scopeDepth)
self.names.add(newTable[string, seq[Name]]())
proc endScope(self: PeonCompiler) =
proc endScope(self: TypeChecker) =
## Closes the current lexical
## scope and reverts to the one
discard self.names.pop()
@ -1116,7 +1118,7 @@ proc endScope(self: PeonCompiler) =
assert self.scopeDepth == self.names.high()
proc check(self: PeonCompiler, term: Expression, expected: Type): TypedExpr {.inline, discardable.} =
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
@ -1127,7 +1129,7 @@ proc check(self: PeonCompiler, term: Expression, expected: Type): TypedExpr {.in
self.error("any is not a valid type in this context", term)
proc check(self: PeonCompiler, term, expected: Type, node: ASTNode = nil): Type {.inline, discardable.} =
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
@ -1137,7 +1139,7 @@ proc check(self: PeonCompiler, term, expected: Type, node: ASTNode = nil): Type
self.error("any is not a valid type in this context", node=node)
proc getTypeDistance(self: PeonCompiler, a, b: Type): int =
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()
@ -1161,7 +1163,7 @@ proc getTypeDistance(self: PeonCompiler, a, b: Type): int =
parent = parent.parent
proc calcTypeDistance(self: PeonCompiler, typ: Type, sig: TypeSignature): int =
proc calcTypeDistance(self: TypeChecker, typ: Type, sig: TypeSignature): int =
## Computes the cumulative type distance between
## the given type and the given type signature. It's
## basically just adding up the type distances of each
@ -1181,7 +1183,7 @@ proc calcTypeDistance(self: PeonCompiler, typ: Type, sig: TypeSignature): int =
self.error(&"cannot compute type distance for object of type {self.stringify(typ)}")
proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: TypeSignature): bool =
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
@ -1227,7 +1229,7 @@ proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: TypeSignature): bool
self.error(&"cannot check type signature for object of type {self.stringify(typ)}")
proc match(self: PeonCompiler, name: string, sig: TypeSignature, node: ASTNode = nil): Name =
proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode = nil): Name =
## Tries to find a matching type for a given (typeName, typeSignature) pair
## and returns it. In this context, "type signature" means an ordered list of
## tuples (paramName, paramType, paramDefault) that represents the arguments we
@ -1357,7 +1359,7 @@ proc match(self: PeonCompiler, name: string, sig: TypeSignature, node: ASTNode =
self.error(msg, node)
proc specialize(self: PeonCompiler, name: Name, args: seq[Expression]): Type =
proc specialize(self: TypeChecker, name: Name, args: seq[Expression]): Type =
## Instantiates a generic type
if not name.isGeneric:
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
@ -1392,7 +1394,7 @@ proc specialize(self: PeonCompiler, name: Name, args: seq[Expression]): Type =
self.error(&"cannot instantiate concrete type for object of type {self.stringify(name.valueType)}")
proc unpackTypes(self: PeonCompiler, 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, isGeneric: bool = false) =
## Recursively unpacks a type constraint
case condition.kind:
of identExpr:
@ -1422,7 +1424,7 @@ proc unpackTypes(self: PeonCompiler, condition: Expression, list: var seq[tuple[
self.error("invalid type constraint", condition)
proc dispatchPragmas(self: PeonCompiler, name: Name) =
proc dispatchPragmas(self: TypeChecker, name: Name) =
## Dispatches pragmas bound to objects
if name.node.isNil():
return
@ -1434,34 +1436,34 @@ proc dispatchPragmas(self: PeonCompiler, name: Name) =
pragmas = LambdaExpr(name.node).pragmas
else:
discard # Unreachable
var f: CompilerFunc
var f: PragmaFunc
for pragma in pragmas:
if pragma.name.token.lexeme notin self.compilerProcs:
if pragma.name.token.lexeme notin self.pragmas:
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
f = self.compilerProcs[pragma.name.token.lexeme]
f = self.pragmas[pragma.name.token.lexeme]
if f.kind != Immediate:
continue
f.handler(self, pragma, name)
proc dispatchDelayedPragmas(self: PeonCompiler, name: Name) {.used.} =
proc dispatchDelayedPragmas(self: TypeChecker, name: Name) {.used.} =
## Dispatches pragmas bound to objects once they
## are used
if name.node.isNil():
return
var pragmas: seq[Pragma] = @[]
pragmas = Declaration(name.node).pragmas
var f: CompilerFunc
var f: PragmaFunc
for pragma in pragmas:
if pragma.name.token.lexeme notin self.compilerProcs:
if pragma.name.token.lexeme notin self.pragmas:
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
f = self.compilerProcs[pragma.name.token.lexeme]
f = self.pragmas[pragma.name.token.lexeme]
if f.kind == Immediate:
continue
f.handler(self, pragma, name)
proc addName(self: PeonCompiler, name: Name) =
proc addName(self: TypeChecker, name: Name) =
## Adds a name to the current lexical scope
var scope = self.names[self.scopeDepth]
if scope.hasKey(name.ident.token.lexeme):
@ -1484,7 +1486,7 @@ proc addName(self: PeonCompiler, name: Name) =
scope[name.ident.token.lexeme].add(name)
proc declare(self: PeonCompiler, node: ASTNode): Name {.discardable.} =
proc declare(self: TypeChecker, node: ASTNode): Name {.discardable.} =
## Declares a name into the current scope
var scope = self.names[self.scopeDepth]
var name: Name
@ -1584,12 +1586,12 @@ proc declare(self: PeonCompiler, node: ASTNode): Name {.discardable.} =
return name
proc identifier(self: PeonCompiler, node: IdentExpr): TypedExpr =
proc identifier(self: TypeChecker, node: IdentExpr): TypedExpr =
## Compiles name resolution
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme))
proc unary(self: PeonCompiler, node: UnaryExpr): TypedUnaryExpr =
proc unary(self: TypeChecker, node: UnaryExpr): TypedUnaryExpr =
## Compiles unary expressions
var
default: Expression
@ -1602,7 +1604,7 @@ proc unary(self: PeonCompiler, node: UnaryExpr): TypedUnaryExpr =
result = newTypedUnaryExpr(node, impl.returnType, typeOfA)
proc binary(self: PeonCompiler, node: BinaryExpr): TypedBinaryExpr =
proc binary(self: TypeChecker, node: BinaryExpr): TypedBinaryExpr =
## Compiles unary expressions
var
default: Expression
@ -1618,12 +1620,12 @@ proc binary(self: PeonCompiler, node: BinaryExpr): TypedBinaryExpr =
result = newTypedBinaryExpr(node, impl.returnType, typeOfA, typeOfB)
proc genericExpr(self: PeonCompiler, node: GenericExpr): TypedExpr =
proc genericExpr(self: TypeChecker, node: GenericExpr): TypedExpr =
## Compiles generic instantiation
result = newTypedExpr(node, self.specialize(self.findOrError(node.ident.token.lexeme), node.args))
proc call(self: PeonCompiler, node: CallExpr): TypedExpr =
proc call(self: TypeChecker, node: CallExpr): TypedExpr =
## Compiles call expressions. This includes
## things like object and enum construction
var args: TypeSignature = @[]
@ -1700,14 +1702,14 @@ proc call(self: PeonCompiler, node: CallExpr): TypedExpr =
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
proc refExpr(self: PeonCompiler, node: Ref): TypedExpr =
proc refExpr(self: TypeChecker, node: Ref): TypedExpr =
## Compiles ref expressions
result = self.check(node.value, "typevar".toIntrinsic())
result.kind = result.kind.toRef()
result.kind = result.kind.wrapType()
proc constExpr(self: PeonCompiler, node: ast.Const): TypedExpr =
proc constExpr(self: TypeChecker, node: ast.Const): TypedExpr =
## Compiles const expressions
var kind = "typevar".toIntrinsic()
kind.wrapped = kind.wrapped.toConst()
@ -1718,7 +1720,7 @@ proc constExpr(self: PeonCompiler, node: ast.Const): TypedExpr =
result.kind = result.kind.wrapType()
proc lentExpr(self: PeonCompiler, node: ast.Lent): TypedExpr =
proc lentExpr(self: TypeChecker, node: ast.Lent): TypedExpr =
## Compiles lent expressions
var kind = "typevar".toIntrinsic()
# Only match references
@ -1765,7 +1767,7 @@ method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true):
]#
proc expression(self: PeonCompiler, node: Expression): TypedExpr =
proc expression(self: TypeChecker, node: Expression): TypedExpr =
## Compiles expressions
if node.isConst():
return self.literal(LiteralExpr(node))
@ -1796,43 +1798,43 @@ proc expression(self: PeonCompiler, node: Expression): TypedExpr =
self.error(&"failed to compile expression of type {node.kind}")
proc blockStmt(self: PeonCompiler, node: BlockStmt): TypedBlockStmt =
proc blockStmt(self: TypeChecker, node: BlockStmt): TypedBlockStmt =
## Compiles block statements
self.beginScope()
var body: seq[TypedNode] = @[]
for decl in node.code:
body.add(self.compile(decl))
body.add(self.validate(decl))
self.endScope()
result = newTypedBlockStmt(node, body)
proc ifStmt(self: PeonCompiler, node: IfStmt): TypedNode =
## Compiles if/else statements for conditional
proc ifStmt(self: TypeChecker, node: IfStmt): TypedNode =
## Typechecks if/else statements for conditional
## execution of code
# Check that the condition is a boolean and record it
let condition = self.check(node.condition, "bool".toIntrinsic())
# Compile the "then" part of "if-then-else"
let then = TypedBlockStmt(self.compile(node.thenBranch))
let then = TypedBlockStmt(self.validate(node.thenBranch))
# Compile the "else" part
let otherwise = TypedBlockStmt(self.compile(node.elseBranch))
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
return newTypedIfStmt(node, then, otherwise, condition)
proc whileStmt(self: PeonCompiler, node: WhileStmt): TypedNode =
## Compiles C-style while loops
proc whileStmt(self: TypeChecker, node: WhileStmt): TypedNode =
## Typechecks C-style while loops
# Compile and check the condition
let condition = self.check(node.condition, "bool".toIntrinsic())
# Compile the body
return newTypedWhileStmt(node, TypedBlockStmt(self.compile(node.body)), condition)
return newTypedWhileStmt(node, TypedBlockStmt(self.validate(node.body)), condition)
proc varDecl(self: PeonCompiler, node: VarDecl): TypedVarDecl =
## Compiles variable declarations
proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
## Typechecks variable declarations
var
name = self.declare(node)
init: TypedExpr
@ -1845,7 +1847,7 @@ proc varDecl(self: PeonCompiler, node: VarDecl): TypedVarDecl =
else:
if node.isConst and not node.value.isConst():
self.error("constant initializer is not a constant", node.value)
init = TypedExpr(self.compile(node.value))
init = TypedExpr(self.validate(node.value))
typ = init.kind
if not node.valueType.isNil():
# Explicit type declaration always takes over
@ -1864,8 +1866,8 @@ proc varDecl(self: PeonCompiler, node: VarDecl): TypedVarDecl =
result = newTypedVarDecl(node, name, init)
proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
## Compiles function declarations
proc funDecl(self: TypeChecker, node: FunDecl, name: Name = nil): TypedDecl =
## Typechecks function declarations
if node.token.kind == Operator and node.name.token.lexeme in [".", ]:
self.error(&"Due to compiler limitations, the '{node.name.token.lexeme}' operator cannot be currently overridden", node.name)
var name = name
@ -1953,7 +1955,7 @@ proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
if BlockStmt(node.body).code.len() == 0:
self.error("cannot declare function with empty body")
for decl in BlockStmt(node.body).code:
discard self.compile(decl)
discard self.validate(decl)
self.endScope()
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
@ -1961,8 +1963,8 @@ proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
proc typeDecl(self: PeonCompiler, node: TypeDecl, name: Name = nil): TypedTypeDecl =
## Compiles type declarations
proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDecl =
## Typechecks type declarations
var name = name
if name.isNil():
name = self.declare(node)
@ -2055,9 +2057,9 @@ proc typeDecl(self: PeonCompiler, node: TypeDecl, name: Name = nil): TypedTypeDe
self.endScope()
proc compile(self: PeonCompiler, node: ASTNode): TypedNode =
## Dispatches typeless AST nodes to compile them into
## typed ones
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,
@ -2078,12 +2080,12 @@ proc compile(self: PeonCompiler, node: ASTNode): TypedNode =
of NodeKind.typeDecl:
result = self.typeDecl(TypeDecl(node))
else:
self.error(&"failed to compile node {node.kind}")
self.error(&"failed to dispatch node of type {node.kind}")
proc compile*(self: PeonCompiler, tree: seq[Declaration], file, source: string, showMismatches: bool = false,
proc validate*(self: TypeChecker, tree: seq[Declaration], file, source: string, showMismatches: bool = false,
disabledWarnings: seq[WarningKind] = @[]): seq[TypedNode] =
## Compiles a sequence of typeless AST nodes
## Transforms a sequence of typeless AST nodes
## into a sequence of typed AST nodes
self.file = file
self.source = source
@ -2126,7 +2128,9 @@ proc compile*(self: PeonCompiler, tree: seq[Declaration], file, source: string,
line: 1)
self.addName(main)
while not self.done():
result.add(self.compile(self.step()))
result.add(self.validate(self.step()))
assert self.scopeDepth == 0
self.endScope()
assert self.scopeDepth == -1
# Do not close the global scope if
# we're being imported
if self.isMainModule:
self.endScope()

View File

@ -17,7 +17,7 @@ import util/fmterr
import util/symbols
import frontend/parsing/lexer
import frontend/parsing/parser
import frontend/compiler/compiler
import frontend/compiler/typechecker
import std/os
import std/parseopt
@ -63,7 +63,7 @@ proc test(warnings: seq[WarningKind] = @[], mismatches: bool = false) =
keep = true
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
compiler = newPeonCompiler()
typeChecker = newTypeChecker()
lexer = newLexer()
parser = newParser()
editor = getLineEditor()
@ -106,17 +106,17 @@ proc test(warnings: seq[WarningKind] = @[], mismatches: bool = false) =
echo ""
if debugCompiler:
styledEcho fgCyan, "Compilation step:"
for typedNode in compiler.compile(parser.parse(lexer.lex(input, "<stdin>"), lexer.getFile(), lexer.getLines(), lexer.getSource()),
for typedNode in typeChecker.validate(parser.parse(lexer.lex(input, "<stdin>"), lexer.getFile(), lexer.getLines(), lexer.getSource()),
lexer.getFile(), lexer.getSource(), showMismatches=mismatches, disabledWarnings=warnings):
if debugCompiler:
styledEcho fgGreen, &"\t{typedNode.node} -> {compiler.stringify(typedNode)}\n"
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
echo ""
except LexingError:
print(LexingError(getCurrentException()))
except ParseError:
print(ParseError(getCurrentException()))
except CompileError:
print(CompileError(getCurrentException()))
except TypeCheckError:
print(TypeCheckError(getCurrentException()))
quit(0)
#[
var
@ -218,7 +218,7 @@ when isMainModule:
else:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'noWarn'")
quit()
of "debug-compiler":
of "debug-tc":
debugCompiler = true
of "compile":
run = false

View File

@ -13,7 +13,7 @@
# limitations under the License.
## Utilities to print formatted error messages to stderr
import frontend/compiler/compiler
import frontend/compiler/typechecker
import frontend/parsing/parser
import frontend/parsing/lexer
import errors
@ -42,15 +42,15 @@ proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], f
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
proc print*(exc: CompileError) =
proc print*(exc: TypeCheckError) =
## Prints a formatted error message
## for compilation errors to stderr
## for type checking errors to stderr
var file = exc.file
var contents = ""
case exc.line:
of -1: discard
of 0: contents = exc.compiler.getSource().strip(chars={'\n'}).splitLines()[exc.line]
else: contents = exc.compiler.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
of 0: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line]
else: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
printError(file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function, exc.msg)