Fixed bug with calling a call

This commit is contained in:
Mattia Giambirtone 2022-07-31 16:09:22 +02:00
parent da2cfefe75
commit 33066d3b9b
8 changed files with 117 additions and 100 deletions

View File

@ -188,7 +188,7 @@ proc varDecl(self: Compiler, node: VarDecl)
proc inferType(self: Compiler, node: LiteralExpr): Type proc inferType(self: Compiler, node: LiteralExpr): Type
proc inferType(self: Compiler, node: Expression): Type proc inferType(self: Compiler, node: Expression): Type
proc findByName(self: Compiler, name: string): seq[Name] proc findByName(self: Compiler, name: string): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[Name]
proc compareTypes(self: Compiler, a, b: Type): bool proc compareTypes(self: Compiler, a, b: Type): bool
proc patchReturnAddress(self: Compiler, pos: int) proc patchReturnAddress(self: Compiler, pos: int)
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode) proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
@ -456,7 +456,8 @@ proc detectClosureVariable(self: Compiler, name: var Name, depth: int = self.sco
for i, b in self.closedOver.high().toTriple(): for i, b in self.closedOver.high().toTriple():
self.chunk.code[name.codePos + i + 1] = b self.chunk.code[name.codePos + i + 1] = b
else: else:
self.error("it is currently not possible to close over function arguments") discard
# self.error("it is currently not possible to close over function arguments")
proc compareTypes(self: Compiler, a, b: Type): bool = proc compareTypes(self: Compiler, a, b: Type): bool =
@ -639,12 +640,12 @@ proc inferType(self: Compiler, node: Expression): Type =
let resolved = self.resolve(IdentExpr(node.callee)) let resolved = self.resolve(IdentExpr(node.callee))
if not resolved.isNil(): if not resolved.isNil():
result = resolved.valueType.returnType result = resolved.valueType.returnType
if result.isNil():
result = Type(kind: Any)
else: else:
result = nil result = nil
of lambdaExpr: of lambdaExpr:
result = self.inferType(LambdaExpr(node.callee).returnType) result = self.inferType(LambdaExpr(node.callee).returnType)
of callExpr:
result = self.inferType(CallExpr(node.callee).callee)
else: else:
discard # Unreachable discard # Unreachable
of varExpr: of varExpr:
@ -719,11 +720,12 @@ proc findByName(self: Compiler, name: string): seq[Name] =
result.add(obj) result.add(obj)
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] = proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[Name] =
## Looks for objects that have already been declared ## Looks for objects that have already been declared
## with the given name and type ## with the given name and type. If depth is not -1,
## it also compares the name's scope depth
for obj in self.findByName(name): for obj in self.findByName(name):
if self.compareTypes(obj.valueType, kind): if self.compareTypes(obj.valueType, kind) and depth == -1 or depth == obj.depth:
result.add(obj) result.add(obj)
#[ #[
@ -751,7 +753,7 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name =
msg &= "s" msg &= "s"
msg &= ": " msg &= ": "
for name in names: for name in names:
msg &= &"\n - '{name.name.token.lexeme}' of type '{self.typeToStr(name.valueType)}'" msg &= &"\n - in module '{name.owner}' at line {name.name.token.line} of type '{self.typeToStr(name.valueType)}'"
if name.valueType.kind != Function: if name.valueType.kind != Function:
msg &= ", not a callable" msg &= ", not a callable"
elif kind.args.len() != name.valueType.args.len(): elif kind.args.len() != name.valueType.args.len():
@ -1004,7 +1006,7 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
self.error(&"unknown built-in: '{fn.valueType.builtinOp}'") self.error(&"unknown built-in: '{fn.valueType.builtinOp}'")
proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) = proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) =
## Small wrapper that abstracts emitting a call instruction ## Small wrapper that abstracts emitting a call instruction
## for a given function ## for a given function
if fn.valueType.isBuiltinFunction: if fn.valueType.isBuiltinFunction:
@ -1013,11 +1015,8 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
# them differently # them differently
self.handleBuiltinFunction(fn, args) self.handleBuiltinFunction(fn, args)
return return
if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic): if not onStack:
# The function has generic arguments! We need to compile a version self.emitFunction(fn)
# of it with the right type data
self.funDecl(nil, fn, args)
self.emitFunction(fn)
self.emitByte(LoadReturnAddress) self.emitByte(LoadReturnAddress)
let pos = self.chunk.code.len() let pos = self.chunk.code.len()
# We initially emit a dummy return # We initially emit a dummy return
@ -1177,7 +1176,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
self.emitConstant(node, self.inferType(node)) self.emitConstant(node, self.inferType(node))
else: else:
self.detectClosureVariable(s) self.detectClosureVariable(s)
if s.valueType.kind == Function: if s.valueType.kind == Function and s.isFunDecl:
# Functions have no runtime # Functions have no runtime
# representation, so we need # representation, so we need
# to create one on the fly # to create one on the fly
@ -1242,24 +1241,29 @@ proc endScope(self: Compiler) =
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
dec(self.scopeDepth) dec(self.scopeDepth)
var names: seq[Name] = @[] var names: seq[Name] = @[]
var popCount = 0
for name in self.names: for name in self.names:
if name.depth > self.scopeDepth and name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl: if name.depth > self.scopeDepth:
names.add(name) names.add(name)
if len(names) > 1: if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl:
# We don't increase the pop count for these kinds of objects
# because they're not stored the same way as regular variables
inc(popCount)
if popCount > 1:
# If we're popping less than 65535 variables, then # If we're popping less than 65535 variables, then
# we can emit a PopN instruction. This is true for # we can emit a PopN instruction. This is true for
# 99.99999% of the use cases of the language (who the # 99.99999% of the use cases of the language (who the
# hell is going to use 65 THOUSAND local variables?), but # hell is going to use 65 THOUSAND variables?), but
# if you'll ever use more then Peon will emit a PopN instruction # if you'll ever use more then Peon will emit a PopN instruction
# for the first 65 thousand and change local variables and then # for the first 65 thousand and change local variables and then
# emit another batch of plain ol' Pop instructions for the rest # emit another batch of plain ol' Pop instructions for the rest
self.emitByte(PopN) self.emitByte(PopN)
self.emitBytes(len(names).toDouble()) self.emitBytes(popCount.toDouble())
if len(names) > uint16.high().int(): if popCount > uint16.high().int():
for i in countdown(self.names.high(), len(names) - uint16.high().int()): for i in countdown(self.names.high(), popCount - uint16.high().int()):
if self.names[i].depth > self.scopeDepth: if self.names[i].depth > self.scopeDepth:
self.emitByte(PopC) self.emitByte(PopC)
elif len(names) == 1: elif popCount == 1:
# We only emit PopN if we're popping more than one value # We only emit PopN if we're popping more than one value
self.emitByte(PopC) self.emitByte(PopC)
# This seems *really* slow, but # This seems *really* slow, but
@ -1274,7 +1278,6 @@ proc endScope(self: Compiler) =
inc(idx) inc(idx)
proc blockStmt(self: Compiler, node: BlockStmt) = proc blockStmt(self: Compiler, node: BlockStmt) =
## Compiles block statements, which create a new ## Compiles block statements, which create a new
## local scope ## local scope
@ -1321,15 +1324,17 @@ proc whileStmt(self: Compiler, node: WhileStmt) =
proc checkCallIsPure(self: Compiler, node: ASTnode): bool = proc checkCallIsPure(self: Compiler, node: ASTnode): bool =
## Checks if a call has any side effects ## Checks if a call has any side effects. Returns
## true if it doesn't and false otherwise
return true # TODO return true # TODO
proc callExpr(self: Compiler, node: CallExpr) = proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} =
## Compiles code to call a function ## Compiles code to call a function
var args: seq[tuple[name: string, kind: Type]] = @[] var args: seq[tuple[name: string, kind: Type]] = @[]
var argExpr: seq[Expression] = @[] var argExpr: seq[Expression] = @[]
var kind: Type var kind: Type
var onStack = false
# TODO: Keyword arguments # TODO: Keyword arguments
for i, argument in node.arguments.positionals: for i, argument in node.arguments.positionals:
kind = self.inferType(argument) kind = self.inferType(argument)
@ -1340,6 +1345,7 @@ proc callExpr(self: Compiler, node: CallExpr) =
args.add(("", kind)) args.add(("", kind))
argExpr.add(argument) argExpr.add(argument)
for argument in node.arguments.keyword: for argument in node.arguments.keyword:
# TODO
discard discard
if args.len() >= 16777216: if args.len() >= 16777216:
self.error(&"cannot pass more than 16777215 arguments") self.error(&"cannot pass more than 16777215 arguments")
@ -1348,28 +1354,19 @@ proc callExpr(self: Compiler, node: CallExpr) =
of identExpr: of identExpr:
funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args)) funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args))
of NodeKind.callExpr: of NodeKind.callExpr:
var node = node.callee funct = self.callExpr(CallExpr(node.callee))
while node.kind == callExpr: funct = Name(valueType: Type(kind: Function, returnType: Type(kind: Any), args: args))
self.callExpr(CallExpr(node)) onStack = true
node = CallExpr(node).callee # TODO: Calling lambdas on-the-fly (i.e. on the same line)
# funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args))
# TODO: Calling lambdas
else: else:
let typ = self.inferType(node) let typ = self.inferType(node)
if typ.isNil(): if typ.isNil():
self.error(&"expression has no type") self.error(&"expression has no type")
else: else:
self.error(&"object of type '{self.typeToStr(typ)}' is not callable") self.error(&"object of type '{self.typeToStr(typ)}' is not callable")
if any(funct.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic): result = funct
# The function has generic arguments! We need to compile a version self.generateCall(funct, argExpr, onStack)
# of it with the right type data if not self.checkCallIsPure(node.callee):
self.funDecl(nil, funct, argExpr)
# TODO: What next?
elif funct.valueType.isBuiltinFunction:
self.handleBuiltinFunction(funct, argExpr)
else:
self.generateCall(funct, argExpr)
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
if self.currentFunction.name != "": if self.currentFunction.name != "":
self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free") self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free")
else: else:
@ -1688,14 +1685,14 @@ proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type =
proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) = proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) =
## Compiles function declarations ## Compiles function declarations
if not node.isNil(): #[if not node.isNil():
if node.generics.len() > 0 and fn.isNil() and args.len() == 0: if node.generics.len() > 0 and fn.isNil() and args.len() == 0:
# Generic function! We can't compile it right now # Generic function! We can't compile it right now
self.declareName(node) self.declareName(node)
self.dispatchPragmas(node) self.dispatchPragmas(node)
return return]#
self.declareName(node) self.declareName(node)
self.dispatchPragmas(node) self.dispatchPragmas(node)
var node = node var node = node
var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn
if fn.valueType.isBuiltinFunction: if fn.valueType.isBuiltinFunction:
@ -1729,14 +1726,14 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression
self.error("cannot declare function with empty body") self.error("cannot declare function with empty body")
else: else:
discard # TODO: Forward declarations discard # TODO: Forward declarations
let impl = self.findByType(fn.name.token.lexeme, fn.valueType) let impl = self.findByType(fn.name.token.lexeme, fn.valueType, self.scopeDepth)
if impl.len() > 1: if impl.len() > 1:
# We found more than one (public) implementation of # We found more than one (public) implementation of
# the same function with the same name: this is an # the same function with the same name: this is an
# error, as it would raise ambiguity when calling them # error, as it would raise ambiguity when calling them
var msg = &"multiple matching implementations of '{fn.name.token.lexeme}' found:\n" var msg = &"multiple matching implementations of '{fn.name.token.lexeme}' found:\n"
for f in reversed(impl): for f in reversed(impl):
msg &= &"- '{f.name.token.lexeme}' at line {f.line} of type {self.typeToStr(f.valueType)}\n" msg &= &"- in module '{f.owner}' at line {f.line} of type {self.typeToStr(f.valueType)}\n"
self.error(msg) self.error(msg)
# Since the deferred array is a linear # Since the deferred array is a linear
# sequence of instructions and we want # sequence of instructions and we want

View File

@ -55,6 +55,7 @@ type
proc newSymbolTable: SymbolTable = proc newSymbolTable: SymbolTable =
## Initializes a new symbol table
new(result) new(result)
result.keywords = newTable[string, TokenType]() result.keywords = newTable[string, TokenType]()
result.symbols = newTable[string, TokenType]() result.symbols = newTable[string, TokenType]()
@ -144,6 +145,7 @@ proc isAlphaNumeric(s: string): bool =
return false return false
return true return true
# Forward declaration
proc incLine(self: Lexer) proc incLine(self: Lexer)
# Simple public getters used for error # Simple public getters used for error
@ -213,7 +215,8 @@ proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
## previously consumed tokens. If the ## previously consumed tokens. If the
## distance and/or the length are beyond ## distance and/or the length are beyond
## EOF (even partially), the resulting string ## EOF (even partially), the resulting string
## will be shorter than length bytes ## will be shorter than length bytes. The string
## may be empty
var i = distance var i = distance
while len(result) < length: while len(result) < length:
if self.done() or self.current + i > self.source.high() or if self.done() or self.current + i > self.source.high() or
@ -225,8 +228,8 @@ proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
proc error(self: Lexer, message: string) = proc error(self: Lexer, message: string) =
## Raises a lexing error with a formatted ## Raises a lexing error with info
## error message ## for error messages
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek()) raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek())
@ -299,7 +302,7 @@ proc parseEscape(self: Lexer) =
# likely be soon. Another notable limitation is that # likely be soon. Another notable limitation is that
# \xhhh and \nnn are limited to the size of a char # \xhhh and \nnn are limited to the size of a char
# (i.e. uint8, or 256 values) # (i.e. uint8, or 256 values)
case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings
# (i.e. not well, given they crash the C code generator) # (i.e. not well, given they crash the C code generator)
of 'a': of 'a':
self.source[self.current] = cast[char](0x07) self.source[self.current] = cast[char](0x07)

View File

@ -183,7 +183,7 @@ type
returnType*: Expression returnType*: Expression
hasExplicitReturn*: bool hasExplicitReturn*: bool
freeVars*: seq[IdentExpr] freeVars*: seq[IdentExpr]
depth*: int
SliceExpr* = ref object of Expression SliceExpr* = ref object of Expression
expression*: Expression expression*: Expression
@ -265,6 +265,7 @@ type
returnType*: Expression returnType*: Expression
hasExplicitReturn*: bool hasExplicitReturn*: bool
freeVars*: seq[IdentExpr] freeVars*: seq[IdentExpr]
depth*: int
TypeDecl* = ref object of Declaration TypeDecl* = ref object of Declaration
name*: IdentExpr name*: IdentExpr
@ -411,10 +412,11 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
result.token = token result.token = token
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]], proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
defaults: seq[Expression], body: Statement, isGenerator: bool, body: Statement, isAsync, isGenerator: bool,
isAsync: bool, token: Token, returnType: Expression, pragmas: seq[Pragma], token: Token, depth: int, pragmas: seq[Pragma] = @[],
generics: seq[tuple[name: IdentExpr, cond: Expression]], freeVars: seq[IdentExpr] = @[]): LambdaExpr = returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[],
freeVars: seq[IdentExpr] = @[]): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr) result = LambdaExpr(kind: lambdaExpr)
result.body = body result.body = body
result.arguments = arguments result.arguments = arguments
@ -427,6 +429,7 @@ proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]
result.pragmas = pragmas result.pragmas = pragmas
result.generics = generics result.generics = generics
result.freeVars = freeVars result.freeVars = freeVars
result.depth = depth
proc newGetItemExpr*(obj: Expression, name: IdentExpr, proc newGetItemExpr*(obj: Expression, name: IdentExpr,
@ -615,8 +618,9 @@ proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false,
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression], proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool, body: Statement, isAsync, isGenerator: bool,
isPrivate: bool, token: Token, pragmas: seq[Pragma], isPrivate: bool, token: Token, depth: int,
returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]], freeVars: seq[IdentExpr] = @[]): FunDecl = pragmas: seq[Pragma] = @[], returnType: Expression,
generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[], freeVars: seq[IdentExpr] = @[]): FunDecl =
result = FunDecl(kind: funDecl) result = FunDecl(kind: funDecl)
result.name = name result.name = name
result.arguments = arguments result.arguments = arguments
@ -631,6 +635,7 @@ proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueTyp
result.isPure = false result.isPure = false
result.generics = generics result.generics = generics
result.freeVars = freeVars result.freeVars = freeVars
result.depth = depth
proc newTypeDecl*(name: IdentExpr, fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]], proc newTypeDecl*(name: IdentExpr, fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]],
@ -730,13 +735,13 @@ proc `$`*(self: ASTNode): string =
result &= &"Var(name={self.name}, value={self.value}, const={self.isConst}, private={self.isPrivate}, type={self.valueType}, pragmas={self.pragmas})" result &= &"Var(name={self.name}, value={self.value}, const={self.isConst}, private={self.isPrivate}, type={self.valueType}, pragmas={self.pragmas})"
of funDecl: of funDecl:
var self = FunDecl(self) var self = FunDecl(self)
result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generics=[{self.generics.join(", ")}], async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate}, pragmas={self.pragmas})""" result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generics=[{self.generics.join(", ")}], async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate}, pragmas={self.pragmas}, vars=[{self.freeVars.join(", ")}])"""
of typeDecl: of typeDecl:
var self = TypeDecl(self) var self = TypeDecl(self)
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, defaults={self.defaults}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, pragmas={self.pragmas}, type={self.valueType})""" result &= &"""TypeDecl(name={self.name}, fields={self.fields}, defaults={self.defaults}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, pragmas={self.pragmas}, type={self.valueType})"""
of lambdaExpr: of lambdaExpr:
var self = LambdaExpr(self) var self = LambdaExpr(self)
result &= &"""Lambda(body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generator={self.isGenerator}, async={self.isAsync}, pragmas={self.pragmas})""" result &= &"""Lambda(body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generator={self.isGenerator}, async={self.isAsync}, pragmas={self.pragmas}, vars=[{self.freeVars.join(", ")}])"""
of deferStmt: of deferStmt:
var self = DeferStmt(self) var self = DeferStmt(self)
result &= &"Defer({self.expression})" result &= &"Defer({self.expression})"

View File

@ -79,7 +79,7 @@ type
proc `$`*(self: Token): string = proc `$`*(self: Token): string =
## Strinfifies ## Strinfifies
if self != nil: if self != nil:
result = &"Token(kind={self.kind}, lexeme={($self.lexeme).escape()}, line={self.line}, pos=({self.pos.start}, {self.pos.stop}), spaces={self.spaces})" result = &"Token(kind={self.kind}, lexeme={self.lexeme.escape()}, line={self.line}, pos=({self.pos.start}, {self.pos.stop}), spaces={self.spaces})"
else: else:
result = "nil" result = "nil"

View File

@ -192,9 +192,9 @@ proc step(self: Parser, n: int = 1): Token =
self.current += 1 self.current += 1
proc error(self: Parser, message: string) {.raises: [ParseError].} = proc error(self: Parser, message: string, token: Token = nil) {.raises: [ParseError].} =
## Raises a ParseError exception ## Raises a ParseError exception
raise ParseError(msg: message, token: self.getCurrentToken(), file: self.file, module: self.getModule()) raise ParseError(msg: message, token: if token.isNil(): self.getCurrentToken() else: token, file: self.file, module: self.getModule())
# Why do we allow strings or enum members of TokenType? Well, it's simple: # Why do we allow strings or enum members of TokenType? Well, it's simple:
@ -306,6 +306,16 @@ proc primary(self: Parser): Expression =
result = newIntExpr(self.step()) result = newIntExpr(self.step())
of Identifier: of Identifier:
result = newIdentExpr(self.step(), self.scopeDepth) result = newIdentExpr(self.step(), self.scopeDepth)
if not self.currentFunction.isNil() and self.scopeDepth > 0:
case self.currentFunction.kind:
of NodeKind.funDecl:
if FunDecl(self.currentFunction).depth != self.scopeDepth:
FunDecl(self.currentFunction).freeVars.add(IdentExpr(result))
of NodeKind.lambdaExpr:
if LambdaExpr(self.currentFunction).depth != self.scopeDepth:
LambdaExpr(self.currentFunction).freeVars.add(IdentExpr(result))
else:
discard # Unreachable
of LeftParen: of LeftParen:
let tok = self.step() let tok = self.step()
result = newGroupingExpr(self.expression(), tok) result = newGroupingExpr(self.expression(), tok)
@ -313,10 +323,10 @@ proc primary(self: Parser): Expression =
of Yield: of Yield:
let tok = self.step() let tok = self.step()
if self.currentFunction.isNil(): if self.currentFunction.isNil():
self.error("'yield' cannot be used outside functions") self.error("'yield' cannot be used outside functions", tok)
elif self.currentFunction.token.kind != Generator: elif self.currentFunction.token.kind != Generator:
# It's easier than doing conversions for lambda/funDecl # It's easier than doing conversions for lambda/funDecl
self.error("'yield' cannot be used outside generators") self.error("'yield' cannot be used outside generators", tok)
if not self.check([RightBrace, RightBracket, RightParen, Comma, Semicolon]): if not self.check([RightBrace, RightBracket, RightParen, Comma, Semicolon]):
# Expression delimiters # Expression delimiters
result = newYieldExpr(self.expression(), tok) result = newYieldExpr(self.expression(), tok)
@ -326,9 +336,9 @@ proc primary(self: Parser): Expression =
of Await: of Await:
let tok = self.step() let tok = self.step()
if self.currentFunction.isNil(): if self.currentFunction.isNil():
self.error("'await' cannot be used outside functions") self.error("'await' cannot be used outside functions", tok)
if self.currentFunction.token.kind != Coroutine: if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines") self.error("'await' can only be used inside coroutines", tok)
result = newAwaitExpr(self.expression(), tok) result = newAwaitExpr(self.expression(), tok)
of RightParen, RightBracket, RightBrace: of RightParen, RightBracket, RightBrace:
# This is *technically* unnecessary: the parser would # This is *technically* unnecessary: the parser would
@ -502,7 +512,7 @@ proc parseAssign(self: Parser): Expression =
of getItemExpr: of getItemExpr:
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok) result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
else: else:
self.error("invalid assignment target") self.error("invalid assignment target", tok)
proc parseArrow(self: Parser): Expression = proc parseArrow(self: Parser): Expression =
@ -568,8 +578,8 @@ proc deferStmt(self: Parser): Statement =
let tok = self.peek(-1) let tok = self.peek(-1)
if self.currentFunction.isNil(): if self.currentFunction.isNil():
self.error("'defer' cannot be used outside functions") self.error("'defer' cannot be used outside functions")
result = newDeferStmt(self.expression(), tok)
endOfLine("missing statement terminator after 'defer'") endOfLine("missing statement terminator after 'defer'")
result = newDeferStmt(self.expression(), tok)
proc continueStmt(self: Parser): Statement = proc continueStmt(self: Parser): Statement =
@ -622,8 +632,8 @@ proc awaitStmt(self: Parser): Statement =
self.error("'await' cannot be used outside functions") self.error("'await' cannot be used outside functions")
if self.currentFunction.token.kind != Coroutine: if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines") self.error("'await' can only be used inside coroutines")
result = newAwaitStmt(self.expression(), tok)
endOfLine("missing statement terminator after 'await'") endOfLine("missing statement terminator after 'await'")
result = newAwaitStmt(self.expression(), tok)
proc raiseStmt(self: Parser): Statement = proc raiseStmt(self: Parser): Statement =
@ -661,8 +671,8 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
tok = self.peek(-1) tok = self.peek(-1)
# TODO: New AST node # TODO: New AST node
self.expect(Identifier, "expecting module name(s) after import statement") self.expect(Identifier, "expecting module name(s) after import statement")
result = newImportStmt(newIdentExpr(self.peek(-1), self.scopeDepth), tok)
endOfLine("missing statement terminator after 'import'") endOfLine("missing statement terminator after 'import'")
result = newImportStmt(newIdentExpr(self.peek(-1), self.scopeDepth), tok)
@ -687,10 +697,10 @@ proc tryStmt(self: Parser): Statement =
if self.match(Finally): if self.match(Finally):
finallyClause = self.statement() finallyClause = self.statement()
if handlers.len() == 0 and elseClause.isNil() and finallyClause.isNil(): if handlers.len() == 0 and elseClause.isNil() and finallyClause.isNil():
self.error("expecting 'except', 'finally' or 'else' statement after 'try' block") self.error("expecting 'except', 'finally' or 'else' statement after 'try' block", tok)
for i, handler in handlers: for i, handler in handlers:
if handler.exc.isNil() and i != handlers.high(): if handler.exc.isNil() and i != handlers.high():
self.error("catch-all exception handler with bare 'except' must come last in try statement") self.error("catch-all exception handler with bare 'except' must come last in try statement", handler.exc.token)
result = newTryStmt(body, handlers, finallyClause, elseClause, tok) result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
@ -807,15 +817,15 @@ proc parsePragmas(self: Parser): seq[Pragma] =
while not self.match(")") and not self.done(): while not self.match(")") and not self.done():
exp = self.primary() exp = self.primary()
if not exp.isLiteral(): if not exp.isLiteral():
self.error("pragma arguments can only be literals") self.error("pragma arguments can only be literals", exp.token)
args.add(LiteralExpr(exp)) args.add(LiteralExpr(exp))
if not self.match(","): if not self.match(","):
break break
self.expect(")", "unterminated parenthesis in pragma arguments") self.expect(LeftParen, "unterminated parenthesis in pragma arguments")
else: else:
exp = self.primary() exp = self.primary()
if not exp.isLiteral(): if not exp.isLiteral():
self.error("pragma arguments can only be literals") self.error("pragma arguments can only be literals", exp.token)
args.add(LiteralExpr(exp)) args.add(LiteralExpr(exp))
result.add(newPragma(name, args)) result.add(newPragma(name, args))
if self.match(","): if self.match(","):
@ -866,7 +876,7 @@ proc varDecl(self: Parser, isLet: bool = false,
else: else:
discard # Unreachable discard # Unreachable
if not hasInit and VarDecl(result).valueType.isNil(): if not hasInit and VarDecl(result).valueType.isNil():
self.error("expecting initializer or explicit type declaration, but neither was found") self.error("expecting initializer or explicit type annotation, but neither was found", result.token)
result.pragmas = pragmas result.pragmas = pragmas
@ -876,7 +886,7 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
## Helper to parse declaration arguments and avoid code duplication ## Helper to parse declaration arguments and avoid code duplication
while not self.check(RightParen): while not self.check(RightParen):
if arguments.len > 255: if arguments.len > 255:
self.error("cannot have more than 255 arguments in function declaration") self.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
self.expect(Identifier, "expecting parameter name") self.expect(Identifier, "expecting parameter name")
parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth) parameter.name = newIdentExpr(self.peek(-1), self.scopeDepth)
if self.match(":"): if self.match(":"):
@ -888,12 +898,12 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
else: else:
parameter.valueType = nil parameter.valueType = nil
if parameter in arguments: if parameter in arguments:
self.error("duplicate parameter name in function declaration") self.error("duplicate parameter name in function declaration", parameter.name.token)
arguments.add(parameter) arguments.add(parameter)
if self.match("="): if self.match("="):
defaults.add(self.expression()) defaults.add(self.expression())
elif defaults.len() > 0: elif defaults.len() > 0:
self.error("positional argument cannot follow default argument in function declaration") self.error("positional argument cannot follow default argument in function declaration", parameter.name.token)
if not self.match(Comma): if not self.match(Comma):
break break
self.expect(RightParen) self.expect(RightParen)
@ -910,7 +920,7 @@ proc parseFunExpr(self: Parser): LambdaExpr =
var defaults: seq[Expression] = @[] var defaults: seq[Expression] = @[]
result = newLambdaExpr(arguments, defaults, nil, isGenerator=self.peek(-1).kind == Generator, result = newLambdaExpr(arguments, defaults, nil, isGenerator=self.peek(-1).kind == Generator,
isAsync=self.peek(-1).kind == Coroutine, token=self.peek(-1), isAsync=self.peek(-1).kind == Coroutine, token=self.peek(-1),
returnType=nil, pragmas=(@[]), generics=(@[])) returnType=nil, depth=self.scopeDepth)
var parameter: tuple[name: IdentExpr, valueType: Expression] var parameter: tuple[name: IdentExpr, valueType: Expression]
if self.match(LeftParen): if self.match(LeftParen):
self.parseDeclArguments(arguments, parameter, defaults) self.parseDeclArguments(arguments, parameter, defaults)
@ -956,9 +966,9 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
isAsync=isAsync, isAsync=isAsync,
isGenerator=isGenerator, isGenerator=isGenerator,
isPrivate=true, isPrivate=true,
token=tok, pragmas=(@[]), token=tok,
returnType=nil, returnType=nil,
generics=(@[])) depth=self.scopeDepth)
if self.match("*"): if self.match("*"):
FunDecl(self.currentFunction).isPrivate = false FunDecl(self.currentFunction).isPrivate = false
self.checkDecl(FunDecl(self.currentFunction).isPrivate) self.checkDecl(FunDecl(self.currentFunction).isPrivate)
@ -978,8 +988,8 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
self.currentFunction = enclosingFunction self.currentFunction = enclosingFunction
return result return result
elif isLambda: elif isLambda:
self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator = isGenerator, isAsync = isAsync, token = tok, self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator=isGenerator, isAsync=isAsync, token=tok,
returnType = nil, pragmas = (@[]), generics=(@[])) returnType=nil, depth=self.scopeDepth)
if self.match(":"): if self.match(":"):
# Function has explicit return type # Function has explicit return type
if self.match([Function, Coroutine, Generator]): if self.match([Function, Coroutine, Generator]):
@ -1198,12 +1208,12 @@ proc parse*(self: Parser, tokens: seq[Token], file: string): seq[Declaration] =
# with an EOF token # with an EOF token
if token.kind == Operator: if token.kind == Operator:
if i == self.tokens.high(): if i == self.tokens.high():
self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)") self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)", token)
self.operators.addOperator(self.tokens[i + 1].lexeme) self.operators.addOperator(self.tokens[i + 1].lexeme)
if i == self.tokens.high() and token.kind != EndOfFile: if i == self.tokens.high() and token.kind != EndOfFile:
# Since we're iterating this list anyway might as # Since we're iterating this list anyway might as
# well perform some extra checks # well perform some extra checks
self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)") self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)", token)
while not self.done(): while not self.done():
self.tree.add(self.declaration()) self.tree.add(self.declaration())
if self.tree[^1] == nil: if self.tree[^1] == nil:

View File

@ -142,9 +142,9 @@ proc repl(vm: PeonVM = newPeonVM()) =
input = "" input = ""
let exc = LexingError(getCurrentException()) let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line) let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip() let line = tokenizer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme}'", fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg()) fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
@ -155,7 +155,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
let lineNo = exc.token.line let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo) let relPos = tokenizer.getRelPos(lineNo)
let fn = parser.getCurrentFunction() let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() let line = tokenizer.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fnMsg = "" var fnMsg = ""
if fn != nil and fn.kind == funDecl: if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
@ -169,7 +169,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
let lexeme = exc.node.token.lexeme let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo) let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() let line = tokenizer.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = compiler.getCurrentFunction() var fn = compiler.getCurrentFunction()
var fnMsg = "" var fnMsg = ""
if fn != nil and fn.kind == funDecl: if fn != nil and fn.kind == funDecl:
@ -265,21 +265,23 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
styledEcho fgCyan, "\n\nExecution step: " styledEcho fgCyan, "\n\nExecution step: "
vm.run(serialized.chunk) vm.run(serialized.chunk)
except LexingError: except LexingError:
input = ""
let exc = LexingError(getCurrentException()) let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line) let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip() let line = tokenizer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'", fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg()) fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError: except ParseError:
input = ""
let exc = ParseError(getCurrentException()) let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme.escape() let lexeme = exc.token.lexeme
let lineNo = exc.token.line let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo) let relPos = tokenizer.getRelPos(lineNo)
let fn = parser.getCurrentFunction() let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() let line = tokenizer.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fnMsg = "" var fnMsg = ""
if fn != nil and fn.kind == funDecl: if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
@ -290,10 +292,10 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError: except CompileError:
let exc = CompileError(getCurrentException()) let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme.escape() let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo) let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() let line = tokenizer.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = compiler.getCurrentFunction() var fn = compiler.getCurrentFunction()
var fnMsg = "" var fnMsg = ""
if fn != nil and fn.kind == funDecl: if fn != nil and fn.kind == funDecl:

View File

@ -138,7 +138,7 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls ## Debugs function calls
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction) printInstruction(instruction)
styledEcho fgGreen, &", creates frame of size ", fgYellow, $size styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2)
self.current += 4 self.current += 4

View File

@ -7,5 +7,5 @@ fn makeClosure(n: int): fn: int {
} }
var closure = makeClosure(1); var closure = makeClosure(1)();
closure(); closure;