Added template support and related test

This commit is contained in:
Mattia Giambirtone 2022-12-06 12:55:05 +01:00
parent 405c47cd56
commit af3c7234be
7 changed files with 147 additions and 100 deletions

View File

@ -1383,6 +1383,8 @@ proc endScope(self: Compiler) =
# Automatic functions do not materialize
# at runtime, so their arguments don't either
continue
if not name.isReal:
continue
inc(popCount)
if not name.resolved:
case name.kind:
@ -1391,7 +1393,7 @@ proc endScope(self: Compiler) =
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
of NameKind.Argument:
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
if not name.belongsTo.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal:
if not name.belongsTo.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved:
# Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for
# example as a result of generic specialization) because such objects do
@ -1527,6 +1529,8 @@ proc declare(self: Compiler, node: ASTNode): Name {.discardable.} =
kind: NameKind.Function,
belongsTo: self.currentFunction,
isReal: true)
if node.isTemplate:
fn.valueType.compiled = true
if node.generics.len() > 0:
fn.isGeneric = true
var typ: Type
@ -1968,10 +1972,13 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
else:
discard # Unreachable
else:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
if not s.belongsTo.isNil() and s.belongsTo.valueType.fun.kind == funDecl and FunDecl(s.belongsTo.valueType.fun).isTemplate:
discard
else:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
@ -2119,7 +2126,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
belongsTo: fn,
kind: NameKind.Argument,
node: argument.name,
position: self.stackIndex
position: self.stackIndex,
isReal: not node.isTemplate
))
if node.arguments.high() - node.defaults.high() <= node.arguments.high():
# There's a default argument!
@ -2133,6 +2141,8 @@ proc prepareFunction(self: Compiler, fn: Name) =
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType)
fn.position = self.stackIndex
self.stackIndex = idx
if node.isTemplate:
fn.valueType.compiled = true
proc prepareAutoFunction(self: Compiler, fn: Name, args: seq[tuple[name: string, kind: Type, default: Expression]]): Name =
@ -2170,14 +2180,18 @@ proc prepareAutoFunction(self: Compiler, fn: Name, args: seq[tuple[name: string,
belongsTo: fn,
kind: NameKind.Argument,
node: argument.name,
position: self.stackIndex
position: self.stackIndex,
isReal: not node.isTemplate
))
if node.isTemplate:
fn.valueType.compiled = true
fn.valueType.args = args
fn.position = self.stackIndex
self.stackIndex = idx
return fn
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
## Small wrapper that abstracts emitting a call instruction
## for a given function
@ -2272,7 +2286,23 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
self.funDecl(FunDecl(result.fun), impl)
result = result.returnType
if compile:
self.generateCall(impl, argExpr, node.token.line)
if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate:
for arg in reversed(argExpr):
self.expression(arg)
let code = BlockStmt(FunDecl(impl.valueType.fun).body).code
for i, decl in code:
if i < code.high():
self.declaration(decl)
else:
# The last expression in a template
# is its return type, so we compute
# it, but don't pop it off the stack
if decl.kind == exprStmt:
self.expression(ExprStmt(decl).expression)
else:
self.declaration(decl)
else:
self.generateCall(impl, argExpr, node.token.line)
of NodeKind.callExpr:
# Calling a call expression, like hello()()
var node: Expression = node
@ -2718,84 +2748,85 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
return
let stackIdx = self.stackIndex
self.stackIndex = name.position
# A function's code is just compiled linearly
# and then jumped over
name.valueType.compiled = true
jmp = self.emitJump(JumpForwards, node.token.line)
name.codePos = self.chunk.code.len()
name.valueType.location = name.codePos
# We let our debugger know this function's boundaries
self.chunk.functions.add(self.chunk.code.len().toTriple())
self.functions.add((start: self.chunk.code.len(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
var offset = self.functions[^1]
let idx = self.chunk.functions.len()
self.chunk.functions.add(0.toTriple()) # Patched it later
self.chunk.functions.add(uint8(node.arguments.len()))
if not node.name.isNil():
self.chunk.functions.add(name.ident.token.lexeme.len().toDouble())
var s = name.ident.token.lexeme
if s.len() >= uint16.high().int:
s = node.name.token.lexeme[0..uint16.high()]
self.chunk.functions.add(s.toBytes())
else:
self.chunk.functions.add(0.toDouble())
if BlockStmt(node.body).code.len() == 0:
self.error("cannot declare function with empty body")
# 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()
var last: Declaration
self.beginScope()
for decl in BlockStmt(node.body).code:
if not last.isNil():
if last.kind == returnStmt:
self.warning(UnreachableCode, "code after 'return' statement is unreachable")
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
if not node.isTemplate:
# A function's code is just compiled linearly
# and then jumped over
name.valueType.compiled = true
jmp = self.emitJump(JumpForwards, node.token.line)
name.codePos = self.chunk.code.len()
name.valueType.location = name.codePos
# We let our debugger know this function's boundaries
self.chunk.functions.add(self.chunk.code.len().toTriple())
self.functions.add((start: self.chunk.code.len(), stop: 0, pos: self.chunk.functions.len() - 3, fn: name))
var offset = self.functions[^1]
var idx = self.chunk.functions.len()
self.chunk.functions.add(0.toTriple()) # Patched it later
self.chunk.functions.add(uint8(node.arguments.len()))
if not node.name.isNil():
self.chunk.functions.add(name.ident.token.lexeme.len().toDouble())
var s = name.ident.token.lexeme
if s.len() >= uint16.high().int:
s = node.name.token.lexeme[0..uint16.high()]
self.chunk.functions.add(s.toBytes())
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)
# Currently defer is not functional, so we
# just pop the instructions
for _ in deferStart..self.deferred.high():
discard self.deferred.pop()
let stop = self.chunk.code.len().toTriple()
self.chunk.functions[idx] = stop[0]
self.chunk.functions[idx + 1] = stop[1]
self.chunk.functions[idx + 2] = stop[2]
offset.stop = self.chunk.code.len()
# Well, we've compiled everything: time to patch
# the jump offset
self.patchJump(jmp)
self.chunk.functions.add(0.toDouble())
if BlockStmt(node.body).code.len() == 0:
self.error("cannot declare function with empty body")
# 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()
var last: Declaration
self.beginScope()
for decl in BlockStmt(node.body).code:
if not last.isNil():
if last.kind == returnStmt:
self.warning(UnreachableCode, "code after 'return' statement is unreachable")
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)
# Currently defer is not functional, so we
# just pop the instructions
for _ in deferStart..self.deferred.high():
discard self.deferred.pop()
let stop = self.chunk.code.len().toTriple()
self.chunk.functions[idx] = stop[0]
self.chunk.functions[idx + 1] = stop[1]
self.chunk.functions[idx + 2] = stop[2]
offset.stop = self.chunk.code.len()
# 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

View File

@ -264,6 +264,7 @@ type
defaults*: seq[Expression]
isAsync*: bool
isGenerator*: bool
isTemplate*: bool
isPure*: bool
returnType*: Expression
hasExplicitReturn*: bool
@ -737,7 +738,7 @@ proc `$`*(self: ASTNode): string =
result &= &"Var(name={self.name}, value={self.value}, const={self.isConst}, private={self.isPrivate}, type={self.valueType}, pragmas={self.pragmas})"
of funDecl:
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}, template={self.isTemplate}, private={self.isPrivate}, pragmas={self.pragmas})"""
of typeDecl:
var self = TypeDecl(self)
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, defaults={self.defaults}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, parent={self.parent}, ref={self.isRef}, enum={self.isEnum})"""

View File

@ -38,7 +38,7 @@ type
Yield, Defer, Try, Except,
Finally, Type, Operator, Case,
Enum, From, Ptr, Ref, Object,
Export, Block
Export, Block, Template
# Literal types
Integer, Float, String, Identifier,

View File

@ -301,7 +301,7 @@ proc varDecl(self: Parser, isLet: bool = false,
isConst: bool = false): Declaration
proc parseFunExpr(self: Parser): LambdaExpr
proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
isLambda: bool = false, isOperator: bool = false): Declaration
isLambda: bool = false, isOperator: bool = false, isTemplate: bool = false): Declaration
proc declaration(self: Parser): Declaration
proc parse*(self: Parser, tokens: seq[Token], file: string, lines: seq[tuple[start, stop: int]], source: string, persist: bool = false): seq[Declaration]
# End of forward declarations
@ -335,7 +335,7 @@ proc primary(self: Parser): Expression =
self.expect(RightParen, "unterminated parenthesized expression")
of Yield:
let tok = self.step()
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'yield' cannot be used outside functions", tok)
elif self.currentFunction.token.kind != Generator:
# It's easier than doing conversions for lambda/funDecl
@ -349,7 +349,7 @@ proc primary(self: Parser): Expression =
result.file = self.file
of Await:
let tok = self.step()
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'await' cannot be used outside functions", tok)
if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines", tok)
@ -659,7 +659,7 @@ proc breakStmt(self: Parser): Statement =
proc deferStmt(self: Parser): Statement =
## Parses defer statements
let tok = self.peek(-1)
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'defer' cannot be used outside functions")
endOfLine("missing semicolon after 'defer'")
result = newDeferStmt(self.expression(), tok)
@ -683,7 +683,7 @@ proc continueStmt(self: Parser): Statement =
proc returnStmt(self: Parser): Statement =
## Parses return statements
let tok = self.peek(-1)
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'return' cannot be used outside functions")
var value: Expression
if not self.check(Semicolon):
@ -704,7 +704,7 @@ proc returnStmt(self: Parser): Statement =
proc yieldStmt(self: Parser): Statement =
## Parses yield statements
let tok = self.peek(-1)
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'yield' cannot be outside functions")
elif self.currentFunction.token.kind != Generator:
self.error("'yield' can only be used inside generators")
@ -719,7 +719,7 @@ proc yieldStmt(self: Parser): Statement =
proc awaitStmt(self: Parser): Statement =
## Parses await statements
let tok = self.peek(-1)
if self.currentFunction.isNil():
if self.currentFunction.isNil() or (self.currentFunction.kind == funDecl and FunDecl(self.currentFunction).isTemplate):
self.error("'await' cannot be used outside functions")
if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines")
@ -1033,13 +1033,13 @@ proc parseFunExpr(self: Parser): LambdaExpr =
var arguments: seq[tuple[name: IdentExpr, valueType: Expression]] = @[]
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),
isAsync=self.peek(-1).kind == Coroutine, token=self.peek(-1),
returnType=nil, depth=self.scopeDepth)
var parameter: tuple[name: IdentExpr, valueType: Expression]
if self.match(LeftParen):
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
if self.match([Function, Coroutine, Generator]):
if self.match([Function, Coroutine, Generator, Template]):
result.returnType = self.parseFunExpr()
else:
result.returnType = self.expression()
@ -1082,7 +1082,7 @@ proc parseGenerics(self: Parser, decl: Declaration) =
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!
isLambda: bool = false, isOperator: bool = false, isTemplate: bool = false): Declaration = # Can't use just FunDecl because it can also return LambdaExpr!
## Parses all types of functions, coroutines, generators and operators
## (with or without a name, where applicable)
let tok = self.peek(-1)
@ -1128,7 +1128,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
returnType=nil, depth=self.scopeDepth)
if self.match(":"):
# Function has explicit return type
if self.match([Function, Coroutine, Generator]):
if self.match([Function, Coroutine, Generator, Template]):
# The function's return type is another
# function. We specialize this case because
# the type declaration for a function lacks
@ -1142,7 +1142,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
self.parseDeclArguments(arguments, parameter, defaults)
if self.match(":"):
# Function's return type
if self.match([Function, Coroutine, Generator]):
if self.match([Function, Coroutine, Generator, Template]):
returnType = self.parseFunExpr()
else:
returnType = self.expression()
@ -1154,6 +1154,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false,
if self.match(TokenType.Pragma):
for pragma in self.parsePragmas():
pragmas.add(pragma)
FunDecl(self.currentFunction).isTemplate = isTemplate
FunDecl(self.currentFunction).body = self.blockStmt()
else:
# This is a forward declaration, so we explicitly
@ -1344,6 +1345,9 @@ proc declaration(self: Parser): Declaration =
of Function:
discard self.step()
result = self.funDecl()
of Template:
discard self.step()
result = self.funDecl(isTemplate=true)
of Coroutine:
discard self.step()
result = self.funDecl(isAsync=true)

View File

@ -1,6 +1,5 @@
# Stub type declarations for peon's intrinsic types
type int64* = object {
#pragma[magic: "int64"]
}

View File

@ -56,6 +56,7 @@ proc fillSymbolTable*(tokenizer: Lexer) =
tokenizer.symbols.addKeyword("ref", TokenType.Ref)
tokenizer.symbols.addKeyword("ptr", TokenType.Ptr)
tokenizer.symbols.addKeyword("block", TokenType.Block)
tokenizer.symbols.addKeyword("template", TokenType.Template)
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^",
">>", "<<"]:

11
tests/templates.pn Normal file
View File

@ -0,0 +1,11 @@
# A test for templates
import std;
template sum[T: Integer](a, b: T): T {
a + b;
}
print(sum(1, 2) == 3); # true
print(sum(1'i32, 2'i32) == 3'i32); # true