aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 05:43:35 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 05:43:35 +0000
commitdd5bbae055fd34ce2e12ef63f8a261c6b15dc83e (patch)
tree757e367daacf92c50148eb42d7bda97ba33ed3fb
parentc682f983b0dce3acba76d1f08719a26b9c52c5ec (diff)
feat(parser): parse TEMPLATE declarations, template ids and parameter packs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--include/cps/CodeGen.h3
-rw-r--r--include/cps/FunctionAST.h15
-rw-r--r--include/cps/Lexer.h1
-rw-r--r--include/cps/Parser.h7
-rw-r--r--include/cps/TemplateAST.h43
-rw-r--r--lib/CodeGen/CodeGen.cc7
-rw-r--r--lib/CodeGen/StatementGen.cc2
-rw-r--r--lib/Lexer/Lexer.cc1
-rw-r--r--lib/Parser/Parser.cc17
-rw-r--r--lib/Parser/ParserExpression.cc55
-rw-r--r--lib/Parser/ParserFunction.cc38
-rw-r--r--lib/Parser/ParserType.cc162
-rw-r--r--tests/i/template-unused/case.json1
-rw-r--r--tests/i/template-unused/expected.txt1
-rw-r--r--tests/i/template-unused/lib.cps48
-rw-r--r--tests/i/template-unused/notes.md5
-rw-r--r--tests/i/template-unused/program.cps2
-rw-r--r--tests/iii/template-header-errors/case.json36
-rw-r--r--tests/iii/template-header-errors/program.cps39
-rw-r--r--tests/iii/template-pack-errors/case.json32
-rw-r--r--tests/iii/template-pack-errors/program.cps32
21 files changed, 536 insertions, 11 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index e6281c5..283c4b1 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -25,6 +25,7 @@ class MemoryHandler;
class ExceptionHandler;
class CastExprAST;
class NewExprAST;
+class TemplateDeclAST;
class CodeGen {
std::unique_ptr<llvm::LLVMContext> TheContext;
@@ -35,6 +36,8 @@ class CodeGen {
size_t CurrentUnit = 0;
size_t MainUnit = 0;
IdentifierMap<size_t> TypeOwners;
+ struct TemplateDefinition { TemplateDeclAST *Decl; size_t OwnerUnit; };
+ IdentifierMap<std::vector<TemplateDefinition>> Templates;
std::vector<IdentifierMap<SymbolInfo>> Exports;
bool isVisible(size_t Owner) const;
void selectUnit(size_t Unit);
diff --git a/include/cps/FunctionAST.h b/include/cps/FunctionAST.h
index 90ef5c4..9188015 100644
--- a/include/cps/FunctionAST.h
+++ b/include/cps/FunctionAST.h
@@ -23,6 +23,7 @@ struct ParamDecl {
int Rank = 0; // 0 = scalar
std::vector<std::pair<int64_t, int64_t>> DeclaredBounds;
int Line = 0;
+ bool IsPack = false;
};
class PrototypeAST {
@@ -87,25 +88,31 @@ public:
class CallExprAST : public ExprAST {
std::string Callee;
std::vector<std::unique_ptr<ExprAST>> Args;
+ std::vector<std::string> TemplateArgs;
public:
- CallExprAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args, int Line)
- : ExprAST(Line), Callee(Callee), Args(std::move(Args)) {}
+ CallExprAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args, int Line,
+ std::vector<std::string> TemplateArgs = {})
+ : ExprAST(Line), Callee(Callee), Args(std::move(Args)), TemplateArgs(std::move(TemplateArgs)) {}
const std::string &getCallee() const { return Callee; }
const std::vector<std::unique_ptr<ExprAST>> &getArgs() const { return Args; }
+ const std::vector<std::string> &getTemplateArgs() const { return TemplateArgs; }
};
class CallStmtAST : public StmtAST {
std::string Callee;
std::vector<std::unique_ptr<ExprAST>> Args;
+ std::vector<std::string> TemplateArgs;
public:
- CallStmtAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args, int Line)
- : StmtAST(Line), Callee(Callee), Args(std::move(Args)) {}
+ CallStmtAST(const std::string &Callee, std::vector<std::unique_ptr<ExprAST>> Args, int Line,
+ std::vector<std::string> TemplateArgs = {})
+ : StmtAST(Line), Callee(Callee), Args(std::move(Args)), TemplateArgs(std::move(TemplateArgs)) {}
const std::string &getCallee() const { return Callee; }
const std::vector<std::unique_ptr<ExprAST>> &getArgs() const { return Args; }
+ const std::vector<std::string> &getTemplateArgs() const { return TemplateArgs; }
};
diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h
index b514904..0ac2068 100644
--- a/include/cps/Lexer.h
+++ b/include/cps/Lexer.h
@@ -103,6 +103,7 @@ enum Token {
tok_c_string = -611,
tok_c_double = -612,
tok_new = -613, tok_delete = -614,
+ tok_template = -615,
tok_include = -650, tok_global = -651, tok_endglobal = -652,
tok_include_obj = -653, tok_extern = -654,
diff --git a/include/cps/Parser.h b/include/cps/Parser.h
index eedc3f8..f2c0868 100644
--- a/include/cps/Parser.h
+++ b/include/cps/Parser.h
@@ -4,6 +4,7 @@
#include "cps/AST.h"
#include "cps/TypeAST.h"
#include "cps/FunctionAST.h"
+#include "cps/TemplateAST.h"
#include <cstdint>
#include <vector>
#include <initializer_list>
@@ -23,6 +24,8 @@ class Parser {
std::vector<unsigned> CaseBodyDepths;
unsigned NumErrors = 0;
unsigned BlockDepth = 0; // ParseBlock nesting; 1 = top level
+ std::vector<TemplateParam> CurrentTemplate;
+ IdentifierSet PackNames;
std::map<int, int> BinopPrecedence;
IdentifierSet TypeNames = {
@@ -49,6 +52,9 @@ class Parser {
std::unique_ptr<MemberCallExprAST> ParseMemberCall(
const std::string &Name, std::vector<DesignatorAccess> Accesses, int Line);
std::string ParseTypeName(bool AllowVoid = false);
+ std::optional<std::vector<std::string>> ParseTemplateArgs();
+ bool isTemplateCall();
+ bool isTemplatePack(const std::string &Name) const;
std::unique_ptr<StmtAST> ParseStatement();
std::unique_ptr<StmtAST> ParseStatementImpl();
@@ -70,6 +76,7 @@ class Parser {
bool ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out);
std::unique_ptr<StmtAST> ParseTypeDecl();
+ std::unique_ptr<StmtAST> ParseTemplateDecl();
std::optional<std::vector<RecordFieldDecl>> ParseRecordBody(
const std::string &Name, int Line, int EndToken,
std::vector<std::unique_ptr<FunctionDefAST>> *Methods = nullptr);
diff --git a/include/cps/TemplateAST.h b/include/cps/TemplateAST.h
new file mode 100644
index 0000000..c338b66
--- /dev/null
+++ b/include/cps/TemplateAST.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "cps/FunctionAST.h"
+#include "cps/TypeAST.h"
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace cps {
+
+struct TemplateParam {
+ std::string Name;
+ bool IsPack = false;
+ int Line = 0;
+};
+
+class TemplateDeclAST : public StmtAST {
+ std::vector<TemplateParam> Params;
+ std::unique_ptr<FunctionDefAST> Function;
+ std::unique_ptr<ClassTypeDeclAST> Class;
+public:
+ TemplateDeclAST(std::vector<TemplateParam> Params, std::unique_ptr<FunctionDefAST> Function)
+ : Params(std::move(Params)), Function(std::move(Function)) {}
+ TemplateDeclAST(std::vector<TemplateParam> Params, std::unique_ptr<ClassTypeDeclAST> Class)
+ : Params(std::move(Params)), Class(std::move(Class)) {}
+ const auto &getParams() const { return Params; }
+ const std::string &getName() const {
+ return Function ? Function->getProto()->getName() : Class->getName();
+ }
+ bool isVariadic() const { return !Params.empty() && Params.back().IsPack; }
+ FunctionDefAST *getFunction() const { return Function.get(); }
+ ClassTypeDeclAST *getClass() const { return Class.get(); }
+};
+
+class PackExpansionExprAST : public ExprAST {
+ std::string Name;
+public:
+ PackExpansionExprAST(std::string Name, int Line)
+ : ExprAST(Line), Name(std::move(Name)) {}
+ const std::string &getName() const { return Name; }
+};
+
+} // namespace cps
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 30b6131..641b424 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -6,6 +6,7 @@
#include "cps/ExceptionHandler.h"
#include "Builtins.h"
#include "cps/TypeAST.h"
+#include "cps/TemplateAST.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Verifier.h"
#include <cstdarg>
@@ -209,7 +210,11 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
for (const auto &S : Sources[I].Statements) {
SaveAndRestore<int> Location(DiagnosticLine, S->getLine());
std::string TypeName;
- if (auto *F = dynamic_cast<FunctionDefAST*>(S.get())) {
+ if (auto *T = dynamic_cast<TemplateDeclAST*>(S.get())) {
+ auto *Function = T->getFunction();
+ Reserve(T->getName(), Function != nullptr, Function && Function->getProto()->isOverload());
+ Templates[T->getName()].push_back({T, I});
+ } else if (auto *F = dynamic_cast<FunctionDefAST*>(S.get())) {
Reserve(F->getProto()->getName(), true, F->getProto()->isOverload());
if (sameIdentifier(F->getProto()->getName(), "MAIN")) {
if (Entry) reportError("Multiple MAIN definitions in %s and %s",
diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc
index 5b60d29..6bc4ec4 100644
--- a/lib/CodeGen/StatementGen.cc
+++ b/lib/CodeGen/StatementGen.cc
@@ -5,6 +5,7 @@
#include "cps/MemoryHandler.h"
#include "cps/ExceptionHandler.h"
#include "cps/TypeAST.h"
+#include "cps/TemplateAST.h"
using namespace llvm;
using namespace cps;
@@ -18,6 +19,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) {
void CodeGen::emitStmtImpl(StmtAST *Stmt) {
SaveAndRestore<int> Location(DiagnosticLine, Stmt && Stmt->getLine() ? Stmt->getLine() : DiagnosticLine);
if (!Stmt) return;
+ if (dynamic_cast<TemplateDeclAST*>(Stmt)) return;
if (dynamic_cast<IncludeStmtAST*>(Stmt)) return;
if (dynamic_cast<GlobalBlockAST*>(Stmt)) return;
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index 6d0246a..91ce88a 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -221,6 +221,7 @@ int Lexer::readToken() {
if (IdentifierStr == "OPERATOR") return tok_operator;
if (IdentifierStr == "OVERLOAD") return tok_overload;
+ if (IdentifierStr == "TEMPLATE") return tok_template;
if (IdentifierStr == "FUNCTION") return tok_function;
if (IdentifierStr == "ENDFUNCTION") return tok_endfunction;
if (IdentifierStr == "PROCEDURE") return tok_procedure;
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index 48f4df4..3d45a97 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -51,7 +51,7 @@ void Parser::syncToStatementStart() {
case tok_delete: case tok_constant: case tok_declare: case tok_identifier: case tok_operator: case tok_input: case tok_output:
case tok_case: case tok_endcase: case tok_otherwise:
case tok_if: case tok_while: case tok_repeat: case tok_for:
- case tok_overload: case tok_function: case tok_procedure: case tok_call: case tok_return:
+ case tok_template: case tok_overload: case tok_function: case tok_procedure: case tok_call: case tok_return:
case tok_else: case tok_endif: case tok_endwhile: case tok_until:
case tok_next: case tok_endfunction: case tok_endprocedure:
case tok_openfile: case tok_readfile: case tok_writefile: case tok_closefile:
@@ -97,6 +97,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatement() {
}
std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
+ if (CurTok == tok_template) return ParseTemplateDecl();
if (CurTok == tok_extern) {
if (BlockDepth != 1) {
sourceDiagnostic(Lex.getLine(), "error: EXTERN is only allowed at top level at line %d\n", Lex.getLine());
@@ -295,17 +296,27 @@ std::unique_ptr<StmtAST> Parser::ParseAssignStmt() {
getNextToken();
}
+ std::vector<std::string> TemplateArgs;
+ if (CurTok == '<') {
+ auto Parsed = ParseTemplateArgs();
+ if (!Parsed) return nullptr;
+ TemplateArgs = std::move(*Parsed);
+ if (CurTok != '(') {
+ sourceDiagnostic(Line, "error: Expected '(' after explicit template arguments at line %d\n", Line);
+ return nullptr;
+ }
+ }
if (CurTok == '(') {
getNextToken();
auto Args = ParseExprList(')', true);
if (!Args) return nullptr;
if (CurTok == '.') {
- auto Result = ParseResultMethods(std::make_unique<CallExprAST>(Name, std::move(*Args), Line), Line);
+ auto Result = ParseResultMethods(std::make_unique<CallExprAST>(Name, std::move(*Args), Line, std::move(TemplateArgs)), Line);
if (!Result) return nullptr;
return std::make_unique<MemberCallStmtAST>(
std::unique_ptr<MemberCallExprAST>(static_cast<MemberCallExprAST *>(Result.release())));
}
- return std::make_unique<CallStmtAST>(Name, std::move(*Args), Line);
+ return std::make_unique<CallStmtAST>(Name, std::move(*Args), Line, std::move(TemplateArgs));
}
std::vector<DesignatorAccess> Accesses;
diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc
index be22b4e..ef8e1d3 100644
--- a/lib/Parser/ParserExpression.cc
+++ b/lib/Parser/ParserExpression.cc
@@ -3,6 +3,7 @@
#include "cps/FunctionAST.h"
#include <cstdio>
#include <limits>
+#include <functional>
using namespace cps;
@@ -40,6 +41,39 @@ std::unique_ptr<ExprAST> Parser::ParseNumberExpr() {
return std::make_unique<IntegerExprAST>(Value);
}
+// Grammar lookahead distinguishes a template call from a comparison without
+// consuming tokens. Like C#, A < B, C > (D) selects the template-call grammar.
+bool Parser::isTemplateCall() {
+ if (CurTok != '<') return false;
+ unsigned Offset = 0;
+ auto IsTypeToken = [](int Tok) {
+ return Tok == tok_identifier || Tok == tok_integer_kw || Tok == tok_real_kw ||
+ Tok == tok_boolean_kw || Tok == tok_string_kw || Tok == tok_char_kw ||
+ Tok == tok_c_int || Tok == tok_c_char || Tok == tok_c_string || Tok == tok_c_double;
+ };
+ std::function<bool()> Type = [&] {
+ if (!IsTypeToken(Lex.peekToken(Offset))) return false;
+ ++Offset;
+ if (Lex.peekToken(Offset) == '<') {
+ ++Offset;
+ if (!Type()) return false;
+ while (Lex.peekToken(Offset) == ',') {
+ ++Offset;
+ if (!Type()) return false;
+ }
+ if (Lex.peekToken(Offset++) != '>') return false;
+ }
+ if (Lex.peekToken(Offset) == tok_ellipsis) ++Offset;
+ return true;
+ };
+ if (!Type()) return false;
+ while (Lex.peekToken(Offset) == ',') {
+ ++Offset;
+ if (!Type()) return false;
+ }
+ return Lex.peekToken(Offset) == '>' && Lex.peekToken(Offset + 1) == '(';
+}
+
std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
int Line = Lex.getLine();
std::string IdName;
@@ -52,6 +86,25 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
getNextToken();
}
+ if (PackNames.count(IdName)) {
+ if (CurTok != tok_ellipsis) {
+ sourceDiagnostic(Line, "error: A parameter pack must be expanded with '...' at line %d\n", Line);
+ return nullptr;
+ }
+ getNextToken();
+ return std::make_unique<PackExpansionExprAST>(IdName, Line);
+ }
+ if (CurTok == tok_ellipsis) {
+ sourceDiagnostic(Line, "error: Only a parameter pack may be expanded at line %d\n", Line);
+ getNextToken();
+ return nullptr;
+ }
+ std::vector<std::string> TemplateArgs;
+ if (isTemplateCall()) {
+ auto Parsed = ParseTemplateArgs();
+ if (!Parsed) return nullptr;
+ TemplateArgs = std::move(*Parsed);
+ }
if (CurTok == '(' && !atCaseBoundary()) {
getNextToken();
auto Args = ParseExprList(')', true);
@@ -61,7 +114,7 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
Lex.getLine());
return nullptr;
}
- return ParseResultMethods(std::make_unique<CallExprAST>(IdName, std::move(*Args), Line), Line);
+ return ParseResultMethods(std::make_unique<CallExprAST>(IdName, std::move(*Args), Line, std::move(TemplateArgs)), Line);
}
std::vector<DesignatorAccess> Accesses;
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index cf8ecf8..a1f04c0 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -106,6 +106,21 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
cps::sourceDiagnostic(Lex.getLine(), "error: Expected argument type for '%s'\n", P.Name.c_str());
return std::nullopt;
}
+ if (CurTok == tok_ellipsis) {
+ if (P.IsArray || !isTemplatePack(P.TypeName)) {
+ sourceDiagnostic(P.Line, "error: A pack parameter must use the current template pack and cannot be ARRAY at line %d\n", P.Line);
+ return std::nullopt;
+ }
+ P.IsPack = true;
+ getNextToken();
+ if (CurTok != ')') {
+ sourceDiagnostic(P.Line, "error: A pack parameter must be last at line %d\n", P.Line);
+ return std::nullopt;
+ }
+ } else if (isTemplatePack(P.TypeName)) {
+ sourceDiagnostic(P.Line, "error: A template parameter pack requires '...' at line %d\n", P.Line);
+ return std::nullopt;
+ }
for (const ParamDecl &Prev : Params) {
if (sameIdentifier(Prev.Name, P.Name)) {
@@ -198,7 +213,11 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), RetType, false, OwnerClass, IsPublic, IsOverload);
Proto->setOperator(Operator);
+ auto SavedPacks = std::move(PackNames);
+ PackNames.clear();
+ for (const auto &P : Proto->getParams()) if (P.IsPack) PackNames.insert(P.Name);
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_endfunction});
+ PackNames = std::move(SavedPacks);
if (CurTok != tok_endfunction) {
cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDFUNCTION\n");
@@ -250,7 +269,11 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID", false, OwnerClass, IsPublic, IsOverload);
Proto->setOperator(Operator);
+ auto SavedPacks = std::move(PackNames);
+ PackNames.clear();
+ for (const auto &P : Proto->getParams()) if (P.IsPack) PackNames.insert(P.Name);
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_endprocedure});
+ PackNames = std::move(SavedPacks);
if (CurTok != tok_endprocedure) {
cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDPROCEDURE\n");
@@ -370,6 +393,17 @@ std::unique_ptr<StmtAST> Parser::ParseCallStmt() {
getNextToken();
}
+ std::vector<std::string> TemplateArgs;
+ if (CurTok == '<') {
+ auto Parsed = ParseTemplateArgs();
+ if (!Parsed) return nullptr;
+ TemplateArgs = std::move(*Parsed);
+ if (CurTok != '(') {
+ sourceDiagnostic(Line, "error: Expected '(' after explicit template arguments at line %d\n", Line);
+ return nullptr;
+ }
+ }
+
std::vector<DesignatorAccess> Accesses;
if (!ParseDesignatorSuffix(Accesses)) return nullptr;
if (!Accesses.empty()) {
@@ -387,12 +421,12 @@ std::unique_ptr<StmtAST> Parser::ParseCallStmt() {
}
if (CurTok == '.') {
- auto Result = ParseResultMethods(std::make_unique<CallExprAST>(Callee, std::move(Args), Line), Line);
+ auto Result = ParseResultMethods(std::make_unique<CallExprAST>(Callee, std::move(Args), Line, std::move(TemplateArgs)), Line);
if (!Result) return nullptr;
return std::make_unique<MemberCallStmtAST>(
std::unique_ptr<MemberCallExprAST>(static_cast<MemberCallExprAST *>(Result.release())));
}
- return std::make_unique<CallStmtAST>(Callee, std::move(Args), Line);
+ return std::make_unique<CallStmtAST>(Callee, std::move(Args), Line, std::move(TemplateArgs));
}
std::unique_ptr<StmtAST> Parser::ParseReturnStmt() {
diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc
index 64330e4..21696d0 100644
--- a/lib/Parser/ParserType.cc
+++ b/lib/Parser/ParserType.cc
@@ -5,6 +5,136 @@
using namespace cps;
+bool Parser::isTemplatePack(const std::string &Name) const {
+ for (const auto &P : CurrentTemplate)
+ if (P.IsPack && sameIdentifier(P.Name, Name)) return true;
+ return false;
+}
+
+// TEMPLATE <T, Rest...> [OVERLOAD] FUNCTION/PROCEDURE or CLASS.
+// Invalid nesting still consumes the entire declaration before diagnosing it.
+std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
+ int Line = Lex.getLine();
+ bool AtTopLevel = BlockDepth == 1 && CurrentTemplate.empty();
+ getNextToken();
+ if (CurTok != '<') {
+ sourceDiagnostic(Line, "error: Expected '<' after TEMPLATE at line %d\n", Line);
+ return nullptr;
+ }
+ getNextToken();
+ std::vector<TemplateParam> Params;
+ IdentifierSet Names;
+ bool Invalid = false;
+ bool SeenPack = false;
+ while (true) {
+ if (CurTok != tok_identifier) {
+ sourceDiagnostic(Lex.getLine(), "error: Expected template parameter name at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ TemplateParam P{Lex.IdentifierStr, false, Lex.getLine()};
+ if (!Names.insert(P.Name).second) {
+ sourceDiagnostic(P.Line, "error: Duplicate template parameter '%s' at line %d\n", P.Name.c_str(), P.Line);
+ Invalid = true;
+ }
+ getNextToken();
+ if (CurTok == tok_ellipsis) {
+ P.IsPack = true;
+ if (SeenPack) {
+ sourceDiagnostic(P.Line, "error: A template may have only one parameter pack at line %d\n", P.Line);
+ Invalid = true;
+ }
+ SeenPack = true;
+ getNextToken();
+ }
+ Params.push_back(std::move(P));
+ if (CurTok == '>') break;
+ if (CurTok != ',') {
+ sourceDiagnostic(Lex.getLine(), "error: Expected ',' or '>' in TEMPLATE at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ if (Params.back().IsPack) {
+ sourceDiagnostic(Params.back().Line, "error: A template parameter pack must be last at line %d\n", Params.back().Line);
+ Invalid = true;
+ }
+ getNextToken();
+ }
+ getNextToken();
+
+ auto SavedTemplate = std::move(CurrentTemplate);
+ auto SavedTypes = TypeNames;
+ CurrentTemplate = Params;
+ for (const auto &P : Params) TypeNames.insert(P.Name);
+ bool IsOverload = CurTok == tok_overload;
+ if (IsOverload) getNextToken();
+ std::unique_ptr<StmtAST> Decl;
+ if (CurTok == tok_function || CurTok == tok_procedure) {
+ bool Operator = Lex.peekToken() == tok_operator;
+ if (Operator) {
+ sourceDiagnostic(Line, "error: Operator templates are not supported at line %d\n", Line);
+ Invalid = true;
+ int End = CurTok == tok_function ? tok_endfunction : tok_endprocedure;
+ do { getNextToken(); } while (CurTok != End && CurTok != tok_eof);
+ if (CurTok == End) getNextToken();
+ } else {
+ auto Function = CurTok == tok_function ? ParseFunction({}, true, IsOverload)
+ : ParseProcedure({}, true, IsOverload);
+ if (Function) Decl = std::make_unique<TemplateDeclAST>(Params, std::move(Function));
+ }
+ } else if (CurTok == tok_class && !IsOverload) {
+ int ClassLine = Lex.getLine();
+ auto Class = ParseTypeDecl();
+ if (Class) {
+ Class->setLine(ClassLine);
+ Decl = std::make_unique<TemplateDeclAST>(Params,
+ std::unique_ptr<ClassTypeDeclAST>(static_cast<ClassTypeDeclAST *>(Class.release())));
+ }
+ } else if (CurTok == tok_type) {
+ sourceDiagnostic(Line, "error: TEMPLATE cannot prefix TYPE; use CLASS at line %d\n", Line);
+ ParseTypeDecl();
+ Invalid = true;
+ } else {
+ sourceDiagnostic(Line, "error: TEMPLATE must be followed by FUNCTION, PROCEDURE or CLASS at line %d\n", Line);
+ Invalid = true;
+ }
+ CurrentTemplate = std::move(SavedTemplate);
+ TypeNames = std::move(SavedTypes);
+ if (!AtTopLevel) {
+ sourceDiagnostic(Line, "error: TEMPLATE declarations are only allowed at top level at line %d\n", Line);
+ Invalid = true;
+ }
+ if (Invalid || !Decl) return nullptr;
+ if (auto *T = dynamic_cast<TemplateDeclAST *>(Decl.get()); T->getClass()) TypeNames.insert(T->getName());
+ Decl->setLine(Line);
+ return Decl;
+}
+
+std::optional<std::vector<std::string>> Parser::ParseTemplateArgs() {
+ getNextToken(); // '<'
+ std::vector<std::string> Args;
+ while (true) {
+ int Line = Lex.getLine();
+ std::string Type = ParseTypeName(false);
+ if (Type.empty()) return std::nullopt;
+ if (CurTok == tok_ellipsis) {
+ if (!isTemplatePack(Type)) {
+ sourceDiagnostic(Line, "error: Only the current template parameter pack may use '...' in a type argument at line %d\n", Line);
+ return std::nullopt;
+ }
+ Type += "...";
+ getNextToken();
+ }
+ Args.push_back(std::move(Type));
+ if (CurTok == '>') break;
+ if (CurTok != ',') {
+ sourceDiagnostic(Lex.getLine(), "error: Expected ',' or '>' in template arguments at line %d\n", Lex.getLine());
+ return std::nullopt;
+ }
+ getNextToken();
+ }
+ getNextToken();
+ return Args;
+}
+
// TYPE <id> = (value1, value2, ...) enumerated type
// TYPE <id> = ^<type> pointer type
// TYPE <id> record type
@@ -29,6 +159,7 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
}
std::string Name = CurTok == tok_string_kw ? "STRING" : Lex.IdentifierStr;
getNextToken();
+ if (IsClass && !CurrentTemplate.empty()) TypeNames.insert(Name);
std::unique_ptr<StmtAST> Decl;
@@ -122,6 +253,15 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
std::vector<RecordFieldDecl> Fields;
bool HadFieldError = false;
while (CurTok != EndToken && CurTok != tok_eof) {
+ if (CurTok == tok_template) {
+ // A class body has no ParseBlock of its own.
+ ++BlockDepth;
+ ParseTemplateDecl();
+ --BlockDepth;
+ ++NumErrors;
+ HadFieldError = true;
+ continue;
+ }
if (AtField()) {
// Class fields default to public, like record fields.
bool IsPublic = CurTok != tok_private;
@@ -154,6 +294,12 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
while (!AtField() && CurTok != EndToken && CurTok != tok_eof) {
if (CurTok == tok_class || CurTok == tok_type)
ParseTypeDecl();
+ else if (CurTok == tok_template) {
+ ++BlockDepth;
+ ParseTemplateDecl();
+ --BlockDepth;
+ ++NumErrors;
+ }
else
getNextToken();
}
@@ -175,6 +321,7 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
// <field introducer> <field> : <type>. Array fields are deferred.
bool Parser::ParseRecordField(std::vector<RecordFieldDecl> &Out, bool IsPublic) {
+ int Line = Lex.getLine();
const char *Introducer = CurTok == tok_declare ? "DECLARE"
: CurTok == tok_public ? "PUBLIC" : "PRIVATE";
if (CurTok != tok_identifier) getNextToken(); // optional class access keyword
@@ -200,6 +347,11 @@ bool Parser::ParseRecordField(std::vector<RecordFieldDecl> &Out, bool IsPublic)
std::string TypeName = ParseTypeName(false);
if (TypeName.empty()) return false;
+ if (CurTok == tok_ellipsis || isTemplatePack(TypeName)) {
+ sourceDiagnostic(Line, "error: Pack fields are not supported at line %d\n", Line);
+ if (CurTok == tok_ellipsis) getNextToken();
+ return false;
+ }
Out.push_back({FieldName, TypeName, IsPublic});
return true;
@@ -240,6 +392,16 @@ std::string Parser::ParseTypeName(bool AllowVoid) {
return "";
}
getNextToken();
+ if (CurTok == '<') {
+ auto Args = ParseTemplateArgs();
+ if (!Args) return {};
+ TypeName += "<";
+ for (size_t I = 0; I < Args->size(); ++I) {
+ if (I) TypeName += ",";
+ TypeName += (*Args)[I];
+ }
+ TypeName += ">";
+ }
return TypeName;
}
diff --git a/tests/i/template-unused/case.json b/tests/i/template-unused/case.json
new file mode 100644
index 0000000..3415f39
--- /dev/null
+++ b/tests/i/template-unused/case.json
@@ -0,0 +1 @@
+{"sources": ["program.cps", "lib.cps"]}
diff --git a/tests/i/template-unused/expected.txt b/tests/i/template-unused/expected.txt
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/tests/i/template-unused/expected.txt
@@ -0,0 +1 @@
+7
diff --git a/tests/i/template-unused/lib.cps b/tests/i/template-unused/lib.cps
new file mode 100644
index 0000000..3de8ac3
--- /dev/null
+++ b/tests/i/template-unused/lib.cps
@@ -0,0 +1,48 @@
+TEMPLATE <T>
+FUNCTION Identity(Value : T) RETURNS T
+ IF Value < Value THEN
+ RETURN (T)Value
+ ENDIF
+ RETURN Value
+ENDFUNCTION
+
+TEMPLATE <T, Rest...>
+OVERLOAD PROCEDURE Visit(First : T, Others : Rest...)
+ OUTPUT Identity<T>(First)
+ CALL Visit<Rest...>(Others...)
+ Visit<Box<Pair<INTEGER,REAL>>>(Others...)
+ENDPROCEDURE
+
+TEMPLATE <T>
+CLASS Box
+ PRIVATE Value : T
+ PUBLIC PROCEDURE NEW(V : T)
+ Value <- V
+ ENDPROCEDURE
+ PUBLIC FUNCTION Get() RETURNS T
+ RETURN Value
+ ENDFUNCTION
+ PUBLIC FUNCTION AsBox(V : Box<T>) RETURNS Box<T>
+ RETURN (Box<T>)V
+ ENDFUNCTION
+ENDCLASS
+
+TEMPLATE <A, B>
+CLASS Pair
+ Left : A
+ Right : B
+ENDCLASS
+
+TEMPLATE <T, Rest...>
+CLASS Bundle
+ PUBLIC PROCEDURE Send(Values : Rest...)
+ CALL Visit(Values...)
+ ENDPROCEDURE
+ENDCLASS
+
+TEMPLATE <T>
+PROCEDURE NestedTypes(Value : Box<Box<T>>)
+ DECLARE Other : Box<Pair<T,REAL>>
+ Other <- NEW Box<Pair<T,REAL>>()
+ Value <- (Box<Box<T>>)Value
+ENDPROCEDURE
diff --git a/tests/i/template-unused/notes.md b/tests/i/template-unused/notes.md
new file mode 100644
index 0000000..c3ff7fd
--- /dev/null
+++ b/tests/i/template-unused/notes.md
@@ -0,0 +1,5 @@
+# template-unused
+
+Human review: pending. Expected output was independently traced by AI, not captured from cpsc.
+
+Basis: TEMPLATE is a project extension. The library holds unused function, class and variadic templates, including casts, nested type ids, explicit calls and pack forwarding. Their bodies are parsed without being emitted or counted as top-level code. The entry unit prints INTEGER 7.
diff --git a/tests/i/template-unused/program.cps b/tests/i/template-unused/program.cps
new file mode 100644
index 0000000..d8b89a2
--- /dev/null
+++ b/tests/i/template-unused/program.cps
@@ -0,0 +1,2 @@
+INCLUDE "lib.cps"
+OUTPUT 7
diff --git a/tests/iii/template-header-errors/case.json b/tests/iii/template-header-errors/case.json
new file mode 100644
index 0000000..f74cc0d
--- /dev/null
+++ b/tests/iii/template-header-errors/case.json
@@ -0,0 +1,36 @@
+{
+ "errors": [
+ {
+ "reason": "Duplicate template parameter",
+ "line": 1
+ },
+ {
+ "reason": "template parameter pack must be last",
+ "line": 6
+ },
+ {
+ "reason": "only one parameter pack",
+ "line": 10
+ },
+ {
+ "reason": "TEMPLATE cannot prefix TYPE",
+ "line": 14
+ },
+ {
+ "reason": "Operator templates are not supported",
+ "line": 17
+ },
+ {
+ "reason": "TEMPLATE declarations are only allowed at top level",
+ "line": 23
+ },
+ {
+ "reason": "TEMPLATE declarations are only allowed at top level",
+ "line": 30
+ },
+ {
+ "reason": "Expected '<' after TEMPLATE",
+ "line": 35
+ }
+ ]
+}
diff --git a/tests/iii/template-header-errors/program.cps b/tests/iii/template-header-errors/program.cps
new file mode 100644
index 0000000..73c34a2
--- /dev/null
+++ b/tests/iii/template-header-errors/program.cps
@@ -0,0 +1,39 @@
+TEMPLATE <T, t>
+FUNCTION Duplicate(Value : T) RETURNS T
+ RETURN Value
+ENDFUNCTION
+
+TEMPLATE <Rest..., T>
+PROCEDURE NotLast()
+ENDPROCEDURE
+
+TEMPLATE <A..., B...>
+PROCEDURE TwoPacks()
+ENDPROCEDURE
+
+TEMPLATE <T>
+TYPE Alias = ^T
+
+TEMPLATE <T>
+OVERLOAD FUNCTION OPERATOR+(Value : T) RETURNS T
+ RETURN Value
+ENDFUNCTION
+
+IF TRUE THEN
+ TEMPLATE <T>
+ PROCEDURE Nested(Value : T)
+ OUTPUT 99
+ ENDPROCEDURE
+ENDIF
+
+CLASS Outer
+ TEMPLATE <T>
+ PROCEDURE Member(Value : T)
+ ENDPROCEDURE
+ENDCLASS
+
+TEMPLATE <>
+PROCEDURE Empty()
+ENDPROCEDURE
+
+OUTPUT 0
diff --git a/tests/iii/template-pack-errors/case.json b/tests/iii/template-pack-errors/case.json
new file mode 100644
index 0000000..53b5a23
--- /dev/null
+++ b/tests/iii/template-pack-errors/case.json
@@ -0,0 +1,32 @@
+{
+ "errors": [
+ {
+ "reason": "A pack parameter must be last",
+ "line": 2
+ },
+ {
+ "reason": "pack parameter must use the current template pack and cannot be ARRAY",
+ "line": 6
+ },
+ {
+ "reason": "pack parameter must use the current template pack and cannot be ARRAY",
+ "line": 10
+ },
+ {
+ "reason": "parameter pack must be expanded",
+ "line": 15
+ },
+ {
+ "reason": "Only a parameter pack may be expanded",
+ "line": 20
+ },
+ {
+ "reason": "Only the current template parameter pack may use",
+ "line": 25
+ },
+ {
+ "reason": "template parameter pack requires",
+ "line": 29
+ }
+ ]
+}
diff --git a/tests/iii/template-pack-errors/program.cps b/tests/iii/template-pack-errors/program.cps
new file mode 100644
index 0000000..51ffa46
--- /dev/null
+++ b/tests/iii/template-pack-errors/program.cps
@@ -0,0 +1,32 @@
+TEMPLATE <Rest...>
+PROCEDURE NotLast(Values : Rest..., Extra : INTEGER)
+ENDPROCEDURE
+
+TEMPLATE <Rest...>
+PROCEDURE ArrayPack(Values : ARRAY OF Rest...)
+ENDPROCEDURE
+
+TEMPLATE <T>
+PROCEDURE WrongPack(Values : T...)
+ENDPROCEDURE
+
+TEMPLATE <Rest...>
+PROCEDURE BarePack(Values : Rest...)
+ OUTPUT Values
+ENDPROCEDURE
+
+TEMPLATE <T>
+PROCEDURE ScalarExpansion(Value : T)
+ CALL Missing(Value...)
+ENDPROCEDURE
+
+TEMPLATE <T>
+PROCEDURE WrongTypeExpansion(Value : T)
+ CALL Missing<T...>(Value)
+ENDPROCEDURE
+
+TEMPLATE <Rest...>
+PROCEDURE MissingDots(Values : Rest)
+ENDPROCEDURE
+
+OUTPUT 0