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
@ -1967,6 +1971,9 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool
self.emitByte(LoadInf, node.token.line)
else:
discard # Unreachable
else:
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)
@ -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,6 +2286,22 @@ proc call(self: Compiler, node: CallExpr, compile: bool = true): Type {.discarda
self.funDecl(FunDecl(result.fun), impl)
result = result.returnType
if compile:
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()()
@ -2718,6 +2748,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
return
let stackIdx = self.stackIndex
self.stackIndex = name.position
if not node.isTemplate:
# A function's code is just compiled linearly
# and then jumped over
name.valueType.compiled = true
@ -2728,7 +2759,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
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()
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():

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")
@ -1039,7 +1039,7 @@ proc parseFunExpr(self: Parser): LambdaExpr =
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