Initial work on fixing lambdas and minor improvements to error reporting
This commit is contained in:
parent
d98feb499d
commit
97698b28af
|
@ -35,7 +35,7 @@ import ../frontend/compiler/targets/bytecode/opcodes
|
||||||
import ../frontend/compiler/targets/bytecode/util/multibyte
|
import ../frontend/compiler/targets/bytecode/util/multibyte
|
||||||
|
|
||||||
|
|
||||||
when debugVM or debugMem or debugGC:
|
when debugVM or debugMem or debugGC or debugAlloc:
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
import std/terminal
|
import std/terminal
|
||||||
|
|
|
@ -587,11 +587,11 @@ proc endScope(self: BytecodeCompiler) =
|
||||||
# Automatic functions do not materialize
|
# Automatic functions do not materialize
|
||||||
# at runtime, so their arguments don't either
|
# at runtime, so their arguments don't either
|
||||||
continue
|
continue
|
||||||
# This name has been generated internally by the
|
# This name has been generated internally by the
|
||||||
# compiler and is a copy of an already existing
|
# compiler and is a copy of an already existing
|
||||||
# one, so we only need to pop its "real" counterpart
|
# one, so we only need to pop its "real" counterpart
|
||||||
if not name.isReal:
|
if not name.isReal:
|
||||||
continue
|
continue
|
||||||
inc(popCount)
|
inc(popCount)
|
||||||
if not name.resolved:
|
if not name.resolved:
|
||||||
# We emit warnings for names that are declared but never used
|
# We emit warnings for names that are declared but never used
|
||||||
|
@ -935,6 +935,45 @@ proc prepareAutoFunction(self: BytecodeCompiler, fn: Name, args: seq[tuple[name:
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
|
|
||||||
|
proc prepareAutoLambda(self: BytecodeCompiler, fn: Type, args: seq[tuple[name: string, kind: Type, default: Expression]]): Type =
|
||||||
|
## "Prepares" an automatic lambda function
|
||||||
|
## by instantiating a concrete version of it
|
||||||
|
## along with its arguments
|
||||||
|
let idx = self.stackIndex
|
||||||
|
self.stackIndex = 1
|
||||||
|
var default: Expression
|
||||||
|
var node = LambdaExpr(fn.fun)
|
||||||
|
var fn = deepCopy(fn)
|
||||||
|
fn.isAuto = false
|
||||||
|
fn.compiled = false
|
||||||
|
# We now declare and typecheck the function's
|
||||||
|
# arguments
|
||||||
|
for (argument, val) in zip(node.arguments, args):
|
||||||
|
if self.names.high() > 16777215:
|
||||||
|
self.error("cannot declare more than 16777215 variables at a time")
|
||||||
|
inc(self.stackIndex)
|
||||||
|
self.names.add(Name(depth: self.depth + 1,
|
||||||
|
isPrivate: true,
|
||||||
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
|
isConst: false,
|
||||||
|
ident: argument.name,
|
||||||
|
valueType: val.kind,
|
||||||
|
codePos: 0,
|
||||||
|
isLet: false,
|
||||||
|
line: argument.name.token.line,
|
||||||
|
belongsTo: self.currentFunction,
|
||||||
|
kind: NameKind.Argument,
|
||||||
|
node: argument.name,
|
||||||
|
position: self.stackIndex,
|
||||||
|
isReal: true
|
||||||
|
))
|
||||||
|
fn.args = args
|
||||||
|
fn.location = self.stackIndex
|
||||||
|
self.stackIndex = idx
|
||||||
|
return fn
|
||||||
|
|
||||||
|
|
||||||
proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) =
|
proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) =
|
||||||
## Small wrapper that abstracts emitting a call instruction
|
## Small wrapper that abstracts emitting a call instruction
|
||||||
## for a given function
|
## for a given function
|
||||||
|
@ -1274,7 +1313,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
|
||||||
var default: Expression
|
var default: Expression
|
||||||
var kind: Type
|
var kind: Type
|
||||||
for i, argument in node.arguments.positionals:
|
for i, argument in node.arguments.positionals:
|
||||||
kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message
|
kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message later
|
||||||
if kind.isNil():
|
if kind.isNil():
|
||||||
if argument.kind == NodeKind.identExpr:
|
if argument.kind == NodeKind.identExpr:
|
||||||
self.error(&"reference to undefined name '{argument.token.lexeme}'", argument)
|
self.error(&"reference to undefined name '{argument.token.lexeme}'", argument)
|
||||||
|
@ -1298,9 +1337,13 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
|
||||||
result = self.specialize(result, argExpr)
|
result = self.specialize(result, argExpr)
|
||||||
if impl.valueType.isAuto:
|
if impl.valueType.isAuto:
|
||||||
impl = self.prepareAutoFunction(impl, args)
|
impl = self.prepareAutoFunction(impl, args)
|
||||||
result = impl.valueType
|
result = impl.valueType
|
||||||
if not impl.valueType.compiled:
|
if not impl.valueType.compiled:
|
||||||
self.funDecl(FunDecl(result.fun), impl)
|
case result.fun.kind:
|
||||||
|
of NodeKind.lambdaExpr:
|
||||||
|
self.lambdaExpr(LambdaExpr(result.fun), compile=compile)
|
||||||
|
else:
|
||||||
|
self.funDecl(FunDecl(result.fun), impl)
|
||||||
result = result.returnType
|
result = result.returnType
|
||||||
if compile:
|
if compile:
|
||||||
if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate:
|
if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate:
|
||||||
|
@ -1312,7 +1355,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
|
||||||
self.declaration(decl)
|
self.declaration(decl)
|
||||||
else:
|
else:
|
||||||
# The last expression in a template
|
# The last expression in a template
|
||||||
# is its return type, so we compute
|
# is its "return value", so we compute
|
||||||
# it, but don't pop it off the stack
|
# it, but don't pop it off the stack
|
||||||
if decl.kind == exprStmt:
|
if decl.kind == exprStmt:
|
||||||
self.expression(ExprStmt(decl).expression)
|
self.expression(ExprStmt(decl).expression)
|
||||||
|
@ -1346,6 +1389,15 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
|
||||||
result = self.specialize(result, argExpr)
|
result = self.specialize(result, argExpr)
|
||||||
result = result.returnType
|
result = result.returnType
|
||||||
self.generateCall(impl, argExpr, node.token.line)
|
self.generateCall(impl, argExpr, node.token.line)
|
||||||
|
of NodeKind.lambdaExpr:
|
||||||
|
var node = LambdaExpr(node.callee)
|
||||||
|
var impl = self.lambdaExpr(node, compile=false)
|
||||||
|
if impl.isAuto:
|
||||||
|
impl = self.prepareAutoLambda(impl, args)
|
||||||
|
result = impl
|
||||||
|
result = result.returnType
|
||||||
|
if compile:
|
||||||
|
self.generateCall(impl, argExpr, node.token.line)
|
||||||
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
||||||
else:
|
else:
|
||||||
let typ = self.infer(node)
|
let typ = self.infer(node)
|
||||||
|
@ -1388,9 +1440,27 @@ method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = tr
|
||||||
self.error("invalid syntax", node)
|
self.error("invalid syntax", node)
|
||||||
|
|
||||||
|
|
||||||
|
proc blockStmt(self: BytecodeCompiler, node: BlockStmt, compile: bool = true) =
|
||||||
|
## Compiles block statements, which create
|
||||||
|
## a new local scope
|
||||||
|
self.beginScope()
|
||||||
|
var last: Declaration
|
||||||
|
for decl in node.code:
|
||||||
|
if not last.isNil():
|
||||||
|
case last.kind:
|
||||||
|
of breakStmt, continueStmt:
|
||||||
|
self.warning(UnreachableCode, &"code after '{last.token.lexeme}' statement is unreachable", nil, decl)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
self.declaration(decl)
|
||||||
|
last = decl
|
||||||
|
self.endScope()
|
||||||
|
|
||||||
|
|
||||||
method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} =
|
method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} =
|
||||||
## Compiles lambda functions as expressions
|
## Compiles lambda functions as expressions
|
||||||
result = Type(kind: Function, isLambda: true, fun: node, location: self.chunk.code.high(), compiled: true)
|
result = Type(kind: Function, isLambda: true, fun: node, location: 0)
|
||||||
|
let function = self.currentFunction
|
||||||
self.beginScope()
|
self.beginScope()
|
||||||
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
|
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
|
||||||
for gen in node.generics:
|
for gen in node.generics:
|
||||||
|
@ -1438,10 +1508,75 @@ method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true
|
||||||
# The function needs a return type too!
|
# The function needs a return type too!
|
||||||
if not node.returnType.isNil():
|
if not node.returnType.isNil():
|
||||||
result.returnType = self.inferOrError(node.returnType)
|
result.returnType = self.inferOrError(node.returnType)
|
||||||
self.endScope()
|
self.currentFunction = Name(depth: self.depth,
|
||||||
if not compile:
|
isPrivate: true,
|
||||||
return
|
isConst: false,
|
||||||
# TODO
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
|
valueType: Type(kind: Function,
|
||||||
|
returnType: result.returnType,
|
||||||
|
args: result.args,
|
||||||
|
fun: node,
|
||||||
|
forwarded: false,
|
||||||
|
isAuto: false),
|
||||||
|
ident: nil,
|
||||||
|
node: node,
|
||||||
|
isLet: false,
|
||||||
|
line: node.token.line,
|
||||||
|
kind: NameKind.Function,
|
||||||
|
belongsTo: self.currentFunction,
|
||||||
|
isReal: true)
|
||||||
|
if compile:
|
||||||
|
result.compiled = true
|
||||||
|
let stackIdx = self.stackIndex
|
||||||
|
self.stackIndex = name.position
|
||||||
|
let jmp = self.emitJump(JumpForwards, node.token.line)
|
||||||
|
if BlockStmt(node.body).code.len() == 0:
|
||||||
|
self.error("cannot construct lambda with empty body")
|
||||||
|
var last: Declaration
|
||||||
|
self.beginScope()
|
||||||
|
result.location = self.chunk.code.len()
|
||||||
|
for decl in BlockStmt(node.body).code:
|
||||||
|
if not last.isNil():
|
||||||
|
if last.kind == returnStmt:
|
||||||
|
self.warning(UnreachableCode, "code after 'return' statement is unreachable", nil, decl)
|
||||||
|
self.declaration(decl)
|
||||||
|
last = decl
|
||||||
|
let typ = self.currentFunction.valueType.returnType
|
||||||
|
var hasVal: bool = false
|
||||||
|
case self.currentFunction.valueType.fun.kind:
|
||||||
|
of NodeKind.funDecl:
|
||||||
|
hasVal = FunDecl(self.currentFunction.valueType.fun).hasExplicitReturn
|
||||||
|
of NodeKind.lambdaExpr:
|
||||||
|
hasVal = LambdaExpr(self.currentFunction.valueType.fun).hasExplicitReturn
|
||||||
|
else:
|
||||||
|
discard # Unreachable
|
||||||
|
if not hasVal and not typ.isNil():
|
||||||
|
# There is no explicit return statement anywhere in the function's
|
||||||
|
# body: while this is not a tremendously useful piece of information
|
||||||
|
# (since the presence of at least one doesn't mean all control flow
|
||||||
|
# cases are covered), it definitely is an error worth reporting
|
||||||
|
self.error("function has an explicit return type, but no return statement was found", node)
|
||||||
|
hasVal = hasVal and not typ.isNil()
|
||||||
|
for jump in self.currentFunction.valueType.retJumps:
|
||||||
|
self.patchJump(jump)
|
||||||
|
self.endScope()
|
||||||
|
# Terminates the function's context
|
||||||
|
self.emitByte(OpCode.Return, self.peek().token.line)
|
||||||
|
if hasVal:
|
||||||
|
self.emitByte(1, self.peek().token.line)
|
||||||
|
else:
|
||||||
|
self.emitByte(0, self.peek().token.line)
|
||||||
|
# Well, we've compiled everything: time to patch
|
||||||
|
# the jump offset
|
||||||
|
self.patchJump(jmp)
|
||||||
|
# Restores the enclosing function (if any).
|
||||||
|
# Makes nested calls work (including recursion)
|
||||||
|
self.currentFunction = function
|
||||||
|
self.stackIndex = stackIdx
|
||||||
|
self.endScope()
|
||||||
|
self.emitByte(LoadUInt64, node.token.line)
|
||||||
|
self.emitBytes(self.chunk.writeConstant(result.location.toLong()), node.token.line)
|
||||||
|
|
||||||
|
|
||||||
method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} =
|
method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} =
|
||||||
|
@ -1484,23 +1619,6 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true
|
||||||
self.error(&"invalid AST node of kind {node.kind} at expression(): {node} (This is an internal error and most likely a bug)")
|
self.error(&"invalid AST node of kind {node.kind} at expression(): {node} (This is an internal error and most likely a bug)")
|
||||||
|
|
||||||
|
|
||||||
proc blockStmt(self: BytecodeCompiler, node: BlockStmt, compile: bool = true) =
|
|
||||||
## Compiles block statements, which create
|
|
||||||
## a new local scope
|
|
||||||
self.beginScope()
|
|
||||||
var last: Declaration
|
|
||||||
for decl in node.code:
|
|
||||||
if not last.isNil():
|
|
||||||
case last.kind:
|
|
||||||
of breakStmt, continueStmt:
|
|
||||||
self.warning(UnreachableCode, &"code after '{last.token.lexeme}' statement is unreachable", nil, decl)
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
self.declaration(decl)
|
|
||||||
last = decl
|
|
||||||
self.endScope()
|
|
||||||
|
|
||||||
|
|
||||||
proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
|
proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
|
||||||
## Compiles if/else statements for conditional
|
## Compiles if/else statements for conditional
|
||||||
## execution of code
|
## execution of code
|
||||||
|
|
|
@ -966,10 +966,7 @@ proc varDecl(self: Parser, isLet: bool = false,
|
||||||
valueType = newIdentExpr(self.peek(-1), self.scopeDepth)
|
valueType = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||||
if self.match("="):
|
if self.match("="):
|
||||||
hasInit = true
|
hasInit = true
|
||||||
if self.match([Function, Coroutine, Generator]):
|
value = self.expression()
|
||||||
value = self.parseFunExpr()
|
|
||||||
else:
|
|
||||||
value = self.expression()
|
|
||||||
if isConst and not value.isConst():
|
if isConst and not value.isConst():
|
||||||
self.error("constant initializer is not a constant")
|
self.error("constant initializer is not a constant")
|
||||||
elif tok.kind != TokenType.Var:
|
elif tok.kind != TokenType.Var:
|
||||||
|
|
|
@ -34,8 +34,12 @@ proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], f
|
||||||
stderr.styledWriteLine(styleBright, fgDefault, ": ", msg)
|
stderr.styledWriteLine(styleBright, fgDefault, ": ", msg)
|
||||||
if line.len() > 0:
|
if line.len() > 0:
|
||||||
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
|
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
|
||||||
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
|
if pos.stop == line.len():
|
||||||
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
|
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..<pos.stop])
|
||||||
|
stderr.styledWriteLine(fgDefault, line[pos.stop..^1])
|
||||||
|
else:
|
||||||
|
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
|
||||||
|
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
|
||||||
|
|
||||||
|
|
||||||
proc print*(exc: CompileError) =
|
proc print*(exc: CompileError) =
|
||||||
|
|
Loading…
Reference in New Issue