Functions can now return functions which return functions... recursively. Initial work on forward declarations and function call resolution

This commit is contained in:
Mattia Giambirtone 2022-05-22 15:26:12 +02:00
parent 9f1769ab71
commit 21a9689f6c
5 changed files with 96 additions and 100 deletions

View File

@ -30,6 +30,7 @@ export ast
export token
export multibyte
type
TypeKind* = enum
## An enumeration of compile-time
@ -44,7 +45,7 @@ type
node*: ASTNode
case kind*: TypeKind:
of Function:
args*: seq[tuple[name: IdentExpr, kind: Type]]
args*: seq[Type]
returnType*: Type
else:
discard
@ -163,6 +164,8 @@ proc inferType(self: Compiler, node: LiteralExpr): Type
proc inferType(self: Compiler, node: Expression): Type
proc findByName(self: Compiler, name: string): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
proc compareTypes(self: Compiler, a, b: Type): bool
## End of forward declarations
## Public getter for nicer error formatting
@ -225,15 +228,17 @@ proc emitBytes(self: Compiler, bytarr: openarray[OpCode | uint8]) =
proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
## Adds a constant to the current chunk's constant table
## and returns its index as a 3-byte array of uint8s
var v: int
discard parseInt(val.token.lexeme, v)
case typ.kind:
of UInt8, Int8:
result = self.chunk.writeConstant([uint8(parseInt(val.token.lexeme))])
result = self.chunk.writeConstant([uint8(v)])
of Int16, UInt16:
result = self.chunk.writeConstant(parseInt(val.token.lexeme).toDouble())
result = self.chunk.writeConstant(v.toDouble())
of Int32, UInt32:
result = self.chunk.writeConstant(parseInt(val.token.lexeme).toQuad())
result = self.chunk.writeConstant(v.toQuad())
of Int64, UInt64:
result = self.chunk.writeConstant(parseInt(val.token.lexeme).toLong())
result = self.chunk.writeConstant(v.toLong())
else:
discard
@ -380,7 +385,6 @@ proc detectClosureVariable(self: Compiler, name: IdentExpr,
self.chunk.code[entry.codePos + 2] = idx[1]
self.chunk.code[entry.codePos + 3] = idx[2]
proc compareTypes(self: Compiler, a, b: Type): bool
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
## Compares two types without using information from
@ -398,7 +402,7 @@ proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
elif not self.compareTypes(a.returnType, b.returnType):
return false
for (argA, argB) in zip(a.args, b.args):
if not self.compareTypes(argA.kind, argB.kind):
if not self.compareTypes(argA, argB):
return false
return true
else:
@ -717,7 +721,7 @@ proc unary(self: Compiler, node: UnaryExpr) =
## and bitwise negation
self.expression(node.a) # Pushes the operand onto the stack
let valueType = self.inferType(node.a)
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil))
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil, args: @[valueType]))
if impl.len() == 0:
self.error(&"cannot find a suitable implementation for '{node.token.lexeme}'")
@ -808,7 +812,7 @@ proc declareName(self: Compiler, node: Declaration) =
isLet: false))
self.names[^1].valueType = self.inferType(argument.valueType)
self.names[^1].valueType.node = argument.name
fn.valueType.args.add((node.name, self.names[^1].valueType))
fn.valueType.args.add(self.names[^1].valueType)
self.emitByte(LoadVar)
self.emitBytes(self.names.high().toTriple())
else:
@ -1204,11 +1208,12 @@ proc varDecl(self: Compiler, node: VarDecl) =
proc funDecl(self: Compiler, node: FunDecl) =
## Compiles function declarations
# A function's code is just compiled linearly
# and then jumped over
let jmp = self.emitJump(Jump)
var function = self.currentFunction
self.declareName(node)
# TODO: Forward declarations
if node.body != nil:
if BlockStmt(node.body).code.len() == 0:
self.error("Cannot declare function with empty body")
@ -1223,50 +1228,49 @@ proc funDecl(self: Compiler, node: FunDecl) =
discard self.typeToStr(fn.valueType)
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
self.error(msg)
# We store the current function
var function = self.currentFunction
self.currentFunction = node
# We store the current function
self.currentFunction = node
# Since the deferred array is a linear
# sequence of instructions and we want
# to keep track to whose function's each
# set of deferred instruction belongs,
# we record the length of the deferred
# array before compiling the function
# and use this info later to compile
# the try/finally block with the deferred
# code
var deferStart = self.deferred.len()
# Since the deferred array is a linear
# sequence of instructions and we want
# to keep track to whose function's each
# set of deferred instruction belongs,
# we record the length of the deferred
# array before compiling the function
# and use this info later to compile
# the try/finally block with the deferred
# code
var deferStart = self.deferred.len()
self.blockStmt(BlockStmt(node.body))
# Yup, we're done. That was easy, huh?
# But after all functions are just named
# scopes, and we compile them just like that:
# we declare their name and arguments (before
# their body so recursion works) and then just
# handle them as a block statement (which takes
# care of incrementing self.scopeDepth so locals
# are resolved properly). There's a need for a bit
# of boilerplate code to make closures work, but
# that's about it
case self.currentFunction.kind:
of NodeKind.funDecl:
if not self.currentFunction.hasExplicitReturn:
let typ = self.inferType(self.currentFunction)
if self.currentFunction.returnType == nil and typ != nil:
self.error("non-empty return statement is not allowed in functions without an explicit return type")
if self.currentFunction.returnType != nil:
self.error("function has an explicit return type, but no explicit return statement was found")
self.emitByte(OpCode.Return)
of NodeKind.lambdaExpr:
if not LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn:
self.emitByte(OpCode.Return)
else:
discard # Unreachable
# Currently defer is not functional so we
# just pop the instructions
for i in countup(deferStart, self.deferred.len() - 1, 1):
self.deferred.delete(i)
self.blockStmt(BlockStmt(node.body))
# Yup, we're done. That was easy, huh?
# But after all functions are just named
# scopes, and we compile them just like that:
# we declare their name and arguments (before
# their body so recursion works) and then just
# handle them as a block statement (which takes
# care of incrementing self.scopeDepth so locals
# are resolved properly). There's a need for a bit
# of boilerplate code to make closures work, but
# that's about it
case self.currentFunction.kind:
of NodeKind.funDecl:
if not self.currentFunction.hasExplicitReturn:
let typ = self.inferType(self.currentFunction)
if self.currentFunction.returnType == nil and typ != nil:
self.error("non-empty return statement is not allowed in void functions")
if self.currentFunction.returnType != nil:
self.error("function has an explicit return type, but no explicit return statement was found")
self.emitByte(OpCode.Return)
of NodeKind.lambdaExpr:
if not LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn:
self.emitByte(OpCode.Return)
else:
discard # Unreachable
# Currently defer is not functional so we
# just pop the instructions
for i in countup(deferStart, self.deferred.len() - 1, 1):
self.deferred.delete(i)
self.patchJump(jmp)
# This makes us compile nested functions correctly

View File

@ -371,7 +371,8 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]], defaults: seq[Expression],
body: Statement, isGenerator: bool, isAsync: bool, token: Token,
returnType: Expression, pragmas: seq[Pragma]): LambdaExpr =
returnType: Expression, pragmas: seq[Pragma],
generics: seq[tuple[name: IdentExpr, cond: Expression]]): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr)
result.body = body
result.arguments = arguments
@ -382,6 +383,7 @@ proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression,
result.returnType = returnType
result.isPure = false
result.pragmas = pragmas
result.generics = generics
proc newGetItemExpr*(obj: Expression, name: IdentExpr,
@ -571,7 +573,7 @@ proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false,
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression, mutable: bool, isRef: bool, isPtr: bool]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
isPrivate: bool, token: Token, pragmas: seq[Pragma],
returnType: Expression): FunDecl =
returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]]): FunDecl =
result = FunDecl(kind: funDecl)
result.name = name
result.arguments = arguments
@ -584,7 +586,7 @@ proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueTyp
result.pragmas = pragmas
result.returnType = returnType
result.isPure = false
result.generics = generics
proc `$`*(self: ASTNode): string =

View File

@ -501,8 +501,7 @@ proc parseAssign(self: Parser): Expression =
of identExpr, sliceExpr:
result = newAssignExpr(result, value, tok)
of getItemExpr:
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(
result).name, value, tok)
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
else:
self.error("invalid assignment target")
@ -875,6 +874,27 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
self.error(&"missing type declaration for '{argument.name.token.lexeme}' in function declaration")
proc parseFunExpr(self: Parser): LambdaExpr =
## Parses the return value of a function
## when it is another function. Works
## recursively
var arguments: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]] = @[]
var defaults: seq[Expression] = @[]
result = newLambdaExpr(arguments, defaults, nil, isGenerator = self.peek(-1).kind == Generator,
isAsync = self.peek(-1).kind == Coroutine, token = self.peek(-1),
returnType = nil, pragmas = (@[]), generics=(@[]))
var parameter: tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]
if self.match(LeftParen):
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
if self.match([Function, Coroutine, Generator]):
result.returnType = self.parseFunExpr()
else:
result.returnType = self.expression()
proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
isLambda: bool = false, isOperator: bool = false): Declaration = # Can't use just FunDecl because it can also return LambdaExpr!
## Parses all types of functions, coroutines, generators and operators
@ -897,7 +917,8 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
isGenerator = isGenerator,
isPrivate = true,
token = tok, pragmas = (@[]),
returnType = nil)
returnType = nil,
generics=(@[]))
if self.match("*"):
FunDecl(self.currentFunction).isPrivate = false
self.checkDecl(FunDecl(self.currentFunction).isPrivate)
@ -916,7 +937,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
return result
elif isLambda:
self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator = isGenerator, isAsync = isAsync, token = tok,
returnType = nil, pragmas = (@[]))
returnType = nil, pragmas = (@[]), generics=(@[]))
if self.match(":"):
# Function has explicit return type
if self.match([Function, Coroutine, Generator]):
@ -925,68 +946,34 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
# the type declaration for a function lacks
# the braces that would qualify it as an
# expression
var arguments: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]] = @[]
var defaults: seq[Expression] = @[]
returnType = newLambdaExpr(arguments, defaults, nil, isGenerator = self.peek(-1).kind == Generator,
isAsync = self.peek(
-1).kind ==
Coroutine,
token = self.peek(
-1),
returnType = nil,
pragmas = (
@[]))
var parameter: tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]
if self.match(LeftParen):
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
LambdaExpr(returnType).returnType = self.expression()
returnType = self.parseFunExpr()
else:
returnType = self.expression()
if not self.match(LeftBrace):
self.expect(LeftParen)
if self.match(LeftParen):
var parameter: tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
# Function's return type
if self.match([Function, Coroutine, Generator]):
var arguments: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]] = @[]
var defaults: seq[Expression] = @[]
returnType = newLambdaExpr(arguments, defaults, nil, isGenerator = self.peek(-1).kind == Generator,
isAsync = self.peek(
-1).kind ==
Coroutine,
token = self.peek(
-1),
returnType = nil,
pragmas = (
@[]))
var parameter: tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]
if self.match(LeftParen):
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
LambdaExpr(returnType).returnType = self.expression()
returnType = self.parseFunExpr()
else:
returnType = self.expression()
self.expect(LeftBrace)
if self.currentFunction.kind == funDecl:
if not self.match(Semicolon):
# If we don't find a semicolon,
# it's not a forward declaration
self.expect(LeftBrace)
FunDecl(self.currentFunction).body = self.blockStmt()
else:
# This is a forward declaration so we explicitly
# This is a forward declaration, so we explicitly
# nullify the function's body to tell the compiler
# to look for it elsewhere in the file later
FunDecl(self.currentFunction).body = nil
FunDecl(self.currentFunction).arguments = arguments
FunDecl(self.currentFunction).returnType = returnType
else:
self.expect(LeftBrace)
LambdaExpr(Expression(self.currentFunction)).body = self.blockStmt()
LambdaExpr(Expression(self.currentFunction)).arguments = arguments
LambdaExpr(Expression(self.currentFunction)).returnType = returnType
@ -1089,6 +1076,7 @@ proc parsePragma(self: Parser): Pragma =
break
else:
var decl = self.currentFunction
# TODO
proc declaration(self: Parser): Declaration =

View File

@ -131,6 +131,7 @@ proc repl =
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
raise
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line

View File

@ -2,4 +2,5 @@ operator `+`(a: int): int {
return a;
}
+1;
+1; # Works: defined for int64
+1'u8; # No definition for int8: error!