1942 lines
80 KiB
Nim
1942 lines
80 KiB
Nim
import errors
|
|
import frontend/parsing/parser
|
|
import frontend/parsing/lexer
|
|
|
|
import std/os
|
|
import std/sets
|
|
import std/tables
|
|
import std/terminal
|
|
import std/strutils
|
|
import std/sequtils
|
|
import std/algorithm
|
|
import std/strformat
|
|
import std/parseutils
|
|
|
|
|
|
export ast, errors
|
|
|
|
|
|
|
|
type
|
|
IntegerSize* = enum
|
|
## Integer size enumeration
|
|
Tiny = 8
|
|
Short = 16
|
|
Long = 32
|
|
LongLong = 64
|
|
|
|
FloatSize* = enum
|
|
## Float size enumeration
|
|
Half = 32
|
|
Full = 64
|
|
|
|
TypeKind* = enum
|
|
## Enumeration of compile-time types
|
|
Integer,
|
|
Float,
|
|
String,
|
|
NaN,
|
|
Infinity,
|
|
Boolean,
|
|
Any,
|
|
Typevar,
|
|
Auto,
|
|
Byte,
|
|
Char,
|
|
Nil,
|
|
CustomType,
|
|
Enum,
|
|
Reference,
|
|
Pointer,
|
|
Generic,
|
|
Union,
|
|
Function,
|
|
Lent
|
|
|
|
Type* = ref object
|
|
## A compile-time type
|
|
isBuiltin*: bool
|
|
case kind*: TypeKind
|
|
of Integer:
|
|
signed*: bool
|
|
size*: IntegerSize
|
|
of Float:
|
|
width*: FloatSize
|
|
of Infinity:
|
|
positive*: bool
|
|
of Function:
|
|
isLambda*: bool
|
|
isGenerator*: bool
|
|
isCoroutine*: bool
|
|
isAuto*: bool
|
|
args*: seq[tuple[name: string, kind: Type, default: Expression]]
|
|
returnType*: Type
|
|
builtinOp*: string
|
|
fun*: Declaration
|
|
forwarded*: bool
|
|
compiled*: bool
|
|
genericArgs*: TableRef[string, Type]
|
|
of Typevar:
|
|
wrapped*: Type
|
|
of CustomType:
|
|
typeName*: string
|
|
generics*: TableRef[string, Type]
|
|
fields*: TableRef[string, Type]
|
|
parent*: Type
|
|
interfaces*: seq[Type]
|
|
of Enum:
|
|
members*: seq[Name]
|
|
of Reference, Pointer, Lent:
|
|
value*: Type
|
|
of Generic:
|
|
# cond represents a type constraint. For
|
|
# example, fn foo[T*: int & ~uint](...) {...}
|
|
# would map to [(true, int), (false, uint)]
|
|
cond*: seq[tuple[match: bool, kind: Type, value: Expression]]
|
|
asUnion*: bool # If this is true, the constraint is treated like a type union
|
|
name*: string
|
|
of Union:
|
|
types*: seq[tuple[match: bool, kind: Type, value: Expression]]
|
|
else:
|
|
discard
|
|
|
|
WarningKind* {.pure.} = enum
|
|
## A warning enumeration type
|
|
UnreachableCode, UnusedName, ShadowOuterScope,
|
|
MutateOuterScope
|
|
|
|
CompileError* = ref object of PeonException
|
|
## A compilation error with location information
|
|
node*: ASTNode
|
|
function*: Declaration
|
|
compiler*: PeonCompiler
|
|
|
|
NameKind* {.pure.} = enum
|
|
## A name enumeration type
|
|
None, Module, Argument, Var, Function, CustomType, Enum
|
|
|
|
Name* = ref object
|
|
## A generic name object
|
|
|
|
# Type of the identifier (NOT of the value!)
|
|
case kind*: NameKind
|
|
of Module:
|
|
path*: string
|
|
# Full absolute path of the module,
|
|
# including the extension
|
|
absPath*: string
|
|
# Just for easier lookup, it's all
|
|
# pointers anyway
|
|
names*: TableRef[string, Name]
|
|
of NameKind.Var:
|
|
# If the variable's value is another
|
|
# name, this attribute contains its
|
|
# name object. This is useful for things
|
|
# like assigning functions to variables and
|
|
# then calling the variable like it's the
|
|
# original function
|
|
assignedName*: Name
|
|
else:
|
|
discard
|
|
# The name's identifier
|
|
ident*: IdentExpr
|
|
# Owner of the identifier (module)
|
|
owner*: Name
|
|
# File where the name is declared
|
|
file*: string
|
|
# Scope depth
|
|
depth*: int
|
|
# Is this name private?
|
|
isPrivate*: bool
|
|
# Is this a constant?
|
|
isConst*: bool
|
|
# Can this name's value be mutated?
|
|
isLet*: bool
|
|
# Is this name a generic type?
|
|
isGeneric*: bool
|
|
# The type of the name's associated
|
|
# value
|
|
valueType*: Type
|
|
# The function that owns this name (may be nil!)
|
|
belongsTo*: Name
|
|
# Where is this node declared in its file?
|
|
line*: int
|
|
# Has this name been referenced at least once?
|
|
resolved*: bool
|
|
# The AST node associated with this node. This
|
|
# is needed because we compile 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?
|
|
isReal: bool
|
|
|
|
## Our typed AST representation
|
|
|
|
TypedNode* = ref object of RootObj
|
|
## A generic typed AST node
|
|
node*: ASTNode
|
|
|
|
TypedExpr* = ref object of TypedNode
|
|
## A generic typed expression
|
|
kind*: Type
|
|
|
|
TypedUnaryExpr* = ref object of TypedExpr
|
|
## A generic typed unary expression
|
|
a*: TypedExpr
|
|
|
|
TypedBinaryExpr* = ref object of TypedUnaryExpr
|
|
## A generic typed binary expression
|
|
b*: TypedExpr
|
|
|
|
TypedIdentExpr* = ref object of TypedExpr
|
|
## A typed identifier expression
|
|
name*: Name
|
|
|
|
TypedDecl* = ref object of TypedNode
|
|
## A typed declaration node
|
|
name*: Name # The declaration's name object
|
|
|
|
TypedTypeDecl* = ref object of TypedDecl
|
|
## A typed type declaration node
|
|
fields*: TableRef[string, TypedExpr]
|
|
parent*: Name
|
|
interfaces*: seq[TypedTypeDecl]
|
|
|
|
TypedEnumDecl* = ref object of TypedTypeDecl
|
|
## A typed enum declaration node
|
|
enumeration*: Type
|
|
variants: seq[TypedTypeDecl]
|
|
|
|
TypedFunDecl* = ref object of TypedDecl
|
|
## A typed function declaration
|
|
args*: seq[tuple[name: Name, default: TypedExpr]]
|
|
body*: TypedBlockStmt
|
|
|
|
TypedStmt* = ref object of TypedNode
|
|
## A typed statement node
|
|
|
|
TypedBlockStmt* = ref object of TypedStmt
|
|
## A typed block statement
|
|
body*: seq[TypedNode]
|
|
|
|
TypedIfStmt* = ref object of TypedStmt
|
|
## A typed if statement node
|
|
thenBranch*: TypedBlockStmt
|
|
elseBranch*: TypedBlockStmt
|
|
condition*: TypedExpr
|
|
|
|
TypedWhileStmt* = ref object of TypedStmt
|
|
## A typed while statement node
|
|
body*: TypedBlockStmt
|
|
condition*: TypedExpr
|
|
|
|
CompilerFunc* = object
|
|
## An internal compiler function called
|
|
## by pragmas
|
|
kind: PragmaKind
|
|
handler: proc (self: PeonCompiler, pragma: Pragma, name: Name)
|
|
|
|
PragmaKind* = enum
|
|
## An enumeration of pragma types
|
|
Immediate,
|
|
Delayed
|
|
|
|
PeonCompiler* = ref object
|
|
## The Peon compiler
|
|
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)
|
|
# These objects are needed to parse other
|
|
# modules
|
|
tokenizer: Lexer
|
|
parser: Parser
|
|
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
|
|
names: seq[TableRef[string, seq[Name]]] # Maps scope depths to namespaces
|
|
# Compiler procedures called by pragmas
|
|
compilerProcs: TableRef[string, CompilerFunc]
|
|
# Show full info about type mismatches when dispatching
|
|
# function calls fails (we hide this under a boolean flag
|
|
# because the output is quite verbose)
|
|
showMismatches: bool
|
|
|
|
|
|
## Forward declarations
|
|
proc compile(self: PeonCompiler, node: ASTNode): TypedNode
|
|
proc toIntrinsic(name: string): Type
|
|
proc done(self: PeonCompiler): bool {.inline.}
|
|
proc wrap(self: Type): Type {.inline.}
|
|
proc unwrap(self: Type): Type {.inline.}
|
|
proc toRef(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 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, typ: Type, args: seq[Expression]): Type {.discardable.}
|
|
proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl
|
|
proc getTypeDistance(self: PeonCompiler, 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 newTypedNode(node: ASTNode): TypedNode =
|
|
## Initializes a new typed node
|
|
new(result)
|
|
result.node = node
|
|
|
|
|
|
proc newTypedExpr(node: Expression, kind: Type): TypedExpr =
|
|
## Initializes a new typed expression
|
|
result = TypedExpr(node: node, kind: kind)
|
|
|
|
|
|
proc newTypedDecl(node: Declaration, name: Name): TypedDecl =
|
|
## Initializes a new typed declaration
|
|
result = TypedDecl(node: node, name: name)
|
|
|
|
|
|
proc newTypedTypeDecl(node: TypeDecl, name: Name, fields: TableRef[string, TypedExpr], parent: Name): TypedTypeDecl =
|
|
## Initializes a new typed function declaration
|
|
result = TypedTypeDecl(node: node, name: name, fields: fields, parent: parent)
|
|
|
|
|
|
proc newTypedEnumDecl(node: TypeDecl, name: Name, variants: seq[TypedTypeDecl], enumeration: Type): TypedEnumDecl =
|
|
## Initializes a new typed function declaration
|
|
result = TypedEnumDecl(node: node, name: name, variants: variants, enumeration: enumeration)
|
|
|
|
|
|
proc newTypedFunDecl(node: FunDecl, name: Name, body: TypedBlockStmt): TypedFunDecl =
|
|
## Initializes a new typed function declaration
|
|
result = TypedFunDecl(node: node, name: name, body: body)
|
|
|
|
|
|
proc newTypedIdentExpr(node: IdentExpr, name: Name): TypedIdentExpr =
|
|
## Initializes a new typed identifier expression
|
|
result = TypedIdentExpr(node: node, name: name, kind: name.valueType)
|
|
|
|
|
|
proc newTypedUnaryExpr(node: UnaryExpr, kind: Type, a: TypedExpr): TypedUnaryExpr =
|
|
## Initializes a new typed unary expression
|
|
result = TypedUnaryExpr(node: node, a: a, kind: kind)
|
|
|
|
|
|
proc newTypedBinaryExpr(node: UnaryExpr, kind: Type, a, b: TypedExpr): TypedBinaryExpr =
|
|
## Initializes a new typed binary expression
|
|
result = TypedBinaryExpr(node: node, a: a, b: b, kind: kind)
|
|
|
|
|
|
proc newTypedBlockStmt(node: BlockStmt, body: seq[TypedNode]): TypedBlockStmt =
|
|
## Initializes a new typed block statement
|
|
result = TypedBlockStmt(node: node, body: body)
|
|
|
|
|
|
proc newTypedWhileStmt(node: WhileStmt, body: TypedBlockStmt, condition: TypedExpr): TypedWhileStmt =
|
|
## Initializes a new typed while statement
|
|
result = TypedWhileStmt(node: node, body: body, condition: condition)
|
|
|
|
|
|
proc newTypedIfStmt(node: IfStmt, thenBranch, elseBranch: TypedBlockStmt, condition: TypedExpr): TypedIfStmt =
|
|
## Initializes a new typed block statement
|
|
result = TypedIfStmt(node: node, thenBranch: thenBranch,
|
|
elseBranch: elseBranch, condition: condition)
|
|
|
|
|
|
proc getName(self: TypedNode): Name =
|
|
## Gets the name object associated with the
|
|
## given typed node, if it has any
|
|
case self.node.kind:
|
|
of identExpr:
|
|
result = TypedIdentExpr(self).name
|
|
of NodeKind.funDecl, varDecl, typeDecl:
|
|
result = TypedDecl(self).name
|
|
else:
|
|
result = nil # TODO
|
|
|
|
|
|
proc newPeonCompiler*: PeonCompiler =
|
|
## Initializes a new compiler instance
|
|
new(result)
|
|
result.current = 0
|
|
result.tree = @[]
|
|
result.scopeDepth = 0
|
|
result.tokenizer = newLexer()
|
|
result.parser = newParser()
|
|
result.source = ""
|
|
result.file = ""
|
|
result.isMainModule = false
|
|
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.showMismatches = false
|
|
|
|
|
|
proc done(self: PeonCompiler): bool {.inline.} = self.current == self.tree.len()
|
|
proc `$`(self: Name): string = $(self[])
|
|
proc `$`(self: Type): string = $(self[])
|
|
|
|
|
|
proc peek(self: PeonCompiler): ASTNode {.inline.} =
|
|
if self.tree.len() == 0:
|
|
return nil
|
|
if self.done():
|
|
return self.tree[^1]
|
|
return self.tree[self.current]
|
|
|
|
|
|
proc step(self: PeonCompiler): ASTNode {.inline.} =
|
|
if self.tree.len() == 0:
|
|
return nil
|
|
if self.done():
|
|
return self.tree[^1]
|
|
result = self.peek()
|
|
inc(self.current)
|
|
|
|
|
|
proc error(self: PeonCompiler, message: string, node: ASTNode = nil) {.inline.} =
|
|
## Raises a CompileError 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)
|
|
|
|
|
|
proc handleMagicPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
|
|
## Handles the "magic" pragma. Assumes the given name is already
|
|
## declared
|
|
if pragma.args.len() != 1:
|
|
self.error(&"'magic' pragma: wrong number of arguments (expected 1, got {len(pragma.args)})")
|
|
elif pragma.args[0].kind != strExpr:
|
|
self.error(&"'magic' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))})")
|
|
elif name.node.kind == NodeKind.funDecl:
|
|
name.valueType.isBuiltin = true
|
|
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
|
|
name.valueType.compiled = true
|
|
elif name.node.kind == NodeKind.typeDecl:
|
|
name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
|
|
if name.valueType.isNil():
|
|
self.error("'magic' pragma: wrong argument value", pragma.args[0])
|
|
if TypeDecl(name.node).isRef:
|
|
name.valueType = name.valueType.toRef()
|
|
name.valueType = name.valueType.wrap()
|
|
name.valueType.isBuiltin = true
|
|
else:
|
|
self.error("'magic' pragma is not valid in this context")
|
|
|
|
|
|
proc handleErrorPragma(self: PeonCompiler, pragma: Pragma, name: Name) =
|
|
## Handles the "error" pragma
|
|
if pragma.args.len() != 1:
|
|
self.error("'error' pragma: wrong number of arguments")
|
|
elif pragma.args[0].kind != strExpr:
|
|
self.error("'error' pragma: wrong type of argument (constant string expected)")
|
|
elif not name.isNil() and name.node.kind != NodeKind.funDecl:
|
|
self.error("'error' pragma is not valid in this context")
|
|
self.error(pragma.args[0].token.lexeme[1..^2])
|
|
|
|
|
|
proc handlePurePragma(self: PeonCompiler, pragma: Pragma, name: Name) =
|
|
## Handles the "pure" pragma
|
|
case name.node.kind:
|
|
of NodeKind.funDecl:
|
|
FunDecl(name.node).isPure = true
|
|
of NodeKind.lambdaExpr:
|
|
LambdaExpr(name.node).isPure = true
|
|
else:
|
|
self.error("'pure' pragma is not valid in this context")
|
|
|
|
|
|
proc warning(self: PeonCompiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) =
|
|
## Raises a warning
|
|
if kind in self.disabledWarnings:
|
|
return
|
|
var node: ASTNode = node
|
|
var fn: Declaration
|
|
if name.isNil():
|
|
if node.isNil():
|
|
node = self.getCurrentNode()
|
|
fn = self.getCurrentFunction()
|
|
else:
|
|
node = name.node
|
|
if node.isNil():
|
|
node = self.getCurrentNode()
|
|
if not name.belongsTo.isNil():
|
|
fn = name.belongsTo.node
|
|
else:
|
|
fn = self.getCurrentFunction()
|
|
var file = self.file
|
|
if not name.isNil():
|
|
file = name.owner.file
|
|
var pos = node.getRelativeBoundaries()
|
|
if file notin ["<string>", ""]:
|
|
file = relativePath(file, getCurrentDir())
|
|
stderr.styledWrite(fgYellow, styleBright, "Warning in ", fgRed, &"{file}:{node.token.line}:{pos.start}")
|
|
if not fn.isNil() and fn.kind == funDecl:
|
|
stderr.styledWrite(fgYellow, styleBright, " in function ", fgRed, FunDecl(fn).name.token.lexeme)
|
|
stderr.styledWriteLine(styleBright, fgDefault, ": ", message)
|
|
try:
|
|
# We try to be as specific as possible with the warning message, pointing to the
|
|
# line it belongs to, but since warnings are not always raised from the source
|
|
# file they're generated in, we take into account the fact that retrieving the
|
|
# exact warning location may fail and bail out silently if it does
|
|
let line = self.source.splitLines()[node.token.line - 1].strip(chars={'\n'})
|
|
stderr.styledWrite(fgYellow, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
|
|
stderr.styledWrite(fgYellow, styleUnderscore, line[pos.start..pos.stop])
|
|
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
|
|
except IndexDefect:
|
|
# Something probably went wrong (wrong line metadata): bad idea to crash!
|
|
stderr.styledwriteLine(resetStyle, fgRed, "Failed to retrieve line information")
|
|
|
|
|
|
proc wrap(self: Type): Type {.inline.} =
|
|
## Wraps a type in a typevar if it's not already
|
|
## wrapped
|
|
if self.kind != Typevar:
|
|
return Type(kind: Typevar, wrapped: self)
|
|
return self
|
|
|
|
|
|
proc unwrap(self: Type): Type {.inline.} =
|
|
## Unwraps a typevar if it's not already
|
|
## unwrapped
|
|
if self.kind == Typevar:
|
|
return self.wrapped
|
|
return self
|
|
|
|
|
|
proc toRef(self: Type): Type {.inline.} =
|
|
## Wraps a type in a ref
|
|
return Type(kind: Reference, value: self, isBuiltin: self.isBuiltin)
|
|
|
|
|
|
proc toPtr(self: Type): Type {.inline} =
|
|
## Wraps a type in a ptr
|
|
return Type(kind: Pointer, value: self, isBuiltin: self.isBuiltin)
|
|
|
|
|
|
proc toLent(self: Type): Type {.inline.} =
|
|
## Turns a type into a lent type
|
|
result = "lent".toIntrinsic()
|
|
result.value = self
|
|
|
|
|
|
proc toIntrinsic(name: string): Type =
|
|
## Converts a string to an intrinsic
|
|
## type if it is valid and returns nil
|
|
## otherwise
|
|
if name == "any":
|
|
return Type(kind: Any, isBuiltin: true)
|
|
elif name == "auto":
|
|
return Type(kind: Auto, isBuiltin: true)
|
|
elif name in ["int", "int64", "i64"]:
|
|
return Type(kind: Integer, size: LongLong, signed: true, isBuiltin: true)
|
|
elif name in ["uint64", "u64", "uint"]:
|
|
return Type(kind: Integer, size: LongLong, signed: false, isBuiltin: true)
|
|
elif name in ["int32", "i32"]:
|
|
return Type(kind: Integer, size: Long, signed: true, isBuiltin: true)
|
|
elif name in ["uint32", "u32"]:
|
|
return Type(kind: Integer, size: Long, signed: false, isBuiltin: true)
|
|
elif name in ["int16", "i16", "short"]:
|
|
return Type(kind: Integer, size: Short, signed: true, isBuiltin: true)
|
|
elif name in ["uint16", "u16"]:
|
|
return Type(kind: Integer, size: Short, signed: false, isBuiltin: true)
|
|
elif name in ["int8", "i8"]:
|
|
return Type(kind: Integer, size: Tiny, signed: true, isBuiltin: true)
|
|
elif name in ["uint8", "u8"]:
|
|
return Type(kind: Integer, size: Tiny, signed: false, isBuiltin: true)
|
|
elif name in ["f64", "float", "float64"]:
|
|
return Type(kind: Float, width: Full, isBuiltin: true)
|
|
elif name in ["f32", "float32"]:
|
|
return Type(kind: Float, width: Half, isBuiltin: true)
|
|
elif name in ["byte", "b"]:
|
|
return Type(kind: Byte, isBuiltin: true)
|
|
elif name in ["char", "c"]:
|
|
return Type(kind: Char, isBuiltin: true)
|
|
elif name == "nan":
|
|
return Type(kind: TypeKind.Nan, isBuiltin: true)
|
|
elif name == "nil":
|
|
return Type(kind: Nil, isBuiltin: true)
|
|
elif name == "inf":
|
|
return Type(kind: Infinity, isBuiltin: true)
|
|
elif name == "bool":
|
|
return Type(kind: Boolean, isBuiltin: true)
|
|
elif name == "typevar":
|
|
return Type(kind: Typevar, isBuiltin: true, wrapped: "any".toIntrinsic())
|
|
elif name == "string":
|
|
return Type(kind: String, isBuiltin: true)
|
|
elif name == "lent":
|
|
return Type(kind: Lent, isBuiltin: true, value: "any".toIntrinsic())
|
|
|
|
|
|
proc infer(self: PeonCompiler, node: LiteralExpr): TypedExpr =
|
|
case node.kind:
|
|
of trueExpr, falseExpr:
|
|
return newTypedExpr(node, "bool".toIntrinsic())
|
|
of strExpr:
|
|
return newTypedExpr(node, "string".toIntrinsic())
|
|
of intExpr, binExpr, octExpr, hexExpr:
|
|
let size = node.token.lexeme.split("'")
|
|
if size.len() == 1:
|
|
return newTypedExpr(node, "int64".toIntrinsic())
|
|
result = newTypedExpr(node, size[1].toIntrinsic())
|
|
if result.isNil():
|
|
self.error(&"invalid type specifier '{size[1]}' for int", node)
|
|
of floatExpr:
|
|
let size = node.token.lexeme.split("'")
|
|
if size.len() == 1:
|
|
return newTypedExpr(node, "float".toIntrinsic())
|
|
result = newTypedExpr(node, size[1].toIntrinsic())
|
|
if result.isNil():
|
|
self.error(&"invalid type specifier '{size[1]}' for float", node)
|
|
else:
|
|
discard
|
|
|
|
|
|
proc infer(self: PeonCompiler, 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 =
|
|
## Compares type unions between each other
|
|
var
|
|
long = a
|
|
short = b
|
|
i = 0
|
|
if b.len() > a.len():
|
|
long = b
|
|
short = a
|
|
for cond1 in short:
|
|
for cond2 in long:
|
|
if not self.compare(cond1.kind, cond2.kind) or cond1.match != cond2.match:
|
|
continue
|
|
inc(i)
|
|
return i >= short.len()
|
|
|
|
|
|
proc matchUnion(self: PeonCompiler, 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
|
|
for constraint in b:
|
|
if self.compare(constraint.kind, a) and constraint.match:
|
|
return true
|
|
return false
|
|
|
|
|
|
proc matchGeneric(self: PeonCompiler, 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
|
|
for constraint in b:
|
|
if not self.compare(constraint.kind, a) or not constraint.match:
|
|
return false
|
|
return true
|
|
|
|
|
|
|
|
proc isAny(typ: Type): bool =
|
|
## Returns true if the given type is
|
|
## of (or contains) the any type. Not
|
|
## applicable to typevars
|
|
case typ.kind:
|
|
of Any:
|
|
return true
|
|
of Generic:
|
|
for condition in typ.cond:
|
|
if condition.kind.isAny():
|
|
return true
|
|
of Union:
|
|
for condition in typ.types:
|
|
if condition.kind.isAny():
|
|
return true
|
|
else:
|
|
return false
|
|
|
|
|
|
proc compare(self: PeonCompiler, a, b: Type): bool =
|
|
if a.isAny() or b.isAny():
|
|
return true
|
|
if a.kind == b.kind:
|
|
case a.kind:
|
|
# TODO: Take interfaces into account
|
|
of CustomType:
|
|
# First, compare their names (only if both types have names)
|
|
if a.typeName != "" and a.typeName != b.typeName:
|
|
return false
|
|
# Then, compare the number of fields
|
|
if a.fields.len() != b.fields.len():
|
|
return false
|
|
# Then the field names
|
|
var fields: HashSet[string] = initHashSet[string]()
|
|
for fieldName in a.fields.keys():
|
|
fields.incl(fieldName)
|
|
for fieldName in b.fields.keys():
|
|
if not fields.contains(fieldName):
|
|
return false
|
|
# From here on out, we know that both a and b have
|
|
# the same number of fields and that they share field
|
|
# names. All we gotta now do is compare the types of the
|
|
# fields
|
|
for fieldName in a.fields.keys():
|
|
if not self.compare(a.fields[fieldName], b.fields[fieldName]):
|
|
return false
|
|
# All checks passed, types are equal as
|
|
# far as the type system is concerned
|
|
return true
|
|
of Boolean, Infinity, Integer, Float, Any,
|
|
Auto, Char, Byte, String, Nil:
|
|
return true
|
|
of Lent, Reference, Pointer:
|
|
return self.compare(a.value, b.value)
|
|
of Enum:
|
|
if a.members.len() != b.members.len():
|
|
return false
|
|
for (memberA, memberB) in zip(a.members, b.members):
|
|
if not self.compare(memberA.valueType, memberB.valueType):
|
|
return false
|
|
return true
|
|
of Union:
|
|
return self.compareUnions(a.types, b.types)
|
|
of Typevar:
|
|
return self.compare(a.wrapped, b.wrapped)
|
|
of Function:
|
|
# Are arities the same?
|
|
if a.args.len() != b.args.len():
|
|
return false
|
|
# Is it a coroutine/generator?
|
|
if a.isCoroutine != b.isCoroutine or a.isGenerator != b.isGenerator:
|
|
return false
|
|
# Do they have compatible return types?
|
|
if not self.compare(b.returnType, a.returnType):
|
|
return false
|
|
# Do they have compatible arguments?
|
|
var i = 0
|
|
for (argA, argB) in zip(a.args, b.args):
|
|
if not self.compare(argA.kind, argB.kind):
|
|
return false
|
|
if argA.name == "":
|
|
continue
|
|
if argB.name == "":
|
|
continue
|
|
if argA.name != argB.name:
|
|
return false
|
|
return true
|
|
else:
|
|
# TODO
|
|
return false
|
|
if a.kind == Union:
|
|
return self.matchUnion(b, a.types)
|
|
if b.kind == Union:
|
|
return self.matchUnion(a, b.types)
|
|
if a.kind == Generic:
|
|
if a.asUnion:
|
|
return self.matchUnion(b, a.cond)
|
|
else:
|
|
return self.matchGeneric(b, a.cond)
|
|
if b.kind == Generic:
|
|
if b.asUnion:
|
|
return self.matchUnion(a, b.cond)
|
|
else:
|
|
return self.matchGeneric(a, b.cond)
|
|
return false
|
|
|
|
|
|
proc literal(self: PeonCompiler, node: LiteralExpr): TypedExpr =
|
|
case node.kind:
|
|
of trueExpr, falseExpr:
|
|
result = self.infer(node)
|
|
of strExpr:
|
|
result = self.infer(node)
|
|
of intExpr:
|
|
let y = IntExpr(node)
|
|
result = self.infer(y)
|
|
if result.kind.kind == Integer:
|
|
var x: int
|
|
try:
|
|
discard parseInt(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("integer value out of range")
|
|
else:
|
|
var x: uint64
|
|
try:
|
|
discard parseBiggestUInt(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("integer value out of range")
|
|
of hexExpr:
|
|
var x: int
|
|
var y = HexExpr(node)
|
|
result = self.infer(y)
|
|
try:
|
|
discard parseHex(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("integer value out of range")
|
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
|
pos: (start: y.token.pos.start,
|
|
stop: y.token.pos.start + len($x)),
|
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
|
)
|
|
)
|
|
of binExpr:
|
|
var x: int
|
|
var y = BinExpr(node)
|
|
result = self.infer(y)
|
|
try:
|
|
discard parseBin(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("integer value out of range")
|
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
|
pos: (start: y.token.pos.start,
|
|
stop: y.token.pos.start + len($x)),
|
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
|
)
|
|
)
|
|
of octExpr:
|
|
var x: int
|
|
var y = OctExpr(node)
|
|
result = self.infer(y)
|
|
try:
|
|
discard parseOct(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("integer value out of range")
|
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
|
pos: (start: y.token.pos.start,
|
|
stop: y.token.pos.start + len($x)),
|
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
|
)
|
|
)
|
|
of floatExpr:
|
|
var x: float
|
|
var y = FloatExpr(node)
|
|
result = self.infer(y)
|
|
try:
|
|
discard parseFloat(y.literal.lexeme, x)
|
|
except ValueError:
|
|
self.error("floating point value out of range")
|
|
else:
|
|
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 =
|
|
## Looks up a name in all scopes starting from the current
|
|
## one. Optionally matches it to the given type
|
|
|
|
## 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
|
|
## rather than a single name? Well, that's to allow cool things like overloading
|
|
## existing functions with different type signatures. We still disallow most cases
|
|
## 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
|
|
while depth >= 0:
|
|
if self.names[depth].hasKey(name):
|
|
for obj in self.names[depth][name]:
|
|
if obj.owner.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
|
|
# in 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 = obj
|
|
result.resolved = true
|
|
break
|
|
if not result.isNil():
|
|
break
|
|
dec(depth)
|
|
|
|
|
|
proc findOrError(self: PeonCompiler, 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] =
|
|
## 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.owner.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
|
|
# in 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)
|
|
|
|
|
|
proc inferOrError(self: PeonCompiler, node: Expression): TypedExpr =
|
|
## Attempts to infer the type of
|
|
## the given expression and raises an
|
|
## error if it fails
|
|
result = self.infer(node)
|
|
if result.isNil():
|
|
self.error("expression has no type", node)
|
|
|
|
|
|
proc stringify*(self: PeonCompiler, typ: Type): string =
|
|
## Returns the string representation of a
|
|
## type object
|
|
case typ.kind:
|
|
of Char, Byte, String, Nil, TypeKind.Nan,
|
|
Auto, Any:
|
|
result &= ($typ.kind).toLowerAscii()
|
|
of CustomType:
|
|
result &= typ.typeName
|
|
if typ.generics.len() > 0:
|
|
result &= "["
|
|
var i = 0
|
|
for gen in typ.generics.keys():
|
|
result &= &"{gen}: {self.stringify(typ.generics[gen])}"
|
|
if i < typ.generics.len() - 1:
|
|
result &= ", "
|
|
inc(i)
|
|
result &= "]"
|
|
of Boolean:
|
|
result = "bool"
|
|
of Infinity:
|
|
result = "inf"
|
|
of Integer:
|
|
if not typ.signed:
|
|
result &= "u"
|
|
result &= &"int{int(typ.size)}"
|
|
of Float:
|
|
result &= "float"
|
|
case typ.width:
|
|
of Half:
|
|
result &= "32"
|
|
of Full:
|
|
result &= "64"
|
|
of Typevar:
|
|
result = &"typevar[{self.stringify(typ.wrapped)}]"
|
|
of Pointer:
|
|
result &= &"ptr {self.stringify(typ.value)}"
|
|
of Reference:
|
|
result &= &"ref {self.stringify(typ.value)}"
|
|
of Function:
|
|
result &= "fn "
|
|
if typ.fun.generics.len() > 0:
|
|
result &= "["
|
|
for i, gen in typ.fun.generics:
|
|
result &= &"{gen.name.token.lexeme}: {self.stringify(typ.genericArgs[gen.name.token.lexeme])}"
|
|
if i < typ.fun.generics.len() - 1:
|
|
result &= ", "
|
|
result &= "]"
|
|
result &= "("
|
|
for i, (argName, argType, argDefault) in typ.args:
|
|
result &= &"{argName}: "
|
|
if argType.kind == Generic:
|
|
result &= argType.name
|
|
else:
|
|
result &= self.stringify(argType)
|
|
if not argDefault.isNil():
|
|
result &= &" = {argDefault}"
|
|
if i < typ.args.len() - 1:
|
|
result &= ", "
|
|
result &= ")"
|
|
if not self.compare(typ.returnType, "nil".toIntrinsic()):
|
|
result &= &": {self.stringify(typ.returnType)}"
|
|
if typ.fun.pragmas.len() > 0:
|
|
result &= " {"
|
|
for i, pragma in typ.fun.pragmas:
|
|
result &= &"{pragma.name.token.lexeme}"
|
|
if pragma.args.len() > 0:
|
|
result &= ": "
|
|
for j, arg in pragma.args:
|
|
result &= arg.token.lexeme
|
|
if j < pragma.args.high():
|
|
result &= ", "
|
|
if i < typ.fun.pragmas.high():
|
|
result &= ", "
|
|
else:
|
|
result &= "}"
|
|
of Lent:
|
|
result &= &"lent {self.stringify(typ.value)}"
|
|
of Union:
|
|
for i, condition in typ.types:
|
|
if i > 0:
|
|
result &= " | "
|
|
if not condition.match:
|
|
result &= "~"
|
|
result &= self.stringify(condition.kind)
|
|
of Generic:
|
|
for i, condition in typ.cond:
|
|
if i > 0:
|
|
result &= " | "
|
|
if not condition.match:
|
|
result &= "~"
|
|
result &= self.stringify(condition.kind)
|
|
else:
|
|
discard # TODO(?)
|
|
|
|
|
|
proc stringify*(self: PeonCompiler, typ: TypedNode): string =
|
|
if typ.node.isConst():
|
|
return self.stringify(TypedExpr(typ).kind)
|
|
case typ.node.kind:
|
|
of NodeKind.funDecl, varDecl, typeDecl:
|
|
result = self.stringify(TypedDecl(typ).name.valueType)
|
|
of binaryExpr, unaryExpr, identExpr, callExpr:
|
|
result = self.stringify(TypedExpr(typ).kind)
|
|
else:
|
|
discard # TODO
|
|
|
|
|
|
proc beginScope(self: PeonCompiler) =
|
|
## Begins a new lexical scope
|
|
inc(self.scopeDepth)
|
|
self.names.add(newTable[string, seq[Name]]())
|
|
|
|
|
|
proc endScope(self: PeonCompiler) =
|
|
## Closes the current lexical
|
|
## scope and reverts to the one
|
|
## above it
|
|
for names in self.names[self.scopeDepth].values():
|
|
for name in names:
|
|
if not name.resolved:
|
|
# We emit warnings for names that are declared but never used
|
|
case name.kind:
|
|
of NameKind.Var:
|
|
# We ignore names that start with an underscore (simply by convention)
|
|
# and of course we ignore public names because we can't know if they have
|
|
# been exported into or imported from other modules and not used yet
|
|
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
|
|
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
|
|
of NameKind.Argument:
|
|
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
|
|
if not name.belongsTo.isNil() and not name.belongsTo.valueType.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved:
|
|
# Builtin functions never use their arguments. We also don't emit this
|
|
# warning if the function was generated internally by the compiler (for
|
|
# example as a result of generic/auto type instantiation) because such
|
|
# objects do not exist in the user's code and are likely duplicated anyway
|
|
self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' of '{name.belongsTo.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
|
|
else:
|
|
discard
|
|
discard self.names.pop()
|
|
dec(self.scopeDepth)
|
|
assert self.scopeDepth == self.names.high()
|
|
|
|
|
|
proc check(self: PeonCompiler, term: Expression, expected: Type): TypedExpr {.inline, discardable.} =
|
|
## Checks the type of the given expression against a known one.
|
|
## Raises an error if appropriate and returns the typed expression
|
|
## otherwise
|
|
result = self.inferOrError(term)
|
|
if not self.compare(result.kind, expected):
|
|
self.error(&"expecting value of type {self.stringify(expected)}, got {self.stringify(result)} instead", term)
|
|
if not result.kind.isAny() and expected.isAny():
|
|
self.error("any is not a valid type in this context", term)
|
|
|
|
|
|
proc getTypeDistance(self: PeonCompiler, 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). The type distance from a
|
|
## to b is defined as their relative distance in their inheritance tree: So
|
|
## for example, from type A to type A it would be 0, from type B (child of A)
|
|
## to type A it would be 1, from type C of B to B it would also be 1 (and it
|
|
## would be 2 from C to A), and so on and so forth
|
|
assert self.compare(a, b)
|
|
if a.kind != CustomType or b.kind != CustomType:
|
|
# TODO: Test
|
|
return 0
|
|
var parent = b.parent
|
|
result = 0
|
|
# 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
|
|
# to walk the tree backwards and keep track of
|
|
# how many times we go up one node. We add some
|
|
# debugging checks for safety tho
|
|
while not parent.isNil():
|
|
when defined(debug):
|
|
if parent.parent.isNil():
|
|
assert self.parent.parent == a
|
|
inc(result)
|
|
parent = parent.parent
|
|
|
|
|
|
|
|
proc match(self: PeonCompiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true, error: bool = true): Name =
|
|
## Tries to find the best matching function implementation
|
|
## compatible with the given type and returns its associated
|
|
## name object
|
|
var
|
|
impl: seq[Name] = self.findAll(name, kind)
|
|
matches: seq[Name] = @[]
|
|
distances: seq[int] = @[]
|
|
dst: int = 0
|
|
minDst: int = dst
|
|
# Compute type distances of all functions and find
|
|
# the smallest one
|
|
for n in impl:
|
|
dst = 0
|
|
# Compute cumulative type distance of the function's
|
|
# type signature
|
|
for (argA, argB) in zip(n.valueType.args, kind.args):
|
|
dst += self.getTypeDistance(argA.kind, argB.kind)
|
|
dst += self.getTypeDistance(n.valueType.returnType, kind.returnType)
|
|
distances.add(dst)
|
|
if dst < minDst:
|
|
minDst = dst
|
|
# Filter implementations by smallest type distance
|
|
for i, n in impl:
|
|
if distances[i] == minDst:
|
|
matches.add(n)
|
|
case matches.len():
|
|
of 0:
|
|
# No matches
|
|
let names = self.findAll(name)
|
|
var msg = &"failed to find a suitable implementation for '{name}'"
|
|
if names.len() > 0:
|
|
msg &= &", found {len(names)} potential candidate"
|
|
if names.len() > 1:
|
|
msg &= "s"
|
|
if self.showMismatches:
|
|
msg &= ":"
|
|
for name in names:
|
|
msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.stringify(name.valueType)}"
|
|
if name.valueType.kind != Function:
|
|
msg &= ": not a callable"
|
|
elif kind.args.len() != name.valueType.args.len():
|
|
msg &= &": wrong number of arguments (expected {name.valueType.args.len()}, got {kind.args.len()})"
|
|
else:
|
|
for i, arg in kind.args:
|
|
if arg.name != "" and name.valueType.args[i].name != "" and arg.name != name.valueType.args[i].name:
|
|
msg &= &": unexpected argument '{arg.name}' at position {i + 1}"
|
|
if not self.compare(arg.kind, name.valueType.args[i].kind):
|
|
msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.valueType.args[i].kind)}, got {self.stringify(arg.kind)})"
|
|
break
|
|
else:
|
|
msg &= " (compile with --showMismatches for more details)"
|
|
else:
|
|
msg = &"call to undefined function '{name}'"
|
|
self.error(msg, node)
|
|
of 1:
|
|
# Match found
|
|
result = impl[0]
|
|
result.resolved = true
|
|
if result.kind == NameKind.Var:
|
|
# Variables bound to other names must always
|
|
# have this field set
|
|
assert not result.assignedName.isNil()
|
|
# We found a function bound to a variable,
|
|
# so we return the original function's name object
|
|
result = result.assignedName
|
|
for (a, b) in zip(result.valueType.args, kind.args):
|
|
if not a.kind.isAny() and b.kind.isAny():
|
|
self.error("any is not a valid type in this context", node)
|
|
if result.valueType.returnType.isAny() and not kind.returnType.isAny():
|
|
self.error("any is not a valid type in this context")
|
|
else:
|
|
# Ambiguity detected
|
|
var msg = &"multiple matching implementations of '{name}' found"
|
|
if self.showMismatches:
|
|
msg &= ":"
|
|
for fn in reversed(impl):
|
|
msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.stringify(fn.valueType)}"
|
|
else:
|
|
msg &= " (compile with --showMismatches for more details)"
|
|
self.error(msg, node)
|
|
|
|
|
|
proc specialize(self: PeonCompiler, typ: Type, args: seq[Expression]): Type =
|
|
## Instantiates a generic type
|
|
var
|
|
mapping: TableRef[string, Type] = newTable[string, Type]()
|
|
kind: Type
|
|
gen: Type
|
|
result = deepCopy(typ)
|
|
case result.kind:
|
|
of TypeKind.Function:
|
|
# This loop checks if a user tries to reassign a generic's
|
|
# name to a different type
|
|
for i, arg in args:
|
|
if i > typ.fun.generics.high():
|
|
gen = typ.genericArgs[typ.fun.generics[^1].name.token.lexeme]
|
|
else:
|
|
gen = typ.genericArgs[typ.fun.generics[i].name.token.lexeme]
|
|
kind = self.inferOrError(arg).kind
|
|
if gen.name in mapping and not self.compare(kind, mapping[gen.name]):
|
|
self.error(&"expecting generic argument '{gen.name}' to be of type {self.stringify(mapping[gen.name])}, got {self.stringify(kind)} instead", args[i])
|
|
mapping[gen.name] = kind
|
|
result.args[i].kind = kind
|
|
for arg in result.args.mitems():
|
|
# We only replace types we know about. This allows
|
|
# us to do partial instantiation
|
|
if arg.kind.kind == Generic and arg.kind.name in mapping:
|
|
arg.kind = mapping[arg.kind.name]
|
|
if not result.returnType.isNil() and result.returnType.kind == Generic:
|
|
if result.returnType.name in mapping:
|
|
result.returnType = mapping[result.returnType.name]
|
|
elif mapping.len() == 0:
|
|
# The function has no generic arguments,
|
|
# just a generic return type
|
|
var typ: Type
|
|
for i, gen in result.fun.generics:
|
|
if gen.name.token.lexeme == result.returnType.name:
|
|
typ = result.args[i].kind
|
|
break
|
|
if typ.isNil():
|
|
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
|
|
result.returnType = typ
|
|
else:
|
|
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
|
|
else:
|
|
discard
|
|
|
|
|
|
proc unpackTypes*(self: PeonCompiler, 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:
|
|
var typ = self.inferOrError(condition).kind
|
|
if not isGeneric and not self.compare(typ, "typevar".toIntrinsic()):
|
|
self.error(&"expecting a type name, got value of type {self.stringify(typ)} instead", condition)
|
|
typ = typ.unwrap()
|
|
if self.compare(typ, "auto".toIntrinsic()):
|
|
self.error("automatic types cannot be used within type constraints", condition)
|
|
list.add((accept, typ, condition))
|
|
of binaryExpr:
|
|
let condition = BinaryExpr(condition)
|
|
case condition.operator.lexeme:
|
|
of "|":
|
|
self.unpackTypes(condition.a, list)
|
|
self.unpackTypes(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)
|
|
else:
|
|
self.error("invalid type constraint in", condition)
|
|
else:
|
|
self.error("invalid type constraint", condition)
|
|
|
|
|
|
proc dispatchPragmas(self: PeonCompiler, name: Name) =
|
|
## Dispatches pragmas bound to objects
|
|
if name.node.isNil():
|
|
return
|
|
var pragmas: seq[Pragma] = @[]
|
|
case name.node.kind:
|
|
of NodeKind.funDecl, NodeKind.typeDecl, NodeKind.varDecl:
|
|
pragmas = Declaration(name.node).pragmas
|
|
of NodeKind.lambdaExpr:
|
|
pragmas = LambdaExpr(name.node).pragmas
|
|
else:
|
|
discard # Unreachable
|
|
var f: CompilerFunc
|
|
for pragma in pragmas:
|
|
if pragma.name.token.lexeme notin self.compilerProcs:
|
|
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
|
|
f = self.compilerProcs[pragma.name.token.lexeme]
|
|
if f.kind != Immediate:
|
|
continue
|
|
f.handler(self, pragma, name)
|
|
|
|
|
|
proc dispatchDelayedPragmas(self: PeonCompiler, 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
|
|
for pragma in pragmas:
|
|
if pragma.name.token.lexeme notin self.compilerProcs:
|
|
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
|
|
f = self.compilerProcs[pragma.name.token.lexeme]
|
|
if f.kind == Immediate:
|
|
continue
|
|
f.handler(self, pragma, name)
|
|
|
|
|
|
proc addName(self: PeonCompiler, name: Name) =
|
|
## Adds a name to the current lexical scope
|
|
var scope = self.names[self.scopeDepth]
|
|
if scope.hasKey(name.ident.token.lexeme):
|
|
for obj in scope[name.ident.token.lexeme]:
|
|
if name.kind == NameKind.Function:
|
|
# We don't check for name clashes for functions because self.match() does that
|
|
continue
|
|
if obj.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum, NameKind.Function] and name.owner == obj.owner:
|
|
self.error(&"re-declaration of '{obj.ident.token.lexeme}' is not allowed (previously declared in {obj.owner.ident.token.lexeme}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", name.node)
|
|
# We emit a bunch of warnings, mostly for QoL
|
|
if obj.owner != name.owner and (obj.isPrivate or name.owner notin obj.exportedTo):
|
|
continue
|
|
if obj.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
|
|
if name.depth < obj.depth:
|
|
self.warning(WarningKind.ShadowOuterScope, &"'{name.ident.token.lexeme}' shadows a name from an outer scope ({obj.owner.file}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", node=obj.ident)
|
|
if name.owner != obj.owner:
|
|
self.warning(WarningKind.ShadowOuterScope, &"'{name.ident.token.lexeme}' shadows a name from an outer module ({obj.owner.file}:{obj.ident.token.line}:{obj.ident.token.relPos.start})", node=obj.ident)
|
|
else:
|
|
scope[name.ident.token.lexeme] = @[]
|
|
scope[name.ident.token.lexeme].add(name)
|
|
|
|
|
|
proc declare(self: PeonCompiler, node: ASTNode): Name {.discardable.} =
|
|
## Declares a name into the current scope
|
|
var scope = self.names[self.scopeDepth]
|
|
var name: Name
|
|
var declaredName: string = ""
|
|
case node.kind:
|
|
of NodeKind.varDecl:
|
|
var node = VarDecl(node)
|
|
declaredName = node.name.token.lexeme
|
|
# Creates a new Name entry so that self.identifier can find it later
|
|
name = Name(depth: self.scopeDepth,
|
|
ident: node.name,
|
|
isPrivate: node.isPrivate,
|
|
owner: self.currentModule,
|
|
file: self.file,
|
|
isConst: node.isConst,
|
|
valueType: "nil".toIntrinsic(), # Done later in varDecl (for better semantics)
|
|
isLet: node.isLet,
|
|
line: node.token.line,
|
|
belongsTo: self.currentFunction,
|
|
kind: NameKind.Var,
|
|
node: node,
|
|
isReal: true
|
|
)
|
|
self.addName(name)
|
|
of NodeKind.funDecl:
|
|
var node = FunDecl(node)
|
|
declaredName = node.name.token.lexeme
|
|
name = Name(depth: self.scopeDepth,
|
|
isPrivate: node.isPrivate,
|
|
isConst: false,
|
|
owner: self.currentModule,
|
|
file: self.file,
|
|
valueType: Type(kind: Function,
|
|
returnType: "nil".toIntrinsic(), # We check it later
|
|
args: @[],
|
|
fun: node,
|
|
forwarded: node.body.isNil(),
|
|
isAuto: false,
|
|
genericArgs: newTable[string, Type]()),
|
|
ident: node.name,
|
|
node: node,
|
|
isLet: false,
|
|
line: node.token.line,
|
|
kind: NameKind.Function,
|
|
belongsTo: self.currentFunction,
|
|
isReal: true,
|
|
)
|
|
if node.generics.len() > 0:
|
|
name.isGeneric = true
|
|
self.addName(name)
|
|
of NodeKind.importStmt:
|
|
var node = ImportStmt(node)
|
|
# We change the name of the module internally so that
|
|
# if you import /path/to/mod, then doing mod.f() will
|
|
# still work without any extra work on our end. Note how
|
|
# we don't change the metadata about the identifier's
|
|
# position so that error messages still highlight the
|
|
# full path
|
|
let path = node.moduleName.token.lexeme
|
|
node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename()
|
|
self.addName(Name(depth: self.scopeDepth,
|
|
owner: self.currentModule,
|
|
file: "", # The file of the module isn't known until it's compiled!
|
|
path: path,
|
|
ident: node.moduleName,
|
|
line: node.moduleName.token.line,
|
|
kind: NameKind.Module,
|
|
isPrivate: false,
|
|
isReal: true
|
|
))
|
|
name = scope[node.moduleName.token.lexeme][^1]
|
|
declaredName = name.ident.token.lexeme
|
|
of NodeKind.typeDecl:
|
|
var node = TypeDecl(node)
|
|
declaredName = node.name.token.lexeme
|
|
var kind = Type(kind: CustomType, typeName: declaredName, generics: newTable[string, Type](), fields: newTable[string, Type](),
|
|
interfaces: @[])
|
|
if node.isRef:
|
|
kind = kind.toRef()
|
|
kind = kind.wrap()
|
|
self.addName(Name(kind: NameKind.CustomType,
|
|
depth: self.scopeDepth,
|
|
owner: self.currentModule,
|
|
node: node,
|
|
ident: node.name,
|
|
line: node.name.token.line,
|
|
#isPrivate: node.isPrivate,
|
|
belongsTo: self.currentFunction,
|
|
valueType: kind,
|
|
isReal: true,
|
|
))
|
|
name = scope[declaredName][^1]
|
|
else:
|
|
discard # TODO: enums
|
|
if not name.isNil():
|
|
self.dispatchPragmas(name)
|
|
return name
|
|
|
|
|
|
proc identifier(self: PeonCompiler, node: IdentExpr): TypedExpr =
|
|
## Compiles name resolution
|
|
return newTypedIdentExpr(node, self.findOrError(node.name.lexeme))
|
|
|
|
|
|
proc unary(self: PeonCompiler, node: UnaryExpr): TypedUnaryExpr =
|
|
## Compiles unary expressions
|
|
var
|
|
default: Expression
|
|
typeOfA = self.infer(node.a)
|
|
let fn = Type(kind: Function,
|
|
returnType: Type(kind: Any),
|
|
args: @[("", typeOfA.kind, default)])
|
|
var name = self.match(node.token.lexeme, fn, node)
|
|
var impl = name.valueType
|
|
if name.isGeneric:
|
|
impl = self.specialize(impl, @[node.a])
|
|
result = newTypedUnaryExpr(node, impl.returnType, typeOfA)
|
|
|
|
|
|
proc binary(self: PeonCompiler, node: BinaryExpr): TypedBinaryExpr =
|
|
## Compiles unary expressions
|
|
var
|
|
default: Expression
|
|
typeOfA = self.infer(node.a)
|
|
typeOfB = self.infer(node.b)
|
|
let fn = Type(kind: Function,
|
|
returnType: Type(kind: Any),
|
|
args: @[("", typeOfA.kind, default), ("", typeOfB.kind, default)])
|
|
var name = self.match(node.token.lexeme, fn, node)
|
|
var impl = name.valueType
|
|
if name.isGeneric:
|
|
impl = self.specialize(impl, @[node.a])
|
|
result = newTypedBinaryExpr(node, impl.returnType, typeOfA, typeOfB)
|
|
|
|
|
|
proc genericExpr(self: PeonCompiler, node: GenericExpr): TypedExpr =
|
|
## Compiles generic instantiation
|
|
var name = self.findOrError(node.ident.token.lexeme)
|
|
if not name.isGeneric:
|
|
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
|
|
var fun = FunDecl(name.node)
|
|
if fun.generics.len() != node.args.len():
|
|
self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()} instead)")
|
|
var concrete = deepCopy(name.valueType)
|
|
var types: seq[Type] = @[]
|
|
var map = newTable[string, Type]()
|
|
for arg in node.args:
|
|
types.add(self.inferOrError(arg).kind)
|
|
if types[^1].kind != Typevar:
|
|
self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])} instead", arg)
|
|
for (gen, value) in zip(fun.generics, node.args):
|
|
map[gen.name.token.lexeme] = self.inferOrError(value).kind
|
|
for i, argument in concrete.args:
|
|
if argument.kind.kind != Generic:
|
|
continue
|
|
elif argument.name in map:
|
|
concrete.args[i].kind = map[argument.name]
|
|
else:
|
|
self.error(&"unknown generic argument name '{argument.name}'", concrete.fun)
|
|
if not concrete.returnType.isNil() and concrete.returnType.kind == Generic:
|
|
if concrete.returnType.name in map:
|
|
concrete.returnType = map[concrete.returnType.name]
|
|
else:
|
|
self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun)
|
|
result = newTypedExpr(node, concrete)
|
|
|
|
|
|
proc call(self: PeonCompiler, node: CallExpr): TypedExpr =
|
|
## Compiles function calls
|
|
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
|
|
var argExpr: seq[Expression] = @[]
|
|
var default: Expression
|
|
var kind: Type
|
|
for i, argument in node.arguments.positionals:
|
|
kind = self.inferOrError(argument).kind
|
|
args.add(("", kind, default))
|
|
argExpr.add(argument)
|
|
for i, argument in node.arguments.keyword:
|
|
kind = self.infer(argument.value).kind
|
|
args.add((argument.name.token.lexeme, kind, default))
|
|
argExpr.add(argument.value)
|
|
case node.callee.kind:
|
|
of NodeKind.identExpr:
|
|
# Calls like hi()
|
|
var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: "any".toIntrinsic(), args: args), node)
|
|
var function = impl.valueType
|
|
if impl.isGeneric:
|
|
function = self.specialize(function, argExpr)
|
|
result = newTypedExpr(node, function.returnType)
|
|
if not impl.valueType.compiled:
|
|
discard self.funDecl(FunDecl(function.fun), impl)
|
|
self.dispatchDelayedPragmas(impl)
|
|
of NodeKind.callExpr:
|
|
# TODO
|
|
# Calling a call expression, like hello()()
|
|
var node: Expression = node
|
|
var all: seq[CallExpr] = @[]
|
|
# Since there can be as many consecutive calls as
|
|
# the user wants, we need to "extract" all of them
|
|
while CallExpr(node).callee.kind == callExpr:
|
|
all.add(CallExpr(CallExpr(node).callee))
|
|
node = CallExpr(node).callee
|
|
# Now that we know how many call expressions we
|
|
# need to compile, we start from the outermost
|
|
# one and work our way to the innermost call
|
|
for exp in all:
|
|
result = self.call(exp)
|
|
of NodeKind.getItemExpr:
|
|
# Calling a.b()
|
|
# TODO
|
|
let node = GetItemExpr(node.callee)
|
|
of NodeKind.lambdaExpr:
|
|
# Calling a lambda on the fly
|
|
var node = LambdaExpr(node.callee)
|
|
# TODO
|
|
of NodeKind.genericExpr:
|
|
# Calling a generic expression
|
|
# TODO
|
|
result = newTypedExpr(node, nil)
|
|
let node = GenericExpr(node.callee)
|
|
let concrete = self.genericExpr(node)
|
|
var impl = self.find(node.ident.token.lexeme).deepCopy()
|
|
impl.valueType = concrete.kind
|
|
result.kind = impl.valueType.returnType
|
|
else:
|
|
let typ = self.infer(node)
|
|
if typ.isNil():
|
|
self.error(&"expression has no type", node)
|
|
else:
|
|
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
|
|
|
|
|
|
proc expression(self: PeonCompiler, node: Expression): TypedExpr =
|
|
## Compiles expressions
|
|
if node.isConst():
|
|
return self.literal(LiteralExpr(node))
|
|
case node.kind:
|
|
of callExpr:
|
|
result = self.call(CallExpr(node))
|
|
of identExpr:
|
|
result = self.identifier(IdentExpr(node))
|
|
of groupingExpr:
|
|
result = self.expression(GroupingExpr(node).expression)
|
|
of unaryExpr:
|
|
result = self.unary(UnaryExpr(node))
|
|
of binaryExpr:
|
|
result = self.binary(BinaryExpr(node))
|
|
of NodeKind.genericExpr:
|
|
result = self.genericExpr(GenericExpr(node))
|
|
of refExpr:
|
|
result = self.check(Ref(node).value, "typevar".toIntrinsic())
|
|
result.kind = result.kind.toRef()
|
|
result.kind = result.kind.wrap()
|
|
of ptrExpr:
|
|
result = self.check(Ref(node).value, "typevar".toIntrinsic())
|
|
result.kind = result.kind.toPtr()
|
|
result.kind = result.kind.wrap()
|
|
of lentExpr:
|
|
result = self.check(ast.Lent(node).value, "typevar".toIntrinsic())
|
|
result.kind = result.kind.toLent()
|
|
result.kind = result.kind.wrap()
|
|
else:
|
|
discard # TODO
|
|
|
|
|
|
proc blockStmt(self: PeonCompiler, node: BlockStmt): TypedBlockStmt =
|
|
## Compiles block statements
|
|
self.beginScope()
|
|
var body: seq[TypedNode] = @[]
|
|
for decl in node.code:
|
|
body.add(self.compile(decl))
|
|
self.endScope()
|
|
result = newTypedBlockStmt(node, body)
|
|
|
|
|
|
proc ifStmt(self: PeonCompiler, node: IfStmt): TypedNode =
|
|
## Compiles 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))
|
|
# Compile the "else" part
|
|
let otherwise = TypedBlockStmt(self.compile(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
|
|
|
|
# Compile and check the condition
|
|
let condition = self.check(node.condition, "bool".toIntrinsic())
|
|
return newTypedWhileStmt(node, TypedBlockStmt(self.compile(node.body)), condition)
|
|
|
|
|
|
proc varDecl(self: PeonCompiler, node: VarDecl): TypedDecl =
|
|
## Compiles variable declarations
|
|
var typ: TypedExpr
|
|
var kind: Type
|
|
# Our parser guarantees that the variable declaration
|
|
# will have a type declaration or a value (or both)
|
|
if node.value.isNil():
|
|
# Variable has no value: the type declaration
|
|
# takes over
|
|
typ = self.inferOrError(node.valueType)
|
|
kind = typ.kind
|
|
if self.compare(typ.kind, "auto".toIntrinsic()):
|
|
self.error("automatic types require initialization", node)
|
|
if not self.compare(typ.kind, "typevar".toIntrinsic()):
|
|
self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name)
|
|
kind = kind.unwrap()
|
|
# TODO: Implement T.default()!
|
|
elif node.valueType.isNil():
|
|
# Variable has no type declaration: the type
|
|
# of its value takes over
|
|
typ = self.inferOrError(node.value)
|
|
else:
|
|
# Variable has both a type declaration and
|
|
# a value: the value's type must match the
|
|
# type declaration
|
|
let expected = self.inferOrError(node.valueType)
|
|
if not self.compare(expected.kind, "auto".toIntrinsic()):
|
|
typ = self.check(node.value, expected.kind)
|
|
else:
|
|
# Let the compiler infer the type (this
|
|
# is the default behavior already, but
|
|
# some users may prefer to be explicit!)
|
|
typ = self.inferOrError(node.value)
|
|
var name = self.declare(node)
|
|
name.valueType = kind
|
|
name.assignedName = typ.getName()
|
|
return newTypedDecl(node, name)
|
|
|
|
|
|
proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl =
|
|
## Compiles 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
|
|
if name.isNil():
|
|
name = self.declare(node)
|
|
result = newTypedDecl(node, name)
|
|
var node = node
|
|
# First we declare the function's generics, if it has any
|
|
var
|
|
constraints: seq[tuple[match: bool, kind: Type, value: Expression]] = @[]
|
|
value: Expression
|
|
self.beginScope()
|
|
for gen in node.generics:
|
|
if gen.cond.isNil():
|
|
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
|
|
else:
|
|
self.unpackTypes(gen.cond, constraints)
|
|
self.addName(Name(depth: self.scopeDepth,
|
|
isPrivate: true,
|
|
valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints),
|
|
isLet: false,
|
|
line: node.token.line,
|
|
belongsTo: name,
|
|
ident: gen.name,
|
|
owner: self.currentModule,
|
|
file: self.file,
|
|
isReal: true))
|
|
if name.valueType.genericArgs.hasKey(gen.name.token.lexeme):
|
|
self.error(&"cannot re-declare generic argument '{gen.name.token.lexeme}'", node=gen.name)
|
|
name.valueType.genericArgs[gen.name.token.lexeme] = self.names[self.scopeDepth][gen.name.token.lexeme][^1].valueType
|
|
constraints = @[]
|
|
# We now declare and typecheck the function's
|
|
# arguments
|
|
var default: Expression
|
|
var i = 0
|
|
var typ: Type
|
|
var arg: Name
|
|
for argument in node.arguments:
|
|
typ = self.inferOrError(argument.valueType).kind
|
|
if typ.kind != Generic:
|
|
typ = self.check(argument.valueType, "typevar".toIntrinsic()).kind.unwrap()
|
|
if self.compare(typ, "auto".toIntrinsic()):
|
|
name.valueType.isAuto = true
|
|
arg = Name(depth: self.scopeDepth,
|
|
isPrivate: true,
|
|
owner: name.owner,
|
|
file: name.file,
|
|
isConst: false,
|
|
ident: argument.name,
|
|
valueType: typ,
|
|
isLet: false,
|
|
line: argument.name.token.line,
|
|
belongsTo: name,
|
|
kind: NameKind.Argument,
|
|
node: argument.name,
|
|
isReal: true)
|
|
if node.arguments.high() - node.defaults.high() <= node.arguments.high():
|
|
# There's a default argument!
|
|
name.valueType.args.add((arg.ident.token.lexeme, typ, node.defaults[i]))
|
|
inc(i)
|
|
else:
|
|
# This argument has no default
|
|
name.valueType.args.add((arg.ident.token.lexeme, typ, default))
|
|
self.addName(arg)
|
|
if not node.returnType.isNil():
|
|
# The function needs a return type too!
|
|
name.valueType.returnType = self.inferOrError(node.returnType).kind
|
|
if name.valueType.returnType.kind != Generic:
|
|
name.valueType.returnType = self.check(node.returnType, "typevar".toIntrinsic()).kind.unwrap()
|
|
if self.compare(name.valueType.returnType, "auto".toIntrinsic()):
|
|
name.valueType.isAuto = true
|
|
if node.body.isNil():
|
|
# When we stumble across a forward declaration,
|
|
# we record it for later so we can look it up at
|
|
# the end of the module
|
|
|
|
# TODO
|
|
# self.forwarded.add((name, 0))
|
|
name.valueType.forwarded = true
|
|
self.endScope()
|
|
return
|
|
if name.valueType.isBuiltin:
|
|
# Builtins are handled at call time
|
|
self.endScope()
|
|
return
|
|
# We store the current function to restore
|
|
# it later
|
|
let function = self.currentFunction
|
|
self.currentFunction = name
|
|
name.valueType.compiled = true
|
|
if BlockStmt(node.body).code.len() == 0:
|
|
self.error("cannot declare function with empty body")
|
|
var last: Declaration
|
|
for decl in BlockStmt(node.body).code:
|
|
if not last.isNil() and last.kind == returnStmt:
|
|
self.warning(UnreachableCode, "code after 'return' statement is unreachable", nil, decl)
|
|
discard self.compile(decl)
|
|
last = decl
|
|
self.endScope()
|
|
# Restores the enclosing function (if any).
|
|
# Makes nested calls work (including recursion)
|
|
self.currentFunction = function
|
|
|
|
|
|
|
|
proc typeDecl(self: PeonCompiler, node: TypeDecl, name: Name = nil): TypedTypeDecl =
|
|
## Compiles type declarations
|
|
var name = name
|
|
if name.isNil():
|
|
name = self.declare(node)
|
|
result = newTypedTypeDecl(node, name, newTable[string, TypedExpr](), nil)
|
|
self.beginScope()
|
|
# Declare the type's generics
|
|
var
|
|
constraints: seq[tuple[match: bool, kind: Type, value: Expression]] = @[]
|
|
value: Expression
|
|
for gen in node.generics:
|
|
if gen.cond.isNil():
|
|
constraints = @[(match: true, kind: "any".toIntrinsic(), value: value)]
|
|
else:
|
|
self.unpackTypes(gen.cond, constraints)
|
|
self.addName(Name(depth: self.scopeDepth,
|
|
isPrivate: true,
|
|
valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints),
|
|
isLet: false,
|
|
line: node.token.line,
|
|
belongsTo: name,
|
|
ident: gen.name,
|
|
owner: self.currentModule,
|
|
file: self.file,
|
|
isReal: true))
|
|
if name.valueType.wrapped.generics.hasKey(gen.name.token.lexeme):
|
|
self.error(&"cannot re-declare generic argument '{gen.name.token.lexeme}'", node=gen.name)
|
|
name.valueType.wrapped.generics[gen.name.token.lexeme] = self.names[self.scopeDepth][gen.name.token.lexeme][^1].valueType
|
|
constraints = @[]
|
|
if node.value.isNil():
|
|
# Type is not a type union nor type alias
|
|
if not node.isEnum:
|
|
# Type has fields
|
|
var fieldType: TypedExpr
|
|
var n: Name
|
|
for field in node.fields:
|
|
fieldType = self.infer(field.valueType)
|
|
if not node.isRef:
|
|
# Check for self-recursion of non-ref (aka managed pointer) types (which would require
|
|
# infinite memory)
|
|
n = fieldType.getName()
|
|
if n.isNil():
|
|
# Expression has no associated name: cannot self-recurse
|
|
continue
|
|
if name == n:
|
|
self.error(&"illegal self-recursion in member '{field.name.token.lexeme}' for non-ref type '{name.ident.token.lexeme}'", fieldType.node)
|
|
result.fields[field.name.token.lexeme] = fieldType
|
|
else:
|
|
# Type is a variant (aka enum). We'll only declare a single
|
|
# object (the enum type itself) so that we don't pollute the
|
|
# global namespace. I don't love unqualified enums, but I use
|
|
# them because I'm lazy and it often leads to problems, so since
|
|
# peon is also meant (among other things) to address my pain points
|
|
# with the languages I'm currentlyusing , I'm going to explicitly
|
|
# forbid myself from using unqualified enum members ever again :)
|
|
var res = newTypedEnumDecl(node, name, @[], Type(kind: Enum, members: @[]))
|
|
for member in node.members:
|
|
res.enumeration.members.add(self.declare(member))
|
|
res.variants.add(self.typeDecl(member, res.enumeration.members[^1]))
|
|
result = res
|
|
else:
|
|
case node.value.kind:
|
|
of identExpr:
|
|
# Type alias
|
|
name.valueType = self.inferOrError(node.value).kind
|
|
of binaryExpr, unaryExpr:
|
|
# Untagged type union
|
|
name.valueType = Type(kind: Union, types: @[])
|
|
self.unpackTypes(node.value, name.valueType.types)
|
|
else:
|
|
# Unreachable
|
|
discard
|
|
if not node.parent.isNil():
|
|
# Ensure parent is actually a type
|
|
var subtype = self.check(node.parent, "typevar".toIntrinsic())
|
|
# Grab its name object
|
|
var parentName = subtype.getName()
|
|
# This should *never* be nil
|
|
if parentName.isNil():
|
|
self.error(&"could not obtain name information for the given object: is this a type?", node.parent)
|
|
result.parent = parentName
|
|
for field in TypeDecl(result.parent.node).fields:
|
|
if result.fields.hasKey(field.name.token.lexeme):
|
|
for f in TypeDecl(result.node).fields:
|
|
if f.name.token.lexeme == field.name.token.lexeme:
|
|
# This always eventually runs
|
|
self.error(&"cannot to re-declare type member '{field}'", f.name)
|
|
result.fields[field.name.token.lexeme] = newTypedExpr(field.name, result.parent.valueType.fields[field.name.token.lexeme])
|
|
|
|
# TODO: Check interfaces
|
|
self.endScope()
|
|
|
|
|
|
proc compile(self: PeonCompiler, node: ASTNode): TypedNode =
|
|
## Dispatches typeless AST nodes to compile them into
|
|
## typed ones
|
|
case node.kind:
|
|
of binaryExpr, unaryExpr, NodeKind.genericExpr, identExpr,
|
|
groupingExpr, callExpr:
|
|
result = self.expression(Expression(node))
|
|
of exprStmt:
|
|
result = self.expression(ExprStmt(node).expression)
|
|
of NodeKind.whileStmt:
|
|
result = self.whileStmt(WhileStmt(node))
|
|
of NodeKind.blockStmt:
|
|
result = self.blockStmt(BlockStmt(node))
|
|
of NodeKind.ifStmt:
|
|
result = self.ifStmt(IfStmt(node))
|
|
of NodeKind.varDecl:
|
|
result = self.varDecl(VarDecl(node))
|
|
of NodeKind.funDecl:
|
|
result = self.funDecl(FunDecl(node))
|
|
of NodeKind.typeDecl:
|
|
result = self.typeDecl(TypeDecl(node))
|
|
else:
|
|
discard
|
|
|
|
|
|
proc compile*(self: PeonCompiler, tree: seq[Declaration], file, source: string, showMismatches: bool = false): seq[TypedNode] =
|
|
## Compiles a sequence of typeless AST nodes
|
|
## into a sequence of typed AST nodes
|
|
self.file = file
|
|
self.source = source
|
|
self.tree = tree
|
|
self.current = 0
|
|
self.scopeDepth = -1
|
|
self.showMismatches = showMismatches
|
|
self.names = @[]
|
|
self.beginScope()
|
|
var mainModule = Name(kind: NameKind.Module,
|
|
depth: 0,
|
|
isPrivate: true,
|
|
isConst: false,
|
|
isLet: false,
|
|
owner: nil,
|
|
file: self.file,
|
|
path: self.file,
|
|
ident: newIdentExpr(Token(lexeme: self.file, kind: Identifier)),
|
|
resolved: true,
|
|
line: 1)
|
|
self.addName(mainModule)
|
|
self.currentModule = mainModule
|
|
# Every peon program has a hidden entry point in
|
|
# which user code is wrapped. Think of it as if
|
|
# peon is implicitly writing the main() function
|
|
# of your program and putting all of your code in
|
|
# there
|
|
var main = Name(depth: 0,
|
|
isPrivate: true,
|
|
isConst: false,
|
|
isLet: false,
|
|
owner: self.currentModule,
|
|
file: self.file,
|
|
valueType: Type(kind: Function,
|
|
returnType: "nil".toIntrinsic(),
|
|
compiled: true,
|
|
args: @[],
|
|
),
|
|
ident: newIdentExpr(Token(lexeme: "", kind: Identifier)),
|
|
kind: NameKind.Function,
|
|
resolved: true,
|
|
line: 1)
|
|
self.addName(main)
|
|
while not self.done():
|
|
result.add(self.compile(self.step()))
|
|
assert self.scopeDepth == 0
|
|
self.endScope()
|
|
assert self.scopeDepth == -1 |