Initial broken work on generics
This commit is contained in:
parent
985ceed075
commit
2c6325d33b
|
@ -53,10 +53,6 @@ type
|
||||||
case kind: TypeKind:
|
case kind: TypeKind:
|
||||||
of Function:
|
of Function:
|
||||||
name: string
|
name: string
|
||||||
# Unfortunately we need to pollute
|
|
||||||
# the type system with AST nodes due
|
|
||||||
# to how we handle generics
|
|
||||||
funNode: FunDecl
|
|
||||||
isLambda: bool
|
isLambda: bool
|
||||||
isGenerator: bool
|
isGenerator: bool
|
||||||
isCoroutine: bool
|
isCoroutine: bool
|
||||||
|
@ -64,6 +60,7 @@ type
|
||||||
returnType: Type
|
returnType: Type
|
||||||
isBuiltinFunction: bool
|
isBuiltinFunction: bool
|
||||||
builtinOp: string
|
builtinOp: string
|
||||||
|
fun: FunDecl
|
||||||
of Reference, Pointer:
|
of Reference, Pointer:
|
||||||
value: Type
|
value: Type
|
||||||
of Generic:
|
of Generic:
|
||||||
|
@ -140,7 +137,7 @@ type
|
||||||
# in a local scope, otherwise it's global
|
# in a local scope, otherwise it's global
|
||||||
scopeDepth: int
|
scopeDepth: int
|
||||||
# The current function being compiled
|
# The current function being compiled
|
||||||
currentFunction: FunDecl
|
currentFunction: Type
|
||||||
# Are optimizations turned on?
|
# Are optimizations turned on?
|
||||||
enableOptimizations: bool
|
enableOptimizations: bool
|
||||||
# The current loop being compiled (used to
|
# The current loop being compiled (used to
|
||||||
|
@ -193,6 +190,7 @@ proc patchReturnAddress(self: Compiler, pos: int)
|
||||||
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
||||||
proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
||||||
proc dispatchPragmas(self: Compiler, node: ASTnode)
|
proc dispatchPragmas(self: Compiler, node: ASTnode)
|
||||||
|
proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[])
|
||||||
## End of forward declarations
|
## End of forward declarations
|
||||||
|
|
||||||
|
|
||||||
|
@ -216,7 +214,7 @@ proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Com
|
||||||
## Public getter for nicer error formatting
|
## Public getter for nicer error formatting
|
||||||
proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >=
|
proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >=
|
||||||
self.ast.len(): self.ast[^1] else: self.ast[self.current - 1])
|
self.ast.len(): self.ast[^1] else: self.ast[self.current - 1])
|
||||||
proc getCurrentFunction*(self: Compiler): Declaration {.inline.} = self.currentFunction
|
proc getCurrentFunction*(self: Compiler): Declaration {.inline.} = (if self.currentFunction.isNil(): nil else: self.currentFunction.fun)
|
||||||
proc getFile*(self: Compiler): string {.inline.} = self.file
|
proc getFile*(self: Compiler): string {.inline.} = self.file
|
||||||
proc getModule*(self: Compiler): string {.inline.} = self.currentModule
|
proc getModule*(self: Compiler): string {.inline.} = self.currentModule
|
||||||
|
|
||||||
|
@ -321,7 +319,7 @@ proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
||||||
self.emitByte(LoadString)
|
self.emitByte(LoadString)
|
||||||
let str = LiteralExpr(obj).literal.lexeme
|
let str = LiteralExpr(obj).literal.lexeme
|
||||||
if str.len() >= 16777216:
|
if str.len() >= 16777216:
|
||||||
self.error("string constants cannot be larger than 16777216 bytes")
|
self.error("string constants cannot be larger than 16777215 bytes")
|
||||||
self.emitBytes(LiteralExpr(obj).literal.lexeme.len().toTriple())
|
self.emitBytes(LiteralExpr(obj).literal.lexeme.len().toTriple())
|
||||||
of Float32:
|
of Float32:
|
||||||
self.emitByte(LoadFloat32)
|
self.emitByte(LoadFloat32)
|
||||||
|
@ -347,7 +345,7 @@ proc patchJump(self: Compiler, offset: int) =
|
||||||
## jump using emitJump
|
## jump using emitJump
|
||||||
var jump: int = self.chunk.code.len() - offset
|
var jump: int = self.chunk.code.len() - offset
|
||||||
if jump > 16777215:
|
if jump > 16777215:
|
||||||
self.error("cannot jump more than 16777216 bytecode instructions")
|
self.error("cannot jump more than 16777215 instructions")
|
||||||
let offsetArray = (jump - 4).toTriple()
|
let offsetArray = (jump - 4).toTriple()
|
||||||
self.chunk.code[offset + 1] = offsetArray[0]
|
self.chunk.code[offset + 1] = offsetArray[0]
|
||||||
self.chunk.code[offset + 2] = offsetArray[1]
|
self.chunk.code[offset + 2] = offsetArray[1]
|
||||||
|
@ -408,6 +406,23 @@ proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
proc resolve(self: Compiler, name: string,
|
||||||
|
depth: int = self.scopeDepth): Name =
|
||||||
|
## Traverses self.names backwards and returns the
|
||||||
|
## first name object with the given name. Returns
|
||||||
|
## nil when the name can't be found. This function
|
||||||
|
## has no concept of scope depth, because getStackPos
|
||||||
|
## does that job. Note that private names declared in
|
||||||
|
## other modules will not be resolved!
|
||||||
|
for obj in reversed(self.names):
|
||||||
|
if obj.name.token.lexeme == name:
|
||||||
|
if obj.isPrivate and obj.owner != self.currentModule:
|
||||||
|
continue # There may be a name in the current module that
|
||||||
|
# matches, so we skip this
|
||||||
|
return obj
|
||||||
|
return nil
|
||||||
|
|
||||||
|
|
||||||
proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDepth) =
|
proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDepth) =
|
||||||
## Detects if the given name is used in a local scope deeper
|
## Detects if the given name is used in a local scope deeper
|
||||||
## than the given one and modifies the code emitted for it
|
## than the given one and modifies the code emitted for it
|
||||||
|
@ -428,7 +443,7 @@ proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDe
|
||||||
self.closedOver.add(name)
|
self.closedOver.add(name)
|
||||||
let idx = self.closedOver.high().toTriple()
|
let idx = self.closedOver.high().toTriple()
|
||||||
if self.closedOver.len() >= 16777216:
|
if self.closedOver.len() >= 16777216:
|
||||||
self.error("too many consecutive closed-over variables (max is 16777216)")
|
self.error("too many consecutive closed-over variables (max is 16777215)")
|
||||||
self.chunk.code[name.codePos] = StoreClosure.uint8
|
self.chunk.code[name.codePos] = StoreClosure.uint8
|
||||||
self.chunk.code[name.codePos + 1] = idx[0]
|
self.chunk.code[name.codePos + 1] = idx[0]
|
||||||
self.chunk.code[name.codePos + 2] = idx[1]
|
self.chunk.code[name.codePos + 2] = idx[1]
|
||||||
|
@ -645,7 +660,7 @@ proc inferType(self: Compiler, node: Declaration, strictMutable: bool = true): T
|
||||||
if node.isNil():
|
if node.isNil():
|
||||||
return nil
|
return nil
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of funDecl:
|
of NodeKind.funDecl:
|
||||||
var node = FunDecl(node)
|
var node = FunDecl(node)
|
||||||
let resolved = self.resolve(node.name)
|
let resolved = self.resolve(node.name)
|
||||||
if not resolved.isNil():
|
if not resolved.isNil():
|
||||||
|
@ -678,7 +693,6 @@ proc typeToStr(self: Compiler, typ: Type): string =
|
||||||
result &= "fn ("
|
result &= "fn ("
|
||||||
for i, (argName, argType) in typ.args:
|
for i, (argName, argType) in typ.args:
|
||||||
result &= &"{argName}: "
|
result &= &"{argName}: "
|
||||||
echo argType[]
|
|
||||||
if argType.mutable:
|
if argType.mutable:
|
||||||
result &= "var "
|
result &= "var "
|
||||||
result &= self.typeToStr(argType)
|
result &= self.typeToStr(argType)
|
||||||
|
@ -693,13 +707,70 @@ proc typeToStr(self: Compiler, typ: Type): string =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
proc findByName(self: Compiler, name: string): seq[Name] =
|
||||||
|
## Looks for objects that have been already declared
|
||||||
|
## with the given name. Returns all objects that apply
|
||||||
|
for obj in reversed(self.names):
|
||||||
|
if obj.name.token.lexeme == name:
|
||||||
|
result.add(obj)
|
||||||
|
|
||||||
|
|
||||||
|
proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name] =
|
||||||
|
## Looks for objects that have already been declared
|
||||||
|
## with the given name and type
|
||||||
|
for obj in self.findByName(name):
|
||||||
|
if self.compareTypes(obj.valueType, kind, strictMutable):
|
||||||
|
result.add(obj)
|
||||||
|
|
||||||
|
|
||||||
|
proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = true): Name =
|
||||||
|
## Tries to find a matching function implementation
|
||||||
|
## compatible with the given type and returns its
|
||||||
|
## name object
|
||||||
|
let impl = self.findByType(name, kind, strictMutable)
|
||||||
|
if impl.len() == 0:
|
||||||
|
var msg = &"cannot find a suitable implementation for '{name}'"
|
||||||
|
let names = self.findByName(name)
|
||||||
|
if names.len() > 0:
|
||||||
|
msg &= &", found {len(names)} candidate"
|
||||||
|
if names.len() > 1:
|
||||||
|
msg &= "s"
|
||||||
|
msg &= ": "
|
||||||
|
for name in names:
|
||||||
|
msg &= &"\n - '{name.name.token.lexeme}' of type '{self.typeToStr(name.valueType)}'"
|
||||||
|
if name.valueType.kind != Function:
|
||||||
|
msg &= ", not a callable"
|
||||||
|
elif kind.args.len() != name.valueType.args.len():
|
||||||
|
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
||||||
|
else:
|
||||||
|
for i, arg in kind.args:
|
||||||
|
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
||||||
|
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
||||||
|
break
|
||||||
|
elif not self.compareTypes(arg.kind, name.valueType.args[i].kind):
|
||||||
|
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead"
|
||||||
|
break
|
||||||
|
self.error(msg)
|
||||||
|
elif impl.len() > 1:
|
||||||
|
var msg = &"multiple matching implementations of '{name}' found:\n"
|
||||||
|
for fn in reversed(impl):
|
||||||
|
msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||||
|
self.error(msg)
|
||||||
|
return impl[0]
|
||||||
|
|
||||||
|
|
||||||
|
proc emitFunction(self: Compiler, name: Name) =
|
||||||
|
## Wrapper to emit LoadFunction instructions
|
||||||
|
self.emitByte(LoadFunction)
|
||||||
|
self.emitBytes(name.codePos.toTriple())
|
||||||
|
|
||||||
|
|
||||||
## End of utility functions
|
## End of utility functions
|
||||||
|
|
||||||
|
|
||||||
proc literal(self: Compiler, node: ASTNode) =
|
proc literal(self: Compiler, node: ASTNode) =
|
||||||
## Emits instructions for literals such
|
## Emits instructions for literals such
|
||||||
## as singletons, strings, numbers and
|
## as singletons, strings and numbers
|
||||||
## collections
|
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of trueExpr:
|
of trueExpr:
|
||||||
self.emitByte(LoadTrue)
|
self.emitByte(LoadTrue)
|
||||||
|
@ -713,7 +784,6 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.emitByte(LoadNan)
|
self.emitByte(LoadNan)
|
||||||
of strExpr:
|
of strExpr:
|
||||||
self.emitConstant(LiteralExpr(node), Type(kind: String))
|
self.emitConstant(LiteralExpr(node), Type(kind: String))
|
||||||
# TODO: Take size specifier into account!
|
|
||||||
of intExpr:
|
of intExpr:
|
||||||
var x: int
|
var x: int
|
||||||
var y = IntExpr(node)
|
var y = IntExpr(node)
|
||||||
|
@ -778,65 +848,6 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
|
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
|
||||||
|
|
||||||
|
|
||||||
proc findByName(self: Compiler, name: string): seq[Name] =
|
|
||||||
## Looks for objects that have been already declared
|
|
||||||
## with the given name. Returns all objects that apply
|
|
||||||
for obj in reversed(self.names):
|
|
||||||
if obj.name.token.lexeme == name:
|
|
||||||
result.add(obj)
|
|
||||||
|
|
||||||
|
|
||||||
proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name] =
|
|
||||||
## Looks for objects that have already been declared
|
|
||||||
## with the given name and type
|
|
||||||
for obj in self.findByName(name):
|
|
||||||
if self.compareTypes(obj.valueType, kind, strictMutable):
|
|
||||||
result.add(obj)
|
|
||||||
|
|
||||||
|
|
||||||
proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = true): Name =
|
|
||||||
## Tries to find a matching function implementation
|
|
||||||
## compatible with the given type and returns its
|
|
||||||
## name object
|
|
||||||
let impl = self.findByType(name, kind, strictMutable)
|
|
||||||
if impl.len() == 0:
|
|
||||||
var msg = &"cannot find a suitable implementation for '{name}'"
|
|
||||||
let names = self.findByName(name)
|
|
||||||
if names.len() > 0:
|
|
||||||
msg &= &", found {len(names)} candidate"
|
|
||||||
if names.len() > 1:
|
|
||||||
msg &= "s"
|
|
||||||
msg &= ": "
|
|
||||||
for name in names:
|
|
||||||
msg &= &"\n - '{name.name.token.lexeme}' of type '{self.typeToStr(name.valueType)}'"
|
|
||||||
if name.valueType.kind != Function:
|
|
||||||
msg &= ", not a callable"
|
|
||||||
elif kind.args.len() != name.valueType.args.len():
|
|
||||||
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
|
||||||
else:
|
|
||||||
for i, arg in kind.args:
|
|
||||||
echo name.valueType.args[i].kind.mutable
|
|
||||||
echo arg.kind.mutable
|
|
||||||
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
|
||||||
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
|
||||||
break
|
|
||||||
elif not self.compareTypes(arg.kind, name.valueType.args[i].kind):
|
|
||||||
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead"
|
|
||||||
break
|
|
||||||
self.error(msg)
|
|
||||||
elif impl.len() > 1:
|
|
||||||
var msg = &"multiple matching implementations of '{name}' found:\n"
|
|
||||||
for fn in reversed(impl):
|
|
||||||
msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
|
||||||
self.error(msg)
|
|
||||||
return impl[0]
|
|
||||||
|
|
||||||
|
|
||||||
proc emitFunction(self: Compiler, name: Name) =
|
|
||||||
## Wrapper to emit LoadFunction instructions
|
|
||||||
self.emitByte(LoadFunction)
|
|
||||||
self.emitBytes(name.codePos.toTriple())
|
|
||||||
|
|
||||||
|
|
||||||
proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
## Emits single instructions for builtin functions
|
## Emits single instructions for builtin functions
|
||||||
|
@ -953,12 +964,7 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
||||||
# The function has generic arguments! We need to compile a version
|
# The function has generic arguments! We need to compile a version
|
||||||
# of it with the right type data
|
# of it with the right type data
|
||||||
|
self.funDecl(nil, fn, args)
|
||||||
# We don't want to cause *any* interference to
|
|
||||||
# other objects, so we just play it safe
|
|
||||||
var node = fn.valueType.funNode.deepCopy()
|
|
||||||
for argument in node.arguments:
|
|
||||||
|
|
||||||
self.emitFunction(fn)
|
self.emitFunction(fn)
|
||||||
self.emitByte(LoadReturnAddress)
|
self.emitByte(LoadReturnAddress)
|
||||||
let pos = self.chunk.code.len()
|
let pos = self.chunk.code.len()
|
||||||
|
@ -1041,7 +1047,7 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
||||||
if self.names.high() > 16777215:
|
if self.names.high() > 16777215:
|
||||||
# If someone ever hits this limit in real-world scenarios, I swear I'll
|
# If someone ever hits this limit in real-world scenarios, I swear I'll
|
||||||
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
|
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
|
||||||
self.error("cannot declare more than 16777216 variables at a time")
|
self.error("cannot declare more than 16777215 variables at a time")
|
||||||
for name in self.findByName(node.name.token.lexeme):
|
for name in self.findByName(node.name.token.lexeme):
|
||||||
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, CustomType} and not name.isFunctionArgument:
|
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, CustomType} and not name.isFunctionArgument:
|
||||||
# Trying to redeclare a variable in the same module is an error, but it's okay
|
# Trying to redeclare a variable in the same module is an error, but it's okay
|
||||||
|
@ -1095,7 +1101,7 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
||||||
name: node.name.token.lexeme,
|
name: node.name.token.lexeme,
|
||||||
returnType: self.inferType(node.returnType),
|
returnType: self.inferType(node.returnType),
|
||||||
args: @[],
|
args: @[],
|
||||||
funNode: node),
|
fun: node),
|
||||||
codePos: self.chunk.code.len(),
|
codePos: self.chunk.code.len(),
|
||||||
name: node.name,
|
name: node.name,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
|
@ -1105,7 +1111,7 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
||||||
var name: Name
|
var name: Name
|
||||||
for argument in node.arguments:
|
for argument in node.arguments:
|
||||||
if self.names.high() > 16777215:
|
if self.names.high() > 16777215:
|
||||||
self.error("cannot declare more than 16777216 variables at a time")
|
self.error("cannot declare more than 16777215 variables at a time")
|
||||||
# wait, no LoadVar? Yes! That's because when calling functions,
|
# wait, no LoadVar? Yes! That's because when calling functions,
|
||||||
# arguments will already be on the stack so there's no need to
|
# arguments will already be on the stack so there's no need to
|
||||||
# load them here
|
# load them here
|
||||||
|
@ -1381,8 +1387,13 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
while node.kind == callExpr:
|
while node.kind == callExpr:
|
||||||
self.callExpr(CallExpr(node))
|
self.callExpr(CallExpr(node))
|
||||||
node = CallExpr(node).callee
|
node = CallExpr(node).callee
|
||||||
|
# TODO: Calling lambdas
|
||||||
else:
|
else:
|
||||||
discard # TODO: Calling expressions
|
let typ = self.inferType(node)
|
||||||
|
if typ.isNil():
|
||||||
|
self.error(&"expression has no type")
|
||||||
|
else:
|
||||||
|
self.error(&"object of type '{self.typeToStr(typ)}' is not callable")
|
||||||
if not funct.isNil():
|
if not funct.isNil():
|
||||||
if funct.valueType.isBuiltinFunction:
|
if funct.valueType.isBuiltinFunction:
|
||||||
self.handleBuiltinFunction(funct, argExpr)
|
self.handleBuiltinFunction(funct, argExpr)
|
||||||
|
@ -1391,8 +1402,8 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
else:
|
else:
|
||||||
self.generateObjCall(argExpr)
|
self.generateObjCall(argExpr)
|
||||||
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
|
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
|
||||||
if not self.currentFunction.name.isNil():
|
if self.currentFunction.name != "":
|
||||||
self.error(&"cannot make sure that calls to '{self.currentFunction.name.token.lexeme}' are side-effect free")
|
self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free")
|
||||||
else:
|
else:
|
||||||
self.error(&"cannot make sure that call is side-effect free")
|
self.error(&"cannot make sure that call is side-effect free")
|
||||||
|
|
||||||
|
@ -1466,7 +1477,7 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
||||||
## its return instruction
|
## its return instruction
|
||||||
var popped = 0
|
var popped = 0
|
||||||
for name in self.names:
|
for name in self.names:
|
||||||
if name.depth == self.scopeDepth and name.valueType.kind != Function:
|
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic}:
|
||||||
inc(popped)
|
inc(popped)
|
||||||
if self.enableOptimizations and popped > 1:
|
if self.enableOptimizations and popped > 1:
|
||||||
self.emitByte(PopN)
|
self.emitByte(PopN)
|
||||||
|
@ -1478,12 +1489,9 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
||||||
|
|
||||||
|
|
||||||
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
## Compiles return statements. An empty return
|
## Compiles return statements
|
||||||
## implicitly returns nil
|
|
||||||
let actual = self.inferType(node.value)
|
let actual = self.inferType(node.value)
|
||||||
let expected = self.inferType(self.currentFunction)
|
let expected = self.currentFunction
|
||||||
var comp: Type = actual
|
|
||||||
## Having the return type
|
|
||||||
if actual.isNil() and not expected.returnType.isNil():
|
if actual.isNil() and not expected.returnType.isNil():
|
||||||
if not node.value.isNil():
|
if not node.value.isNil():
|
||||||
if node.value.kind == identExpr:
|
if node.value.kind == identExpr:
|
||||||
|
@ -1493,8 +1501,8 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
self.error(&"expected return value of type '{self.typeToStr(expected.returnType)}', but expression has no type")
|
self.error(&"expected return value of type '{self.typeToStr(expected.returnType)}', but expression has no type")
|
||||||
elif expected.returnType.isNil() and not actual.isNil():
|
elif expected.returnType.isNil() and not actual.isNil():
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
self.error("non-empty return statement is not allowed in void functions")
|
||||||
elif not self.compareTypes(actual, comp):
|
elif not self.compareTypes(actual, expected.returnType):
|
||||||
self.error(&"expected return value of type '{self.typeToStr(comp)}', got '{self.typeToStr(actual)}' instead")
|
self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead")
|
||||||
if not node.value.isNil():
|
if not node.value.isNil():
|
||||||
self.expression(node.value)
|
self.expression(node.value)
|
||||||
self.emitByte(OpCode.SetResult)
|
self.emitByte(OpCode.SetResult)
|
||||||
|
@ -1667,12 +1675,14 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTNode) =
|
||||||
var fn = self.resolve(node.name)
|
var fn = self.resolve(node.name)
|
||||||
fn.valueType.isBuiltinFunction = true
|
fn.valueType.isBuiltinFunction = true
|
||||||
fn.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
|
fn.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
|
||||||
|
# The magic pragma ignores the function's body
|
||||||
|
node.body = nil
|
||||||
|
|
||||||
|
|
||||||
proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTNode) =
|
proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTNode) =
|
||||||
## Handles the "pure" pragma
|
## Handles the "pure" pragma
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of funDecl:
|
of NodeKind.funDecl:
|
||||||
FunDecl(node).isPure = true
|
FunDecl(node).isPure = true
|
||||||
of lambdaExpr:
|
of lambdaExpr:
|
||||||
LambdaExpr(node).isPure = true
|
LambdaExpr(node).isPure = true
|
||||||
|
@ -1684,7 +1694,7 @@ proc dispatchPragmas(self: Compiler, node: ASTnode) =
|
||||||
## Dispatches pragmas bound to objects
|
## Dispatches pragmas bound to objects
|
||||||
var pragmas: seq[Pragma] = @[]
|
var pragmas: seq[Pragma] = @[]
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of funDecl, NodeKind.typeDecl, NodeKind.varDecl:
|
of NodeKind.funDecl, NodeKind.typeDecl, NodeKind.varDecl:
|
||||||
pragmas = Declaration(node).pragmas
|
pragmas = Declaration(node).pragmas
|
||||||
of lambdaExpr:
|
of lambdaExpr:
|
||||||
pragmas = LambdaExpr(node).pragmas
|
pragmas = LambdaExpr(node).pragmas
|
||||||
|
@ -1696,125 +1706,133 @@ proc dispatchPragmas(self: Compiler, node: ASTnode) =
|
||||||
self.compilerProcs[pragma.name.token.lexeme](self, pragma, node)
|
self.compilerProcs[pragma.name.token.lexeme](self, pragma, node)
|
||||||
|
|
||||||
|
|
||||||
proc funDecl(self: Compiler, node: FunDecl) =
|
proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type =
|
||||||
|
## Specializes generic arguments in functions
|
||||||
|
var fn = name.valueType
|
||||||
|
result = fn.deepCopy()
|
||||||
|
var node = fn.fun
|
||||||
|
for i in 0..args.high():
|
||||||
|
if fn.args[i].kind.kind == Generic:
|
||||||
|
self.resolve(fn.args[i].name).valueType = self.inferType(args[i])
|
||||||
|
|
||||||
|
|
||||||
|
proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) =
|
||||||
## Compiles function declarations
|
## Compiles function declarations
|
||||||
var function = self.currentFunction
|
if not node.isNil():
|
||||||
self.declareName(node)
|
if node.generics.len() > 0 and fn.isNil() and args.len() == 0:
|
||||||
if node.generics.len() < 0:
|
# Generic function! We can't compile it right now
|
||||||
# We can't know the type of
|
self.declareName(node)
|
||||||
# generic arguments yet, so
|
self.dispatchPragmas(node)
|
||||||
# we wait for the function to
|
return
|
||||||
# be called to compile its code
|
self.declareName(node)
|
||||||
# or dispatch any pragmas. We
|
self.dispatchPragmas(node)
|
||||||
# still declare its name so that
|
var node = node
|
||||||
# it can be assigned to variables
|
var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn
|
||||||
# and passed to functions
|
if fn.valueType.returnType.isNil():
|
||||||
return
|
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
|
||||||
self.dispatchPragmas(node)
|
|
||||||
let fn = self.names[^(node.arguments.len() + 1)]
|
|
||||||
var jmp: int
|
|
||||||
if not fn.valueType.isBuiltinFunction:
|
if not fn.valueType.isBuiltinFunction:
|
||||||
|
var function = self.currentFunction
|
||||||
|
var jmp: int
|
||||||
|
# Builtin functions map to a single
|
||||||
|
# bytecode instruction to avoid
|
||||||
|
# unnecessary overhead from peon's
|
||||||
|
# calling convention. This also means
|
||||||
|
# that peon's fast builtins can only
|
||||||
|
# be relatively simple
|
||||||
self.frames.add(self.names.high())
|
self.frames.add(self.names.high())
|
||||||
# A function's code is just compiled linearly
|
# A function's code is just compiled linearly
|
||||||
# and then jumped over
|
# and then jumped over
|
||||||
jmp = self.emitJump(JumpForwards)
|
jmp = self.emitJump(JumpForwards)
|
||||||
# Function's code starts after the jump
|
# Function's code starts after the jump
|
||||||
fn.codePos = self.chunk.code.len()
|
fn.codePos = self.chunk.code.len()
|
||||||
for argument in node.arguments:
|
# We store the current function
|
||||||
|
self.currentFunction = fn.valueType
|
||||||
|
let argLen = if node.isNil(): fn.valueType.args.len() else: node.arguments.len()
|
||||||
|
for _ in 0..<argLen:
|
||||||
# Pops off the operand stack onto the
|
# Pops off the operand stack onto the
|
||||||
# call stack
|
# call stack
|
||||||
self.emitByte(LoadArgument)
|
self.emitByte(LoadArgument)
|
||||||
if not node.returnType.isNil() and self.inferType(node.returnType).isNil():
|
if node.isNil():
|
||||||
# Are we returning a generic type?
|
# We got called back with more specific type
|
||||||
var isGeneric = false
|
# arguments: time to fix them!
|
||||||
if node.returnType.kind == identExpr:
|
self.currentFunction = self.fixGenericFunc(fn, args)
|
||||||
let name = IdentExpr(node.returnType)
|
node = self.currentFunction.fun
|
||||||
for g in node.generics:
|
elif not node.body.isNil():
|
||||||
if name == g.name:
|
if BlockStmt(node.body).code.len() == 0:
|
||||||
# Yep!
|
self.error("cannot declare function with empty body")
|
||||||
isGeneric = true
|
else:
|
||||||
break
|
discard # TODO: Forward declarations
|
||||||
if not isGeneric:
|
let impl = self.findByType(fn.name.token.lexeme, fn.valueType)
|
||||||
# Nope
|
|
||||||
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
|
|
||||||
# TODO: Forward declarations
|
|
||||||
if not node.body.isNil():
|
|
||||||
if BlockStmt(node.body).code.len() == 0 and not fn.valueType.isBuiltinFunction:
|
|
||||||
self.error("cannot declare function with empty body")
|
|
||||||
let fnType = self.inferType(node)
|
|
||||||
let impl = self.findByType(node.name.token.lexeme, fnType)
|
|
||||||
if impl.len() > 1:
|
if impl.len() > 1:
|
||||||
# Oh-oh! We found more than one implementation of
|
# We found more than one (public) implementation of
|
||||||
# the same function with the same name! Error!
|
# the same function with the same name: this is an
|
||||||
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
# error, as it would raise ambiguity when calling them
|
||||||
for fn in reversed(impl):
|
var msg = &"multiple matching implementations of '{fn.name.token.lexeme}' found:\n"
|
||||||
msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
for f in reversed(impl):
|
||||||
|
msg &= &"- '{f.name.token.lexeme}' at line {f.line} of type {self.typeToStr(f.valueType)}\n"
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
# We store the current function
|
# Since the deferred array is a linear
|
||||||
self.currentFunction = node
|
# sequence of instructions and we want
|
||||||
|
# to keep track to whose function's each
|
||||||
if not fn.valueType.isBuiltinFunction:
|
# set of deferred instruction belongs,
|
||||||
# Since the deferred array is a linear
|
# we record the length of the deferred
|
||||||
# sequence of instructions and we want
|
# array before compiling the function
|
||||||
# to keep track to whose function's each
|
# and use this info later to compile
|
||||||
# set of deferred instruction belongs,
|
# the try/finally block with the deferred
|
||||||
# we record the length of the deferred
|
# code
|
||||||
# array before compiling the function
|
var deferStart = self.deferred.len()
|
||||||
# and use this info later to compile
|
# We let our debugger know a function is starting
|
||||||
# the try/finally block with the deferred
|
let start = self.chunk.code.high()
|
||||||
# code
|
self.beginScope()
|
||||||
var deferStart = self.deferred.len()
|
for decl in BlockStmt(node.body).code:
|
||||||
# We let our debugger know a function is starting
|
self.declaration(decl)
|
||||||
let start = self.chunk.code.high()
|
let typ = self.currentFunction.returnType
|
||||||
self.beginScope()
|
var hasVal: bool = false
|
||||||
for decl in BlockStmt(node.body).code:
|
case self.currentFunction.fun.kind:
|
||||||
self.declaration(decl)
|
of NodeKind.funDecl:
|
||||||
var typ: Type
|
hasVal = self.currentFunction.fun.hasExplicitReturn
|
||||||
var hasVal: bool = false
|
of NodeKind.lambdaExpr:
|
||||||
case self.currentFunction.kind:
|
hasVal = LambdaExpr(Declaration(self.currentFunction.fun)).hasExplicitReturn
|
||||||
of NodeKind.funDecl:
|
|
||||||
typ = self.inferType(self.currentFunction)
|
|
||||||
hasVal = self.currentFunction.hasExplicitReturn
|
|
||||||
of NodeKind.lambdaExpr:
|
|
||||||
typ = self.inferType(LambdaExpr(Declaration(self.currentFunction)))
|
|
||||||
hasVal = LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn
|
|
||||||
else:
|
|
||||||
discard # Unreachable
|
|
||||||
if hasVal and self.currentFunction.returnType.isNil() and not typ.returnType.isNil():
|
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
|
||||||
elif not hasVal and not self.currentFunction.returnType.isNil():
|
|
||||||
self.error("function has an explicit return type, but no return statement was found")
|
|
||||||
self.endFunctionBeforeReturn()
|
|
||||||
hasVal = hasVal and not typ.returnType.isNil()
|
|
||||||
self.endScope(deleteNames=true, fromFunc=true)
|
|
||||||
# Terminates the function's context
|
|
||||||
self.emitByte(OpCode.Return)
|
|
||||||
if hasVal:
|
|
||||||
self.emitByte(1)
|
|
||||||
else:
|
else:
|
||||||
self.emitByte(0)
|
discard # Unreachable
|
||||||
# Function is ending!
|
if hasVal and self.currentFunction.returnType.isNil() and not typ.returnType.isNil():
|
||||||
self.chunk.cfi.add(start.toTriple())
|
self.error("non-empty return statement is not allowed in void functions")
|
||||||
self.chunk.cfi.add(self.chunk.code.high().toTriple())
|
elif not hasVal and not self.currentFunction.returnType.isNil():
|
||||||
self.chunk.cfi.add(self.frames[^1].toTriple())
|
self.error("function has an explicit return type, but no return statement was found")
|
||||||
self.chunk.cfi.add(uint8(node.arguments.len()))
|
self.endFunctionBeforeReturn()
|
||||||
if not node.name.isNil():
|
hasVal = hasVal and not typ.isNil()
|
||||||
self.chunk.cfi.add(node.name.token.lexeme.len().toDouble())
|
self.endScope(deleteNames=true, fromFunc=true)
|
||||||
var s = node.name.token.lexeme
|
# Terminates the function's context
|
||||||
if node.name.token.lexeme.len() >= uint16.high().int:
|
self.emitByte(OpCode.Return)
|
||||||
s = node.name.token.lexeme[0..uint16.high()]
|
if hasVal:
|
||||||
self.chunk.cfi.add(s.toBytes())
|
self.emitByte(1)
|
||||||
else:
|
else:
|
||||||
self.chunk.cfi.add(0.toDouble())
|
self.emitByte(0)
|
||||||
# Currently defer is not functional so we
|
# Some debugging info here
|
||||||
# just pop the instructions
|
self.chunk.cfi.add(start.toTriple())
|
||||||
for i in countup(deferStart, self.deferred.len() - 1, 1):
|
self.chunk.cfi.add(self.chunk.code.high().toTriple())
|
||||||
self.deferred.delete(i)
|
self.chunk.cfi.add(self.frames[^1].toTriple())
|
||||||
|
self.chunk.cfi.add(uint8(node.arguments.len()))
|
||||||
self.patchJump(jmp)
|
if not node.name.isNil():
|
||||||
# This makes us compile nested functions correctly
|
self.chunk.cfi.add(fn.name.token.lexeme.len().toDouble())
|
||||||
discard self.frames.pop()
|
var s = fn.name.token.lexeme
|
||||||
self.currentFunction = function
|
if s.len() >= uint16.high().int:
|
||||||
|
s = node.name.token.lexeme[0..uint16.high()]
|
||||||
|
self.chunk.cfi.add(s.toBytes())
|
||||||
|
else:
|
||||||
|
self.chunk.cfi.add(0.toDouble())
|
||||||
|
# Currently defer is not functional so we
|
||||||
|
# just pop the instructions
|
||||||
|
for _ in deferStart..self.deferred.high():
|
||||||
|
discard self.deferred.pop()
|
||||||
|
# Well, we've compiled everything: time to patch
|
||||||
|
# the jump offset
|
||||||
|
self.patchJump(jmp)
|
||||||
|
# Pops a call frame
|
||||||
|
discard self.frames.pop()
|
||||||
|
# Restores the enclosing function (if any).
|
||||||
|
# Makes nested calls work (including recursion)
|
||||||
|
self.currentFunction = function
|
||||||
|
|
||||||
|
|
||||||
proc patchReturnAddress(self: Compiler, pos: int) =
|
proc patchReturnAddress(self: Compiler, pos: int) =
|
||||||
|
|
Loading…
Reference in New Issue