aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 14:03:31 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 14:03:31 +0000
commitb4ffb2d9498560557821714e2d4651279dd6817c (patch)
tree995ad02bdb8cc461f48a53683c4fd7f2ddd8656b
parentf1f5fa394a67a789914ccf2b24d534a0e466ae1b (diff)
feat(diagnostics): print source diagnostics in gcc file:line: layout
Lexer, parser, driver and codegen diagnostics now read "<path>:<line>: error: <message>" instead of "error: <message> at line N in <path>". The path is always printed as given on the command line. Call sites pass the bare message to cps::sourceError/sourceWarning, which carry printf format checking; reportError routes through the same helper. The suite iii harness parses the new layout and matches the reason against the message only. iii/template-class-pack-field passed only because its reason regex matched the case path, so it is now case-insensitive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH/DRIVER.md5
-rw-r--r--AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md9
-rw-r--r--AGENTS/TESTING.md2
-rw-r--r--doc/cpsc.1.md2
-rw-r--r--include/cps/CodeGen.h2
-rw-r--r--include/cps/Diagnostics.h66
-rw-r--r--include/cps/FunctionGen.h2
-rw-r--r--lib/CodeGen/ArithmeticHandler.cc8
-rw-r--r--lib/CodeGen/CodeGen.cc7
-rw-r--r--lib/CodeGen/ConstantGen.cc4
-rw-r--r--lib/CodeGen/FunctionGen.cc7
-rw-r--r--lib/Lexer/Lexer.cc14
-rw-r--r--lib/Parser/Parser.cc34
-rw-r--r--lib/Parser/ParserControlFlow.cc34
-rw-r--r--lib/Parser/ParserExpression.cc37
-rw-r--r--lib/Parser/ParserFile.cc15
-rw-r--r--lib/Parser/ParserFunction.cc97
-rw-r--r--lib/Parser/ParserType.cc97
-rw-r--r--tests/iii/template-class-pack-field/case.json2
-rw-r--r--tests/run.pl13
-rw-r--r--tests/test_runner.pl27
-rw-r--r--tools/driver/main.cc28
22 files changed, 257 insertions, 255 deletions
diff --git a/AGENTS/ARCH/DRIVER.md b/AGENTS/ARCH/DRIVER.md
index af3925a..d571509 100644
--- a/AGENTS/ARCH/DRIVER.md
+++ b/AGENTS/ARCH/DRIVER.md
@@ -34,8 +34,9 @@ style is in [CONVENTIONS/SYMBOLS_AND_ERRORS.md](../CONVENTIONS/SYMBOLS_AND_ERROR
also return 1. Ordinary compiler errors use `error:`; failures that prevent
compilation, such as file I/O errors, use `fatal error:`. The driver prints
`compilation terminated.` once on compilation failure, including argument errors.
- Multi-input lexer/parser/codegen diagnostics append the source path after the line number;
- single-input diagnostics (Diagnostics.h) do not append it.
+ Lexer/parser/codegen diagnostics use the gcc layout `<path>:<line>: error: <message>`
+ (Diagnostics.h), with the path as given on the command line; driver-level errors
+ use `cpsc: error:` / `cpsc: fatal error:`.
Execution-stage failures do not print that line. Check the exit code rather
than grepping stderr.
- Successful compilation overwrites the output, but paths aliasing any input source
diff --git a/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md b/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md
index 3e3c3ad..e21bed1 100644
--- a/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md
+++ b/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md
@@ -42,8 +42,13 @@ keep compiling.** No exceptions, no exit mid-compile.
- `Lexer`/`Parser`/`CodeGen` each expose `hadError()`; AST expressions/statements
share source-line metadata. Parser entry points record token locations. CodeGen
- saves/restores its diagnostic line across recursive emission and coercion;
- `reportError` appends `at line N`.
+ saves/restores its diagnostic line across recursive emission and coercion.
+- Diagnostics use the gcc layout `<path>:<line>: error: <message>` (`<path>:` when
+ the line is unknown). Lexer/parser/driver call `cps::sourceError(Line, fmt, ...)`
+ (`sourceWarning` for warnings) from `Diagnostics.h` with the bare message: no
+ `error:` label, location or newline. `reportError` routes through the same helper
+ with `DiagnosticLine`. `DiagnosticSource` holds the unit path as given on the
+ command line.
- `CodeGen::reportError(fmt, ...)` is the helper for codegen-side diagnostics
(`ArrayHandler` reaches it through its `CodeGen &` parameter,
`FunctionGen`/`ArithmeticHandler` hold a `bool &HadError` reference).
diff --git a/AGENTS/TESTING.md b/AGENTS/TESTING.md
index a43d0fb..f3628d2 100644
--- a/AGENTS/TESTING.md
+++ b/AGENTS/TESTING.md
@@ -2,7 +2,7 @@
Compiler tests live in `tests/`; run instructions are in its README. `BUILD_TESTING` defaults to OFF and uses the compiler build's Perl interpreter with core modules when enabled. CMake builds independent C++ reference executables with the same host ABI and registers one CTest entry per `case.json`, plus harness and generator checks.
-Each case directory has `program.cps` and `case.json`. Suite i adds `expected.txt`, optional `input.txt`, input files under `files/`, expected text-file contents under `expected-files/`, and independently written trace notes with human-review status. Suite ii adds `reference.cc` and `generate.pl`; the generator accepts seed and random count, emits the total record count followed by one ASCII record per line, and each program emits one result line per record. Suite iii stores an `errors` list of `{reason: regex, line: integer}` in its manifest. A cause and its line must appear on the same error diagnostic. EOF after a final newline is the following source line.
+Each case directory has `program.cps` and `case.json`. Suite i adds `expected.txt`, optional `input.txt`, input files under `files/`, expected text-file contents under `expected-files/`, and independently written trace notes with human-review status. Suite ii adds `reference.cc` and `generate.pl`; the generator accepts seed and random count, emits the total record count followed by one ASCII record per line, and each program emits one result line per record. Suite iii stores an `errors` list of `{reason: regex, line: integer}` in its manifest. A cause and its line must appear on the same `<path>:<line>: error: <message>` diagnostic; the reason is matched against the message. EOF after a final newline is the following source line.
Multi-file cases specify `sources` as an ordered array of paths relative to the
case directory, defaulting to `["program.cps"]`. Library sources can be in nested
diff --git a/doc/cpsc.1.md b/doc/cpsc.1.md
index 8359a25..b156d6f 100644
--- a/doc/cpsc.1.md
+++ b/doc/cpsc.1.md
@@ -117,7 +117,7 @@ An uncaught program exception returns 1 after printing `Unhandled exception. Pro
# NOTES
-Compiler diagnostics go to stderr. With multiple input files, lexer, parser and code generation diagnostics include the source path after the line number. Failed source compilation does not write output; failed native linking does not replace an existing output file. Successful compilation overwrites an existing output file, but an output path that refers to any input source file (including a symbolic or hard link) is rejected. Runtime diagnostics of the compiled program (for example `[Fatal] line N: ...` and `Unhandled exception. Program terminated.`) are also written to stderr, so `OUTPUT` text alone appears on stdout.
+Compiler diagnostics go to stderr in the gcc layout `file:line: error: message`, where `file` is the source path as given on the command line; diagnostics that are not tied to a source file start with `cpsc:`. Failed source compilation does not write output; failed native linking does not replace an existing output file. Successful compilation overwrites an existing output file, but an output path that refers to any input source file (including a symbolic or hard link) is rejected. Runtime diagnostics of the compiled program (for example `[Fatal] line N: ...` and `Unhandled exception. Program terminated.`) are also written to stderr, so `OUTPUT` text alone appears on stdout.
Missing input, including invoking `cpsc` without arguments, reports `cpsc: fatal error: no input files`. Ordinary lexical, syntax and semantic errors use `error:`; failures that prevent compilation, such as file I/O errors, use `fatal error:`. Compilation and argument failures end with one `compilation terminated.` line. Execution-stage failures do not print that line.
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 4730585..6477406 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -240,7 +240,7 @@ public:
void print(llvm::raw_ostream &Output);
const llvm::Module &getModule() const { return *TheModule; }
bool hadError() const { return HadError; }
- void reportError(const char *Fmt, ...);
+ __attribute__((format(printf, 2, 3))) void reportError(const char *Fmt, ...);
static bool isBuiltinName(const std::string &Name);
llvm::Value *emitExpr(ExprAST *Expr);
diff --git a/include/cps/Diagnostics.h b/include/cps/Diagnostics.h
index 6f26292..3492f46 100644
--- a/include/cps/Diagnostics.h
+++ b/include/cps/Diagnostics.h
@@ -2,43 +2,45 @@
#include <cstdarg>
#include <cstdio>
#include <string>
-#include <vector>
namespace cps {
-// Empty for single-source builds to preserve existing diagnostic text.
+enum class Severity { Error, Warning, Note, Fatal };
+
+// Path of the unit being diagnosed, as given on the command line.
inline std::string DiagnosticSource;
-inline std::string lineSuffix(int Line) {
- return " at line " + std::to_string(Line) +
- (DiagnosticSource.empty() ? "" : " in " + DiagnosticSource);
+
+// gcc layout: "<locus>: <label>: "; an empty locus prints the label alone.
+inline std::string diagnosticPrefix(Severity S, const std::string &Locus = "cpsc") {
+ const char *Label = S == Severity::Error ? "error: "
+ : S == Severity::Warning ? "warning: "
+ : S == Severity::Note ? "note: "
+ : "fatal error: ";
+ return (Locus.empty() ? "" : Locus + ": ") + Label;
}
-inline void sourceDiagnostic(int Line, const char *Format, ...) {
- va_list Args, Copy;
+
+// Prints "<path>:<line>: <label>: <message>\n" (just "<path>:" when the line is
+// unknown, "cpsc:" when no source is set).
+inline void vsourceDiagnostic(Severity S, int Line, const char *Format, va_list Args) {
+ std::string Locus = DiagnosticSource.empty() ? "cpsc" : DiagnosticSource;
+ if (!DiagnosticSource.empty() && Line > 0) Locus += ":" + std::to_string(Line);
+ std::fputs(diagnosticPrefix(S, Locus).c_str(), stderr);
+ std::vfprintf(stderr, Format, Args);
+ std::fputc('\n', stderr);
+}
+
+__attribute__((format(printf, 2, 3)))
+inline void sourceError(int Line, const char *Format, ...) {
+ va_list Args;
+ va_start(Args, Format);
+ vsourceDiagnostic(Severity::Error, Line, Format, Args);
+ va_end(Args);
+}
+
+__attribute__((format(printf, 2, 3)))
+inline void sourceWarning(int Line, const char *Format, ...) {
+ va_list Args;
va_start(Args, Format);
- va_copy(Copy, Args);
- int Size = std::vsnprintf(nullptr, 0, Format, Copy);
- va_end(Copy);
- if (Size < 0) { va_end(Args); return; }
- std::vector<char> Buffer(Size + 1);
- std::vsnprintf(Buffer.data(), Buffer.size(), Format, Args);
+ vsourceDiagnostic(Severity::Warning, Line, Format, Args);
va_end(Args);
- std::string Message(Buffer.data(), Size);
- if (!DiagnosticSource.empty()) {
- auto At = Message.find("at line ");
- if (At != std::string::npos) {
- auto End = Message.find_first_not_of("0123456789", At + 8);
- Message.insert(End == std::string::npos ? Message.size() : End,
- " in " + DiagnosticSource);
- } else {
- auto Old = Message.find("(line ");
- if (Old != std::string::npos) {
- auto End = Message.find(')', Old);
- Message.replace(Old, End - Old + 1, lineSuffix(Line).substr(1));
- } else {
- auto End = Message.find_last_not_of("\n");
- Message.insert(End == std::string::npos ? 0 : End + 1, lineSuffix(Line));
- }
- }
- }
- std::fputs(Message.c_str(), stderr);
}
}
diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h
index a50e70b..a176e84 100644
--- a/include/cps/FunctionGen.h
+++ b/include/cps/FunctionGen.h
@@ -72,7 +72,7 @@ private:
IdentifierMap<SymbolInfo> &Symbols;
bool &HadError;
int &DiagnosticLine;
- void reportError(const char *Fmt, ...);
+ __attribute__((format(printf, 2, 3))) void reportError(const char *Fmt, ...);
IdentifierMap<std::deque<FuncSig>> Signatures;
IdentifierMap<std::pair<std::string, size_t>> LinkIndex;
diff --git a/lib/CodeGen/ArithmeticHandler.cc b/lib/CodeGen/ArithmeticHandler.cc
index 255da57..480c963 100644
--- a/lib/CodeGen/ArithmeticHandler.cc
+++ b/lib/CodeGen/ArithmeticHandler.cc
@@ -29,7 +29,7 @@ Value *ArithmeticHandler::emitBinaryOp(int Op, Value *LHS, Value *RHS, int Line)
if (LVal->getType()->isIntegerTy(64)) LVal = Builder.CreateSIToFP(LVal, Type::getDoubleTy(Context));
if (RVal->getType()->isIntegerTy(64)) RVal = Builder.CreateSIToFP(RVal, Type::getDoubleTy(Context));
if (!LVal->getType()->isDoubleTy() || !RVal->getType()->isDoubleTy()) {
- cps::sourceDiagnostic(Line, "error: '/' requires numeric operands (line %d).\n", Line);
+ cps::sourceError(Line, "'/' requires numeric operands");
HadError = true;
return nullptr;
}
@@ -40,7 +40,7 @@ Value *ArithmeticHandler::emitBinaryOp(int Op, Value *LHS, Value *RHS, int Line)
if (Op == tok_div || Op == tok_mod) {
if (!LIsInt || !RIsInt) {
- cps::sourceDiagnostic(Line, "error: DIV and MOD operators require INTEGER operands (line %d).\n", Line);
+ cps::sourceError(Line, "DIV and MOD operators require INTEGER operands");
HadError = true;
return nullptr;
}
@@ -97,7 +97,7 @@ Value *ArithmeticHandler::emitBinaryOp(int Op, Value *LHS, Value *RHS, int Line)
R = widenSmallInt(Builder, Context, R);
}
if (L->getType() != R->getType()) {
- cps::sourceDiagnostic(Line, "error: Mismatched operand types for binary operator (line %d).\n", Line);
+ cps::sourceError(Line, "Mismatched operand types for binary operator");
HadError = true;
return nullptr;
}
@@ -115,7 +115,7 @@ Value *ArithmeticHandler::emitBinaryOp(int Op, Value *LHS, Value *RHS, int Line)
}
}
- cps::sourceDiagnostic(Line, "error: Unsupported operand types for binary operator (line %d).\n", Line);
+ cps::sourceError(Line, "Unsupported operand types for binary operator");
HadError = true;
return nullptr;
}
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index fb86120..e184b19 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -28,10 +28,7 @@ bool CodeGen::isBuiltinName(const std::string &Name) {
void CodeGen::reportError(const char *Fmt, ...) {
va_list Args;
va_start(Args, Fmt);
- fprintf(stderr, "error: ");
- vfprintf(stderr, Fmt, Args);
- if (DiagnosticLine) fprintf(stderr, "%s", lineSuffix(DiagnosticLine).c_str());
- fprintf(stderr, "\n");
+ cps::vsourceDiagnostic(cps::Severity::Error, DiagnosticLine, Fmt, Args);
va_end(Args);
HadError = true;
}
@@ -154,7 +151,7 @@ bool CodeGen::isVisible(size_t Owner) const {
void CodeGen::selectUnit(size_t Unit) {
CurrentUnit = Unit;
- DiagnosticSource = Units->size() > 1 ? (*Units)[Unit].Path : "";
+ DiagnosticSource = (*Units)[Unit].Path;
FuncGen->setCurrentUnit(Unit, (*Units)[Unit].Path);
RuntimeChecker->setLibraryMode((*Units)[Unit].IsLibrary);
FuncGen->setCallHook([this](const FuncSig &Sig) {
diff --git a/lib/CodeGen/ConstantGen.cc b/lib/CodeGen/ConstantGen.cc
index 1ac2a28..ca96834 100644
--- a/lib/CodeGen/ConstantGen.cc
+++ b/lib/CodeGen/ConstantGen.cc
@@ -22,8 +22,8 @@ void CodeGen::warnMemberShadow(const std::string &Name, int Line) const {
const TypeInfo *Class = resolveType(CurrentClass);
const RecordPayload *Record = Class ? Class->asRecord() : nullptr;
if (Record && Record->findField(Name)) {
- cps::sourceDiagnostic(Line, "warning: Declaration of %s shadows a field of class %s at line %d\n",
- Name.c_str(), CurrentClass.c_str(), Line);
+ cps::sourceWarning(Line, "Declaration of %s shadows a field of class %s",
+ Name.c_str(), CurrentClass.c_str());
}
}
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index c01b042..628f3ba 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -30,10 +30,7 @@ bool FunctionGen::isReservedRuntimeName(const std::string &Name) {
void FunctionGen::reportError(const char *Fmt, ...) {
va_list Args;
va_start(Args, Fmt);
- fprintf(stderr, "error: ");
- vfprintf(stderr, Fmt, Args);
- if (DiagnosticLine) fprintf(stderr, "%s", lineSuffix(DiagnosticLine).c_str());
- fprintf(stderr, "\n");
+ cps::vsourceDiagnostic(cps::Severity::Error, DiagnosticLine, Fmt, Args);
va_end(Args);
HadError = true;
}
@@ -449,7 +446,7 @@ static Value *GenerateCall(llvm::Module &Module,
bool &HadError, int DiagnosticLine) {
Function *CalleeF = Module.getFunction(CalleeName);
if (!CalleeF) {
- sourceDiagnostic(DiagnosticLine, "error: Call to undefined function %s\n", CalleeName.c_str());
+ sourceError(DiagnosticLine, "Call to undefined function %s", CalleeName.c_str());
HadError = true;
return nullptr;
}
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index 91ce88a..437aed4 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -12,7 +12,7 @@ using namespace cps;
std::string Lexer::readEscape(bool IsChar) {
auto Error = [&](const char *Reason) {
- cps::sourceDiagnostic(TokenLine, "error: %s at line %d\n", Reason, TokenLine);
+ cps::sourceError(TokenLine, "%s", Reason);
HadError = true;
return std::string("?");
};
@@ -106,7 +106,7 @@ int Lexer::readToken() {
Directive.pop_back();
if (Directive == "#LIB") Library = true;
else {
- cps::sourceDiagnostic(TokenLine, "error: Unknown compiler directive at line %d\n", TokenLine);
+ cps::sourceError(TokenLine, "Unknown compiler directive");
HadError = true;
}
return readToken();
@@ -127,12 +127,12 @@ int Lexer::readToken() {
}
if (LastChar == Quote) LastChar = Input.get();
else {
- cps::sourceDiagnostic(TokenLine, "error: Unterminated %s literal at line %d\n", IsChar ? "char" : "string", TokenLine);
+ cps::sourceError(TokenLine, "Unterminated %s literal", IsChar ? "char" : "string");
HadError = true;
}
if (IsChar) {
if (StringVal.size() != 1) {
- cps::sourceDiagnostic(TokenLine, "error: CHAR literal must contain exactly one character at line %d\n", TokenLine);
+ cps::sourceError(TokenLine, "CHAR literal must contain exactly one character");
HadError = true;
}
CharVal = StringVal.size() == 1 ? StringVal[0] : '?';
@@ -154,7 +154,7 @@ int Lexer::readToken() {
std::transform(Prefix.begin(), Prefix.end(), Prefix.begin(),
[](unsigned char C) { return std::tolower(C); });
if (Prefix == "__cps_") {
- cps::sourceDiagnostic(TokenLine, "error: Reserved runtime identifier at line %d\n", TokenLine);
+ cps::sourceError(TokenLine, "Reserved runtime identifier");
HadError = true;
}
}
@@ -277,7 +277,7 @@ int Lexer::readToken() {
auto Parsed = std::from_chars(NumStr.data(), NumStr.data() + NumStr.size(), NumVal);
if (Parsed.ec != std::errc{} ||
NumVal > uint64_t(std::numeric_limits<int64_t>::max()) + 1) {
- cps::sourceDiagnostic(CurrentLine, "error: Integer literal is outside the i64 range at line %d\n", TokenLine);
+ cps::sourceError(TokenLine, "Integer literal is outside the i64 range");
HadError = true;
NumVal = 0;
}
@@ -327,7 +327,7 @@ int Lexer::readToken() {
LastChar = Input.get();
return tok_ellipsis;
}
- cps::sourceDiagnostic(CurrentLine, "error: Expected '...' at line %d\n", CurrentLine);
+ cps::sourceError(CurrentLine, "Expected '...'");
HadError = true;
LastChar = Input.get();
return tok_ellipsis;
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index 3d45a97..b5c842f 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -100,7 +100,7 @@ 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());
+ sourceError(Lex.getLine(), "EXTERN is only allowed at top level");
getNextToken();
return nullptr;
}
@@ -113,12 +113,12 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
int Line = Lex.getLine();
getNextToken();
if (BlockDepth != 1) {
- sourceDiagnostic(Line, "error: %s is only allowed at top level at line %d\n", Keyword, Line);
+ sourceError(Line, "%s is only allowed at top level", Keyword);
return nullptr;
}
if (Include) {
if (CurTok != tok_string_literal) {
- sourceDiagnostic(Line, "error: %s requires a string literal at line %d\n", Keyword, Line);
+ sourceError(Line, "%s requires a string literal", Keyword);
return nullptr;
}
std::unique_ptr<StmtAST> Stmt;
@@ -129,14 +129,14 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
}
auto Body = ParseBlock({tok_endglobal});
if (CurTok != tok_endglobal) {
- sourceDiagnostic(Line, "error: Expected ENDGLOBAL at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected ENDGLOBAL");
return nullptr;
}
getNextToken();
return std::make_unique<GlobalBlockAST>(std::move(Body));
}
if (CurTok == tok_endglobal) {
- sourceDiagnostic(Lex.getLine(), "error: ENDGLOBAL without matching GLOBAL at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "ENDGLOBAL without matching GLOBAL");
getNextToken();
return nullptr;
}
@@ -144,7 +144,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: DELETE requires an object designator at line %d\n", Line);
+ cps::sourceError(Line, "DELETE requires an object designator");
return nullptr;
}
std::string Name = Lex.IdentifierStr;
@@ -156,7 +156,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
}
if (CurTok == tok_case) return ParseCaseStmt();
if (CurTok == tok_endcase || CurTok == tok_otherwise) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Unexpected CASE terminator at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Unexpected CASE terminator");
getNextToken();
return nullptr;
}
@@ -170,7 +170,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
else if (CurTok == tok_input) {
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected variable name after INPUT at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected variable name after INPUT");
return nullptr;
}
std::string Name = Lex.IdentifierStr;
@@ -211,8 +211,8 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
return std::make_unique<ThrowStmtAST>();
}
else if (CurTok == tok_catch || CurTok == tok_endtry) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Unexpected %s without matching TRY at line %d\n",
- CurTok == tok_catch ? "CATCH" : "ENDTRY", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Unexpected %s without matching TRY",
+ CurTok == tok_catch ? "CATCH" : "ENDTRY");
getNextToken();
return nullptr;
}
@@ -230,7 +230,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
getNextToken();
if (CurTok == tok_function) return ParseFunction({}, true, true);
if (CurTok == tok_procedure) return ParseProcedure({}, true, true);
- cps::sourceDiagnostic(Line, "error: OVERLOAD must be followed by FUNCTION or PROCEDURE at line %d\n", Line);
+ cps::sourceError(Line, "OVERLOAD must be followed by FUNCTION or PROCEDURE");
return nullptr;
}
else if (CurTok == tok_function) {
@@ -261,22 +261,22 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
return ParseTypeDecl();
}
else if (CurTok == tok_define) {
- cps::sourceDiagnostic(Lex.getLine(), "error: DEFINE (SET literals) is not implemented at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "DEFINE (SET literals) is not implemented");
getNextToken();
return nullptr;
}
else if (CurTok == tok_endtype) {
- cps::sourceDiagnostic(Lex.getLine(), "error: ENDTYPE without matching TYPE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "ENDTYPE without matching TYPE");
getNextToken();
return nullptr;
}
else if (CurTok == tok_endclass) {
- cps::sourceDiagnostic(Lex.getLine(), "error: ENDCLASS without matching CLASS at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "ENDCLASS without matching CLASS");
getNextToken();
return nullptr;
}
- cps::sourceDiagnostic(Lex.getLine(), "error: Unexpected token at line %d when expecting a statement\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Unexpected token when expecting a statement");
getNextToken(); // guarantee progress so error recovery cannot loop forever
return nullptr;
}
@@ -302,7 +302,7 @@ std::unique_ptr<StmtAST> Parser::ParseAssignStmt() {
if (!Parsed) return nullptr;
TemplateArgs = std::move(*Parsed);
if (CurTok != '(') {
- sourceDiagnostic(Line, "error: Expected '(' after explicit template arguments at line %d\n", Line);
+ sourceError(Line, "Expected '(' after explicit template arguments");
return nullptr;
}
}
@@ -329,7 +329,7 @@ std::unique_ptr<StmtAST> Parser::ParseAssignStmt() {
}
if (CurTok != tok_assign) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '<-' after identifier '%s' at line %d\n", Name.c_str(), Line);
+ cps::sourceError(Line, "Expected '<-' after identifier '%s'", Name.c_str());
return nullptr;
}
getNextToken();
diff --git a/lib/Parser/ParserControlFlow.cc b/lib/Parser/ParserControlFlow.cc
index 8ca024d..8cb5c4e 100644
--- a/lib/Parser/ParserControlFlow.cc
+++ b/lib/Parser/ParserControlFlow.cc
@@ -9,13 +9,13 @@ std::unique_ptr<StmtAST> Parser::ParseTryStmt() {
getNextToken();
auto Body = ParseBlock({tok_catch, tok_endtry});
if (CurTok != tok_catch) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected CATCH in TRY at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected CATCH in TRY");
return nullptr;
}
getNextToken();
auto CatchBody = ParseBlock({tok_endtry, tok_catch});
if (CurTok != tok_endtry) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ENDTRY after CATCH at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ENDTRY after CATCH");
return nullptr;
}
getNextToken();
@@ -28,7 +28,7 @@ std::unique_ptr<StmtAST> Parser::ParseIfStmt() {
if (!Cond) return nullptr;
if (CurTok != tok_then) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected THEN at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "expected THEN");
return nullptr;
}
getNextToken();
@@ -42,7 +42,7 @@ std::unique_ptr<StmtAST> Parser::ParseIfStmt() {
}
if (CurTok != tok_endif) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDIF at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "expected ENDIF");
return nullptr;
}
getNextToken();
@@ -60,7 +60,7 @@ std::unique_ptr<StmtAST> Parser::ParseWhileStmt() {
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_endwhile});
if (CurTok != tok_endwhile) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDWHILE\n");
+ cps::sourceError(Lex.getLine(), "expected ENDWHILE");
return nullptr;
}
getNextToken();
@@ -74,7 +74,7 @@ std::unique_ptr<StmtAST> Parser::ParseRepeatStmt() {
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_until});
if (CurTok != tok_until) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected UNTIL\n");
+ cps::sourceError(Lex.getLine(), "expected UNTIL");
return nullptr;
}
getNextToken();
@@ -88,14 +88,14 @@ std::unique_ptr<StmtAST> Parser::ParseRepeatStmt() {
std::unique_ptr<StmtAST> Parser::ParseForStmt() {
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected identifier after FOR\n");
+ cps::sourceError(Lex.getLine(), "expected identifier after FOR");
return nullptr;
}
std::string VarName = Lex.IdentifierStr;
getNextToken();
if (CurTok != tok_assign) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected '<-' in FOR loop\n");
+ cps::sourceError(Lex.getLine(), "expected '<-' in FOR loop");
return nullptr;
}
getNextToken();
@@ -104,7 +104,7 @@ std::unique_ptr<StmtAST> Parser::ParseForStmt() {
if (!Start) return nullptr;
if (CurTok != tok_to) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected TO in FOR loop\n");
+ cps::sourceError(Lex.getLine(), "expected TO in FOR loop");
return nullptr;
}
getNextToken();
@@ -122,18 +122,18 @@ std::unique_ptr<StmtAST> Parser::ParseForStmt() {
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_next});
if (CurTok != tok_next) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected NEXT\n");
+ cps::sourceError(Lex.getLine(), "expected NEXT");
return nullptr;
}
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected identifier after NEXT (e.g., NEXT %s)\n", VarName.c_str());
+ cps::sourceError(Lex.getLine(), "expected identifier after NEXT (e.g., NEXT %s)", VarName.c_str());
return nullptr;
}
if (!sameIdentifier(Lex.IdentifierStr, VarName)) {
- cps::sourceDiagnostic(Lex.getLine(), "error: NEXT identifier '%s' does not match FOR variable '%s'\n",
+ cps::sourceError(Lex.getLine(), "NEXT identifier '%s' does not match FOR variable '%s'",
Lex.IdentifierStr.c_str(), VarName.c_str());
return nullptr;
}
@@ -178,12 +178,12 @@ bool Parser::atCaseBoundary() {
std::unique_ptr<StmtAST> Parser::ParseCaseStmt() {
getNextToken();
if (CurTok != tok_of) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected OF after CASE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected OF after CASE");
return nullptr;
}
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected identifier after CASE OF at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected identifier after CASE OF");
return nullptr;
}
std::string Selector = Lex.IdentifierStr;
@@ -192,7 +192,7 @@ std::unique_ptr<StmtAST> Parser::ParseCaseStmt() {
bool SeenOtherwise = false;
while (CurTok != tok_endcase && CurTok != tok_eof) {
if (SeenOtherwise || !isCaseLabel()) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected CASE label on a new line (OTHERWISE must be last) at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected CASE label on a new line (OTHERWISE must be last)");
return nullptr;
}
CaseBranch Branch;
@@ -213,7 +213,7 @@ std::unique_ptr<StmtAST> Parser::ParseCaseStmt() {
if (!Branch.Lower) return nullptr;
}
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' after CASE label at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ':' after CASE label");
return nullptr;
}
getNextToken();
@@ -221,7 +221,7 @@ std::unique_ptr<StmtAST> Parser::ParseCaseStmt() {
Branches.push_back(std::move(Branch));
}
if (CurTok != tok_endcase || Branches.empty()) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected nonempty CASE and ENDCASE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected nonempty CASE and ENDCASE");
return nullptr;
}
getNextToken();
diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc
index ef8e1d3..754d1f2 100644
--- a/lib/Parser/ParserExpression.cc
+++ b/lib/Parser/ParserExpression.cc
@@ -19,7 +19,7 @@ int Parser::GetTokPrecedence() {
bool Parser::ParseIntegerValue(bool Negative, int64_t &Out) {
const uint64_t Max = uint64_t(std::numeric_limits<int64_t>::max());
if (Lex.NumVal > Max + (Negative ? 1u : 0u)) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Integer literal is outside the i64 range at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Integer literal is outside the i64 range");
getNextToken();
return false;
}
@@ -88,14 +88,14 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
if (PackNames.count(IdName)) {
if (CurTok != tok_ellipsis) {
- sourceDiagnostic(Line, "error: A parameter pack must be expanded with '...' at line %d\n", Line);
+ sourceError(Line, "A parameter pack must be expanded with '...'");
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);
+ sourceError(Line, "Only a parameter pack may be expanded");
getNextToken();
return nullptr;
}
@@ -110,8 +110,7 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
auto Args = ParseExprList(')', true);
if (!Args) return nullptr;
if (CurTok == '[' || CurTok == '^') {
- cps::sourceDiagnostic(Lex.getLine(), "error: record/array/pointer access on a function result is not supported at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "record/array/pointer access on a function result is not supported");
return nullptr;
}
return ParseResultMethods(std::make_unique<CallExprAST>(IdName, std::move(*Args), Line, std::move(TemplateArgs)), Line);
@@ -135,7 +134,7 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
std::unique_ptr<MemberCallExprAST> Parser::ParseMemberCall(
const std::string &Name, std::vector<DesignatorAccess> Accesses, int Line) {
if (Accesses.empty() || Accesses.back().Kind != AccessKind::Field) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected a method name after '.' at line %d\n", Line);
+ cps::sourceError(Line, "Expected a method name after '.'");
return nullptr;
}
std::string Method = Accesses.back().FieldName;
@@ -149,8 +148,7 @@ std::unique_ptr<MemberCallExprAST> Parser::ParseMemberCall(
Args = std::move(*Parsed);
}
if (CurTok == '[' || CurTok == '^') {
- cps::sourceDiagnostic(Lex.getLine(), "error: record/array/pointer access on a function result is not supported at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "record/array/pointer access on a function result is not supported");
return nullptr;
}
auto Result = ParseResultMethods(std::make_unique<MemberCallExprAST>(std::move(Receiver), Method, std::move(Args), Line), Line);
@@ -172,7 +170,7 @@ bool Parser::ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out) {
} else if (CurTok == '.') {
getNextToken();
if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete && CurTok != tok_operator) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected field name after '.' at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected field name after '.'");
return false;
}
DesignatorAccess A;
@@ -209,7 +207,7 @@ std::unique_ptr<ExprAST> Parser::ParseParenExpr() {
std::string TypeName = ParseTypeName();
if (TypeName.empty()) return nullptr;
if (CurTok != ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ')' after cast type at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ')' after cast type");
return nullptr;
}
getNextToken();
@@ -220,7 +218,7 @@ std::unique_ptr<ExprAST> Parser::ParseParenExpr() {
auto V = ParseExpression();
if (!V) return nullptr;
if (CurTok != ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected ')' at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "expected ')'");
return nullptr;
}
getNextToken();
@@ -239,11 +237,11 @@ std::unique_ptr<ExprAST> Parser::ParseResultMethods(std::unique_ptr<ExprAST> Exp
Name = CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
getNextToken();
} else {
- sourceDiagnostic(Lex.getLine(), "error: Expected method name at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected method name");
return nullptr;
}
if (CurTok != '(') {
- sourceDiagnostic(Lex.getLine(), "error: Expected '(' after method name at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected '(' after method name");
return nullptr;
}
getNextToken();
@@ -264,7 +262,7 @@ std::unique_ptr<ExprAST> Parser::ParsePrimary() {
std::unique_ptr<ExprAST> Parser::ParsePrimaryImpl() {
switch (CurTok) {
case tok_new:
- cps::sourceDiagnostic(Lex.getLine(), "error: NEW is only allowed on the right of an assignment at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "NEW is only allowed on the right of an assignment");
return nullptr;
case tok_identifier: case tok_operator: return ParseIdentifierExpr();
case tok_number_int: return ParseNumberExpr();
@@ -289,8 +287,8 @@ std::unique_ptr<ExprAST> Parser::ParsePrimaryImpl() {
}
case '(': return ParseParenExpr();
default:
- cps::sourceDiagnostic(Lex.getLine(), "error: unknown token '%c' (%d) at line %d when expecting an expression\n",
- (char)CurTok, CurTok, Lex.getLine());
+ cps::sourceError(Lex.getLine(), "unknown token '%c' (%d) when expecting an expression",
+ (char)CurTok, CurTok);
return nullptr;
}
}
@@ -315,14 +313,13 @@ std::unique_ptr<ExprAST> Parser::ParseUnaryImpl() {
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: '^' (address-of) requires a variable, array element or record field at line %d\n",
- Line);
+ cps::sourceError(Line, "'^' (address-of) requires a variable, array element or record field");
return nullptr;
}
std::string BaseName = Lex.IdentifierStr;
getNextToken();
if (CurTok == '(') {
- cps::sourceDiagnostic(Lex.getLine(), "error: cannot take the address of a function result at line %d\n", Line);
+ cps::sourceError(Line, "cannot take the address of a function result");
return nullptr;
}
std::vector<DesignatorAccess> Accesses;
@@ -389,7 +386,7 @@ std::optional<std::vector<std::unique_ptr<ExprAST>>> Parser::ParseExprList(char
if (CurTok == CloseDelim) break;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or '%c' at line %d\n", CloseDelim, Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' or '%c'", CloseDelim);
return std::nullopt;
}
getNextToken();
diff --git a/lib/Parser/ParserFile.cc b/lib/Parser/ParserFile.cc
index 99a939f..c51c7e7 100644
--- a/lib/Parser/ParserFile.cc
+++ b/lib/Parser/ParserFile.cc
@@ -14,7 +14,7 @@ std::unique_ptr<StmtAST> Parser::ParseOpenFile() {
if (!FileName) return nullptr;
if (CurTok != tok_for) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected FOR after OPENFILE filename at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected FOR after OPENFILE filename");
return nullptr;
}
getNextToken(); // eat FOR
@@ -25,11 +25,10 @@ std::unique_ptr<StmtAST> Parser::ParseOpenFile() {
case tok_write: Mode = FileMode::Write; break;
case tok_append: Mode = FileMode::Append; break;
case tok_random:
- cps::sourceDiagnostic(Lex.getLine(), "error: File mode RANDOM is not implemented (text files only) at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "File mode RANDOM is not implemented (text files only)");
return nullptr;
default:
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected file mode READ, WRITE or APPEND at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected file mode READ, WRITE or APPEND");
return nullptr;
}
getNextToken(); // eat the mode keyword
@@ -46,13 +45,13 @@ std::unique_ptr<StmtAST> Parser::ParseReadFile() {
if (!FileName) return nullptr;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' after READFILE filename at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' after READFILE filename");
return nullptr;
}
getNextToken(); // eat ','
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected variable name after ',' in READFILE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected variable name after ',' in READFILE");
return nullptr;
}
std::string VarName = Lex.IdentifierStr;
@@ -62,7 +61,7 @@ std::unique_ptr<StmtAST> Parser::ParseReadFile() {
// Consume the whole designator so it cannot replay as statements.
std::vector<DesignatorAccess> Dummy;
ParseDesignatorSuffix(Dummy);
- cps::sourceDiagnostic(Lex.getLine(), "error: READFILE target must be a plain STRING variable at line %d\n", Line);
+ cps::sourceError(Line, "READFILE target must be a plain STRING variable");
return nullptr;
}
@@ -78,7 +77,7 @@ std::unique_ptr<StmtAST> Parser::ParseWriteFile() {
if (!FileName) return nullptr;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' after WRITEFILE filename at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' after WRITEFILE filename");
return nullptr;
}
getNextToken(); // eat ','
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index a1f04c0..823e17f 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -15,8 +15,7 @@ bool Parser::ParseSignedIntBound(int64_t &Out) {
getNextToken();
}
if (CurTok != tok_number_int) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Array parameter bounds must be integer literals at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Array parameter bounds must be integer literals");
return false;
}
return ParseIntegerValue(Negative, Out);
@@ -37,8 +36,7 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
while (CurTok != ')') {
if (CurTok == tok_byref) {
if (!AllowByRef) {
- cps::sourceDiagnostic(Lex.getLine(), "error: BYREF parameters are not allowed in a FUNCTION (procedures only) at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "BYREF parameters are not allowed in a FUNCTION (procedures only)");
return std::nullopt;
}
Current = PassMode::ByRef;
@@ -49,7 +47,7 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
}
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected argument name at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected argument name");
return std::nullopt;
}
ParamDecl P;
@@ -59,7 +57,7 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
getNextToken();
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' after argument name at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ':' after argument name");
return std::nullopt;
}
getNextToken();
@@ -73,8 +71,7 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
int64_t Lo = 0, Hi = 0;
if (!ParseSignedIntBound(Lo)) return std::nullopt;
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' between array parameter bounds at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ':' between array parameter bounds");
return std::nullopt;
}
getNextToken();
@@ -82,8 +79,7 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
P.DeclaredBounds.emplace_back(Lo, Hi);
if (CurTok == ']') break;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or ']' in array parameter bounds at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' or ']' in array parameter bounds");
return std::nullopt;
}
getNextToken();
@@ -94,8 +90,8 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
P.Rank = 1; // ARRAY OF T: bounds travel with the argument
}
if (CurTok != tok_of) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected OF after ARRAY in parameter '%s' at line %d\n",
- P.Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected OF after ARRAY in parameter '%s'",
+ P.Name.c_str());
return std::nullopt;
}
getNextToken();
@@ -103,29 +99,29 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
P.TypeName = ParseTypeName(false);
if (P.TypeName.empty()) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected argument type for '%s'\n", P.Name.c_str());
+ cps::sourceError(Lex.getLine(), "Expected argument type for '%s'", 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);
+ sourceError(P.Line, "A pack parameter must use the current template pack and cannot be ARRAY");
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);
+ sourceError(P.Line, "A pack parameter must be last");
return std::nullopt;
}
} else if (isTemplatePack(P.TypeName)) {
- sourceDiagnostic(P.Line, "error: A template parameter pack requires '...' at line %d\n", P.Line);
+ sourceError(P.Line, "A template parameter pack requires '...'");
return std::nullopt;
}
for (const ParamDecl &Prev : Params) {
if (sameIdentifier(Prev.Name, P.Name)) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Duplicate parameter name '%s' at line %d\n",
- P.Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Duplicate parameter name '%s'",
+ P.Name.c_str());
return std::nullopt;
}
}
@@ -133,12 +129,12 @@ std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef
if (CurTok == ')') break;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or ')'\n");
+ cps::sourceError(Lex.getLine(), "Expected ',' or ')'");
return std::nullopt;
}
getNextToken();
if (CurTok == ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected another parameter after ',' at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected another parameter after ','");
return std::nullopt;
}
}
@@ -150,13 +146,13 @@ std::string Parser::ParseOperatorName(int &Op) {
getNextToken(); // OPERATOR
Op = CurTok;
if (operatorSpelling(Op).empty()) {
- sourceDiagnostic(Lex.getLine(), "error: Expected a supported operator after OPERATOR at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected a supported operator after OPERATOR");
getNextToken();
return {};
}
getNextToken();
if (CurTok != '(') {
- sourceDiagnostic(Lex.getLine(), "error: Expected '(' after operator name at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected '(' after operator name");
return {};
}
return operatorName(Op);
@@ -166,15 +162,15 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
int Line = Lex.getLine();
getNextToken();
if (CurTok == tok_delete || (CurTok == tok_identifier && sameIdentifier(Lex.IdentifierStr, "DELETE"))) {
- cps::sourceDiagnostic(Lex.getLine(), "error: destructor DELETE must be a PROCEDURE at line %d\n", Line);
+ cps::sourceError(Line, "destructor DELETE must be a PROCEDURE");
return nullptr;
}
if (CurTok == tok_new || (CurTok == tok_identifier && sameIdentifier(Lex.IdentifierStr, "NEW"))) {
- cps::sourceDiagnostic(Lex.getLine(), "error: constructor NEW must be a PROCEDURE at line %d\n", Line);
+ cps::sourceError(Line, "constructor NEW must be a PROCEDURE");
return nullptr;
}
if (CurTok != tok_identifier && CurTok != tok_operator) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected function name\n");
+ cps::sourceError(Lex.getLine(), "Expected function name");
return nullptr;
}
@@ -184,7 +180,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
Name = ParseOperatorName(Operator);
if (Name.empty()) return nullptr;
if (!IsOverload) {
- sourceDiagnostic(Line, "error: An operator function must be declared OVERLOAD at line %d\n", Line);
+ sourceError(Line, "An operator function must be declared OVERLOAD");
return nullptr;
}
} else {
@@ -199,13 +195,12 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
if (CurTok == tok_returns) {
getNextToken();
if (CurTok == tok_array) {
- cps::sourceDiagnostic(Lex.getLine(), "error: A FUNCTION cannot RETURN an array; use a BYREF PROCEDURE parameter instead at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "A FUNCTION cannot RETURN an array; use a BYREF PROCEDURE parameter instead");
return nullptr;
}
RetType = ParseTypeName(true);
if (RetType.empty()) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected return type for function '%s'\n", Name.c_str());
+ cps::sourceError(Lex.getLine(), "Expected return type for function '%s'", Name.c_str());
return nullptr;
}
}
@@ -220,7 +215,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
PackNames = std::move(SavedPacks);
if (CurTok != tok_endfunction) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDFUNCTION\n");
+ cps::sourceError(Lex.getLine(), "expected ENDFUNCTION");
return nullptr;
}
getNextToken();
@@ -234,7 +229,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete && CurTok != tok_operator) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected procedure name\n");
+ cps::sourceError(Lex.getLine(), "Expected procedure name");
return nullptr;
}
@@ -245,17 +240,17 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
OperatorName = ParseOperatorName(Operator);
if (OperatorName.empty()) return nullptr;
if (Operator != tok_assign || OwnerClass.empty() || !IsOverload) {
- sourceDiagnostic(Line, "error: Assignment operator must be an OVERLOAD member PROCEDURE at line %d\n", Line);
+ sourceError(Line, "Assignment operator must be an OVERLOAD member PROCEDURE");
return nullptr;
}
}
std::string Name = IsOperator ? OperatorName : CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
if (OwnerClass.empty() && sameIdentifier(Name, "DELETE")) {
- cps::sourceDiagnostic(Lex.getLine(), "error: destructor DELETE is only allowed in a CLASS at line %d\n", Line);
+ cps::sourceError(Line, "destructor DELETE is only allowed in a CLASS");
return nullptr;
}
if (OwnerClass.empty() && sameIdentifier(Name, "NEW")) {
- cps::sourceDiagnostic(Lex.getLine(), "error: constructor NEW is only allowed in a CLASS at line %d\n", Line);
+ cps::sourceError(Line, "constructor NEW is only allowed in a CLASS");
return nullptr;
}
if (!IsOperator) getNextToken();
@@ -263,7 +258,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
auto Args = ParsePrototypeArgs(/*AllowByRef=*/true);
if (!Args) return nullptr;
if (sameIdentifier(Name, "DELETE") && !Args->empty()) {
- cps::sourceDiagnostic(Lex.getLine(), "error: destructor DELETE must have no parameters at line %d\n", Line);
+ cps::sourceError(Line, "destructor DELETE must have no parameters");
return nullptr;
}
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID", false, OwnerClass, IsPublic, IsOverload);
@@ -276,7 +271,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
PackNames = std::move(SavedPacks);
if (CurTok != tok_endprocedure) {
- cps::sourceDiagnostic(Lex.getLine(), "error: expected ENDPROCEDURE\n");
+ cps::sourceError(Lex.getLine(), "expected ENDPROCEDURE");
return nullptr;
}
getNextToken();
@@ -293,19 +288,18 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_string_literal || Lex.StringVal != "C") {
- cps::sourceDiagnostic(Lex.getLine(), "error: EXTERN must be followed by \"C\" at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "EXTERN must be followed by \"C\"");
return nullptr;
}
getNextToken();
if (CurTok != tok_extern_function && CurTok != tok_extern_procedure) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected EXTERN_FUNCTION or EXTERN_PROCEDURE after EXTERN \"C\" at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected EXTERN_FUNCTION or EXTERN_PROCEDURE after EXTERN \"C\"");
return nullptr;
}
bool IsFunction = CurTok == tok_extern_function;
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected C function name at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected C function name");
return nullptr;
}
std::string Name = Lex.IdentifierStr;
@@ -314,8 +308,8 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
std::vector<ParamDecl> Params;
bool IsVariadic = false;
if (CurTok != '(') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '(' after C function name '%s' at line %d\n",
- Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected '(' after C function name '%s'",
+ Name.c_str());
return nullptr;
}
getNextToken();
@@ -324,14 +318,13 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
IsVariadic = true;
getNextToken();
if (CurTok != ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: '...' must be the last parameter at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "'...' must be the last parameter");
return nullptr;
}
break;
}
if (CurTok == tok_byref || CurTok == tok_byval) {
- cps::sourceDiagnostic(Lex.getLine(), "error: EXTERN parameters cannot be BYREF or BYVAL; use a C pointer type at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "EXTERN parameters cannot be BYREF or BYVAL; use a C pointer type");
return nullptr;
}
ParamDecl P;
@@ -342,12 +335,12 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
Params.push_back(std::move(P));
if (CurTok == ')') break;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or ')' at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' or ')'");
return nullptr;
}
getNextToken();
if (CurTok == ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected another parameter after ',' at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected another parameter after ','");
return nullptr;
}
}
@@ -356,15 +349,15 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
std::string RetType = "VOID";
if (CurTok == tok_returns) {
if (!IsFunction) {
- cps::sourceDiagnostic(Lex.getLine(), "error: EXTERN_PROCEDURE cannot have RETURNS at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "EXTERN_PROCEDURE cannot have RETURNS");
return nullptr;
}
getNextToken();
RetType = ParseTypeName(false);
if (RetType.empty()) return nullptr;
} else if (IsFunction) {
- cps::sourceDiagnostic(Lex.getLine(), "error: EXTERN_FUNCTION '%s' requires RETURNS at line %d\n",
- Name.c_str(), Line);
+ cps::sourceError(Line, "EXTERN_FUNCTION '%s' requires RETURNS",
+ Name.c_str());
return nullptr;
}
@@ -379,7 +372,7 @@ std::unique_ptr<StmtAST> Parser::ParseCallStmt() {
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_identifier && CurTok != tok_operator) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected callee name after CALL\n");
+ cps::sourceError(Lex.getLine(), "Expected callee name after CALL");
return nullptr;
}
@@ -399,7 +392,7 @@ std::unique_ptr<StmtAST> Parser::ParseCallStmt() {
if (!Parsed) return nullptr;
TemplateArgs = std::move(*Parsed);
if (CurTok != '(') {
- sourceDiagnostic(Line, "error: Expected '(' after explicit template arguments at line %d\n", Line);
+ sourceError(Line, "Expected '(' after explicit template arguments");
return nullptr;
}
}
diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc
index 21696d0..22cffeb 100644
--- a/lib/Parser/ParserType.cc
+++ b/lib/Parser/ParserType.cc
@@ -18,7 +18,7 @@ std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
bool AtTopLevel = BlockDepth == 1 && CurrentTemplate.empty();
getNextToken();
if (CurTok != '<') {
- sourceDiagnostic(Line, "error: Expected '<' after TEMPLATE at line %d\n", Line);
+ sourceError(Line, "Expected '<' after TEMPLATE");
return nullptr;
}
getNextToken();
@@ -28,19 +28,19 @@ std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
bool SeenPack = false;
while (true) {
if (CurTok != tok_identifier) {
- sourceDiagnostic(Lex.getLine(), "error: Expected template parameter name at line %d\n", Lex.getLine());
+ sourceError(Lex.getLine(), "Expected template parameter name");
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);
+ sourceError(P.Line, "Duplicate template parameter '%s'", P.Name.c_str());
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);
+ sourceError(P.Line, "A template may have only one parameter pack");
Invalid = true;
}
SeenPack = true;
@@ -49,11 +49,11 @@ std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
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());
+ sourceError(Lex.getLine(), "Expected ',' or '>' in TEMPLATE");
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);
+ sourceError(Params.back().Line, "A template parameter pack must be last");
Invalid = true;
}
getNextToken();
@@ -70,7 +70,7 @@ std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
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);
+ sourceError(Line, "Operator templates are not supported");
Invalid = true;
int End = CurTok == tok_function ? tok_endfunction : tok_endprocedure;
do { getNextToken(); } while (CurTok != End && CurTok != tok_eof);
@@ -89,17 +89,17 @@ std::unique_ptr<StmtAST> Parser::ParseTemplateDecl() {
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);
+ sourceError(Line, "TEMPLATE cannot prefix TYPE; use CLASS");
ParseTypeDecl();
Invalid = true;
} else {
- sourceDiagnostic(Line, "error: TEMPLATE must be followed by FUNCTION, PROCEDURE or CLASS at line %d\n", Line);
+ sourceError(Line, "TEMPLATE must be followed by FUNCTION, PROCEDURE or CLASS");
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);
+ sourceError(Line, "TEMPLATE declarations are only allowed at top level");
Invalid = true;
}
if (Invalid || !Decl) return nullptr;
@@ -117,7 +117,7 @@ std::optional<std::vector<std::string>> Parser::ParseTemplateArgs() {
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);
+ sourceError(Line, "Only the current template parameter pack may use '...' in a type argument");
return std::nullopt;
}
Type += "...";
@@ -126,7 +126,7 @@ std::optional<std::vector<std::string>> Parser::ParseTemplateArgs() {
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());
+ sourceError(Lex.getLine(), "Expected ',' or '>' in template arguments");
return std::nullopt;
}
getNextToken();
@@ -153,7 +153,7 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
getNextToken(); // eat TYPE or CLASS
if (CurTok != tok_identifier && !(IsClass && CurTok == tok_string_kw)) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected type name after %s at line %d\n", Keyword, Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected type name after %s", Keyword);
if (IsClass) ParseRecordBody("<unnamed>", Line, tok_endclass);
return nullptr;
}
@@ -169,18 +169,18 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
if (CurTok == tok_c_type) {
getNextToken();
if (CurTok != '(') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '(' after C_TYPE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected '(' after C_TYPE");
return nullptr;
}
getNextToken();
if (CurTok != tok_string_literal) {
- cps::sourceDiagnostic(Lex.getLine(), "error: C_TYPE requires a string literal at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "C_TYPE requires a string literal");
return nullptr;
}
std::string Expression = Lex.StringVal;
getNextToken();
if (CurTok != ')') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ')' after C_TYPE string at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ')' after C_TYPE string");
return nullptr;
}
getNextToken();
@@ -190,16 +190,16 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
std::vector<std::string> Values;
while (true) {
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected an enum value name in TYPE %s at line %d\n",
- Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected an enum value name in TYPE %s",
+ Name.c_str());
return nullptr;
}
Values.push_back(Lex.IdentifierStr);
getNextToken();
if (CurTok == ')') break;
if (CurTok != ',') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or ')' in TYPE %s at line %d\n",
- Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ',' or ')' in TYPE %s",
+ Name.c_str());
return nullptr;
}
getNextToken();
@@ -212,12 +212,12 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
if (Pointee.empty()) return nullptr;
Decl = std::make_unique<PointerTypeDeclAST>(Name, Pointee);
} else if (CurTok == tok_set) {
- cps::sourceDiagnostic(Lex.getLine(), "error: SET OF types are not implemented at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "SET OF types are not implemented");
getNextToken();
return nullptr;
} else {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '(', '^' or C_TYPE after '=' in TYPE %s at line %d\n",
- Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected '(', '^' or C_TYPE after '=' in TYPE %s",
+ Name.c_str());
return nullptr;
}
} else {
@@ -231,7 +231,7 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
}
if (!AtTopLevel) {
- cps::sourceDiagnostic(Lex.getLine(), "error: %s declarations are only allowed at top level at line %d\n", Keyword, Line);
+ cps::sourceError(Line, "%s declarations are only allowed at top level", Keyword);
return nullptr;
}
TypeNames.insert(Name);
@@ -272,7 +272,7 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
bool IsOverload = CurTok == tok_overload;
if (IsOverload) getNextToken();
if (CurTok != tok_function && CurTok != tok_procedure) {
- cps::sourceDiagnostic(Lex.getLine(), "error: OVERLOAD must be followed by FUNCTION or PROCEDURE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "OVERLOAD must be followed by FUNCTION or PROCEDURE");
HadFieldError = true;
continue;
}
@@ -284,9 +284,9 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
}
} else if (ParseRecordField(Fields, IsPublic)) continue;
} else {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected %s or %s in %s %s at line %d\n",
+ cps::sourceError(Lex.getLine(), "Expected %s or %s in %s %s",
IsClass ? "field, method, PUBLIC or PRIVATE" : "DECLARE", EndKeyword,
- Keyword, Name.c_str(), Lex.getLine());
+ Keyword, Name.c_str());
}
HadFieldError = true;
// Consume invalid bodies rather than replaying their contents as
@@ -305,14 +305,14 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
}
}
if (CurTok != EndToken) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected %s in %s %s at line %d\n",
- EndKeyword, Keyword, Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected %s in %s %s",
+ EndKeyword, Keyword, Name.c_str());
return std::nullopt;
}
getNextToken(); // eat body terminator
if (Fields.empty() && (!Methods || Methods->empty()) && !HadFieldError) {
- cps::sourceDiagnostic(Lex.getLine(), "error: %s %s has no %s at line %d\n", Keyword, Name.c_str(),
- IsClass ? "members" : "fields", Line);
+ cps::sourceError(Line, "%s %s has no %s", Keyword, Name.c_str(),
+ IsClass ? "members" : "fields");
return std::nullopt;
}
if (HadFieldError) return std::nullopt;
@@ -326,29 +326,28 @@ bool Parser::ParseRecordField(std::vector<RecordFieldDecl> &Out, bool IsPublic)
: CurTok == tok_public ? "PUBLIC" : "PRIVATE";
if (CurTok != tok_identifier) getNextToken(); // optional class access keyword
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected field name after %s at line %d\n", Introducer, Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected field name after %s", Introducer);
return false;
}
std::string FieldName = Lex.IdentifierStr;
getNextToken();
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' after field name %s at line %d\n",
- FieldName.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ':' after field name %s",
+ FieldName.c_str());
return false;
}
getNextToken();
if (CurTok == tok_array) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Array fields inside records are not implemented at line %d\n",
- Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Array fields inside records are not implemented");
return false;
}
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);
+ sourceError(Line, "Pack fields are not supported");
if (CurTok == tok_ellipsis) getNextToken();
return false;
}
@@ -388,7 +387,7 @@ std::string Parser::ParseTypeName(bool AllowVoid) {
if (CurTok == tok_identifier) {
std::string TypeName = Lex.IdentifierStr;
if (!AllowVoid && sameIdentifier(TypeName, "VOID")) {
- cps::sourceDiagnostic(Lex.getLine(), "error: VOID is not allowed here\n");
+ cps::sourceError(Lex.getLine(), "VOID is not allowed here");
return "";
}
getNextToken();
@@ -405,7 +404,7 @@ std::string Parser::ParseTypeName(bool AllowVoid) {
return TypeName;
}
- cps::sourceDiagnostic(Lex.getLine(), "error: Unknown type name\n");
+ cps::sourceError(Lex.getLine(), "Unknown type name");
return "";
}
@@ -413,14 +412,14 @@ std::string Parser::ParseTypeName(bool AllowVoid) {
std::unique_ptr<StmtAST> Parser::ParseDeclare() {
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected identifier after DECLARE at line %d\n", Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected identifier after DECLARE");
return nullptr;
}
std::string Name = Lex.IdentifierStr;
getNextToken();
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' after variable name '%s' at line %d\n", Name.c_str(), Lex.getLine());
+ cps::sourceError(Lex.getLine(), "Expected ':' after variable name '%s'", Name.c_str());
return nullptr;
}
getNextToken();
@@ -428,7 +427,7 @@ std::unique_ptr<StmtAST> Parser::ParseDeclare() {
if (CurTok == tok_array) {
getNextToken();
if (CurTok != '[') {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '[' after ARRAY\n");
+ cps::sourceError(Lex.getLine(), "Expected '[' after ARRAY");
return nullptr;
}
getNextToken();
@@ -440,7 +439,7 @@ std::unique_ptr<StmtAST> Parser::ParseDeclare() {
if (!Lower) return nullptr;
if (CurTok != tok_colon) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ':' in array range (e.g. 1:10)\n");
+ cps::sourceError(Lex.getLine(), "Expected ':' in array range (e.g. 1:10)");
return nullptr;
}
getNextToken();
@@ -455,13 +454,13 @@ std::unique_ptr<StmtAST> Parser::ParseDeclare() {
getNextToken();
continue;
}
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected ',' or ']' in array dims\n");
+ cps::sourceError(Lex.getLine(), "Expected ',' or ']' in array dims");
return nullptr;
}
getNextToken();
if (CurTok != tok_of) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected OF after array dims\n");
+ cps::sourceError(Lex.getLine(), "Expected OF after array dims");
return nullptr;
}
getNextToken();
@@ -494,13 +493,13 @@ std::unique_ptr<StmtAST> Parser::ParseConstant() {
int Line = Lex.getLine();
getNextToken();
if (CurTok != tok_identifier) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected identifier after CONSTANT at line %d\n", Line);
+ cps::sourceError(Line, "Expected identifier after CONSTANT");
return nullptr;
}
std::string Name = Lex.IdentifierStr;
getNextToken();
if (CurTok != tok_eq) {
- cps::sourceDiagnostic(Lex.getLine(), "error: Expected '=' in CONSTANT declaration at line %d\n", Line);
+ cps::sourceError(Line, "Expected '=' in CONSTANT declaration");
return nullptr;
}
getNextToken();
@@ -510,7 +509,7 @@ std::unique_ptr<StmtAST> Parser::ParseConstant() {
bool Literal = Numeric || CurTok == tok_true || CurTok == tok_false ||
CurTok == tok_string_literal || CurTok == tok_c_string_literal || CurTok == tok_char_literal;
if (!Literal || (Negative && !Numeric)) {
- cps::sourceDiagnostic(Lex.getLine(), "error: CONSTANT initializer must be a literal at line %d\n", Line);
+ cps::sourceError(Line, "CONSTANT initializer must be a literal");
return nullptr;
}
std::unique_ptr<ExprAST> Value;
@@ -526,7 +525,7 @@ std::unique_ptr<StmtAST> Parser::ParseConstant() {
if (Negative) Value = std::make_unique<UnaryExprAST>('-', std::move(Value));
}
if (GetTokPrecedence() >= 0) {
- cps::sourceDiagnostic(Lex.getLine(), "error: CONSTANT initializer must be a literal, not an expression, at line %d\n", Line);
+ cps::sourceError(Line, "CONSTANT initializer must be a literal, not an expression");
return nullptr;
}
return std::make_unique<ConstantStmtAST>(std::move(Name), std::move(Value));
diff --git a/tests/iii/template-class-pack-field/case.json b/tests/iii/template-class-pack-field/case.json
index 2ad0199..1196e7b 100644
--- a/tests/iii/template-class-pack-field/case.json
+++ b/tests/iii/template-class-pack-field/case.json
@@ -1,5 +1,5 @@
{
"errors": [
- {"reason": "pack.*field|field.*pack", "line": 3}
+ {"reason": "(?i)pack.*field|field.*pack", "line": 3}
]
}
diff --git a/tests/run.pl b/tests/run.pl
index 86eff4f..2835d5e 100644
--- a/tests/run.pl
+++ b/tests/run.pl
@@ -113,18 +113,21 @@ sub check_diagnostics {
if (length($result->{stdout})) {
push @errors, 'unexpected stdout: ' . show_bytes(substr($result->{stdout}, 0, 1000));
}
- # ASCII markers (error:, line N) are matched on raw bytes; the compiler
- # emits UTF-8 and ASCII patterns are unaffected by decoding.
+ # Diagnostics use the gcc layout "<path>:<line>: error: <message>" and are
+ # matched on raw bytes; the compiler emits UTF-8 and ASCII patterns are
+ # unaffected by decoding. The non-greedy path keeps Windows "D:/" intact.
my @lines = split_records(normalized($result->{stderr}));
for my $expectation (@{$expectations}) {
my $reason = $expectation->{reason};
my $line_no = $expectation->{line};
+ my $file = $expectation->{file};
my @matching = grep { /error:/ && /$reason/ } @lines;
my $found = 0;
for my $line (@matching) {
- my $file = $expectation->{file};
- if ($line =~ /\bline $line_no\b/ &&
- (!defined($file) || $line =~ /\bline $line_no in \Q$file\E(?:\s|$)/)) {
+ next unless $line =~ /^(.+?):(\d+): error: (.*)$/;
+ my ($path, $number, $message) = ($1, $2, $3);
+ if ($number == $line_no && $message =~ /$reason/ &&
+ (!defined($file) || $path eq $file)) {
$found = 1;
last;
}
diff --git a/tests/test_runner.pl b/tests/test_runner.pl
index 16a0e35..a074f98 100644
--- a/tests/test_runner.pl
+++ b/tests/test_runner.pl
@@ -29,7 +29,7 @@ for my $case ([1, ''], [-11, ''], [0, 'unexpected']) {
my $good = {
returncode => 1,
stdout => '',
- stderr => "error: Expected ':' at line 3\ncompilation terminated.\n",
+ stderr => "/fixtures/program.cps:3: error: Expected ':'\ncompilation terminated.\n",
};
my $accepted = eval {
check_diagnostics($good, [{reason => "Expected ':'", line => 3}]);
@@ -37,9 +37,10 @@ my $accepted = eval {
};
ok($accepted, 'matching reason and location accepted');
for my $bad (
- "error: Expected ':' at line 4",
- "error: Expected ':'",
- "error: Expected ':' at line 30\nerror: unrelated at line 3",
+ "/fixtures/program.cps:4: error: Expected ':'",
+ "/fixtures/program.cps: error: Expected ':'",
+ "error: Expected ':' at line 3",
+ "/fixtures/program.cps:30: error: Expected ':'\n/fixtures/program.cps:3: error: unrelated",
)
{
my $ok = eval {
@@ -54,11 +55,11 @@ for my $bad (
my $file_expectation = [{reason => "Expected ':'", line => 3, file => '/fixtures/lib.cps'}];
for my $sample (
- ["error: Expected ':' at line 3 in /fixtures/lib.cps", 1],
- ["error: Expected ':' at line 3 in /fixtures/program.cps", 0],
- ["error: Expected ':' at line 3 in /fixtures/lib.cps.extra", 0],
- ["error: Expected ':' at line 30 in /fixtures/lib.cps", 0],
- ["error: Expected ':' at line 3\nerror: unrelated at line 3 in /fixtures/lib.cps", 0],
+ ["/fixtures/lib.cps:3: error: Expected ':'", 1],
+ ["/fixtures/program.cps:3: error: Expected ':'", 0],
+ ["/fixtures/lib.cps.extra:3: error: Expected ':'", 0],
+ ["/fixtures/lib.cps:30: error: Expected ':'", 0],
+ ["/fixtures/program.cps:3: error: Expected ':'\n/fixtures/lib.cps:3: error: unrelated", 0],
) {
my ($diagnostic, $expected) = @{$sample};
my $ok = eval {
@@ -69,6 +70,14 @@ for my $sample (
is($ok ? 1 : 0, $expected, 'file-qualified diagnostic: ' . show_bytes($diagnostic));
}
+my $drive_ok = eval {
+ check_diagnostics({returncode => 1, stdout => '',
+ stderr => "D:/fixtures/lib.cps:3: error: Expected ':'\ncompilation terminated.\n"},
+ [{reason => "Expected ':'", line => 3, file => 'D:/fixtures/lib.cps'}]);
+ 1;
+};
+ok($drive_ok, 'drive-letter path keeps its colon');
+
my $dir = tempdir(CLEANUP => 1);
my $state = commands_new($dir);
my $ran = eval { run_command($state, [$^X, '-e', 'sleep 5'], $dir, '', 1); 1; };
diff --git a/tools/driver/main.cc b/tools/driver/main.cc
index a55d1af..b705843 100644
--- a/tools/driver/main.cc
+++ b/tools/driver/main.cc
@@ -159,7 +159,7 @@ int main(int argc, char **argv) {
llvm::errs() << "cpsc: fatal error: cannot open source file '" << InputPath << "'\n";
return compilationTerminated();
}
- cps::DiagnosticSource = InputPaths.size() > 1 ? InputPath : "";
+ cps::DiagnosticSource = InputPath;
cps::Lexer Lex(Input);
cps::Parser Parser(Lex);
Programs[I] = Parser.Parse();
@@ -176,7 +176,7 @@ int main(int argc, char **argv) {
Units.back().Visible[J] = true;
}
for (size_t I = 0; I < Units.size(); ++I) {
- cps::DiagnosticSource = Units.size() > 1 ? Units[I].Path : "";
+ cps::DiagnosticSource = Units[I].Path;
for (const auto &Stmt : Units[I].Statements) {
auto *Include = dynamic_cast<cps::IncludeStmtAST*>(Stmt.get());
if (!Include) continue;
@@ -193,9 +193,9 @@ int main(int argc, char **argv) {
}
}
if (!Found) {
- cps::sourceDiagnostic(Stmt->getLine(),
- "error: cannot INCLUDE '%s': not given on the command line at line %d\n",
- Include->getPath().c_str(), Stmt->getLine());
+ cps::sourceError(Stmt->getLine(),
+ "cannot INCLUDE '%s': not given on the command line",
+ Include->getPath().c_str());
return compilationTerminated();
}
}
@@ -212,7 +212,7 @@ int main(int argc, char **argv) {
llvm::StringSet<> ExternSymbols;
bool LibcLoaded = false;
for (size_t I = 0; I < Units.size(); ++I) {
- cps::DiagnosticSource = Units.size() > 1 ? Units[I].Path : "";
+ cps::DiagnosticSource = Units[I].Path;
for (const auto &Stmt : Units[I].Statements) {
auto *Object = dynamic_cast<cps::IncludeObjStmtAST*>(Stmt.get());
if (!Object) continue;
@@ -227,9 +227,9 @@ int main(int argc, char **argv) {
llvm::sys::path::append(Path, Object->getPath());
}
if (llvm::sys::path::extension(Path) != ".o") {
- cps::sourceDiagnostic(Stmt->getLine(),
- "error: INCLUDE_OBJ '%s' must name a .o object file or \"__libc\" at line %d\n",
- Object->getPath().c_str(), Stmt->getLine());
+ cps::sourceError(Stmt->getLine(),
+ "INCLUDE_OBJ '%s' must name a .o object file or \"__libc\"",
+ Object->getPath().c_str());
return compilationTerminated();
}
bool Seen = false;
@@ -238,8 +238,8 @@ int main(int argc, char **argv) {
if (Seen) continue;
std::string Error;
if (!collectObjectSymbols(Path, ExternSymbols, Error)) {
- cps::sourceDiagnostic(Stmt->getLine(), "error: cannot INCLUDE_OBJ '%s': %s at line %d\n",
- Object->getPath().c_str(), Error.c_str(), Stmt->getLine());
+ cps::sourceError(Stmt->getLine(), "cannot INCLUDE_OBJ '%s': %s",
+ Object->getPath().c_str(), Error.c_str());
return compilationTerminated();
}
Objects.push_back(std::string(Path));
@@ -247,12 +247,12 @@ int main(int argc, char **argv) {
}
bool MissingSymbol = false;
for (size_t I = 0; I < Units.size(); ++I) {
- cps::DiagnosticSource = Units.size() > 1 ? Units[I].Path : "";
+ cps::DiagnosticSource = Units[I].Path;
for (const auto &Stmt : Units[I].Statements) {
auto *Extern = dynamic_cast<cps::ExternDeclAST*>(Stmt.get());
if (!Extern || ExternSymbols.contains(Extern->getProto()->getName())) continue;
- cps::sourceDiagnostic(Stmt->getLine(), "error: C symbol '%s' not found in any INCLUDE_OBJ at line %d\n",
- Extern->getProto()->getName().c_str(), Stmt->getLine());
+ cps::sourceError(Stmt->getLine(), "C symbol '%s' not found in any INCLUDE_OBJ",
+ Extern->getProto()->getName().c_str());
MissingSymbol = true;
}
}