aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 17:21:14 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 17:21:14 +0000
commit3366a2e0fae05d9e792169e74471c7282bf18681 (patch)
tree0cc4cacdac7d32ee7671d6799c52f74953c2007e /lib
parent0fef2986daa2945c9e3d050c0f750d56792b30bd (diff)
feat: TYPE-system front end — tokens, designators, ParamDecl grammar
- Reserve TYPE/ENDTYPE/SET/DEFINE (-600 token block); SET/DEFINE parse to dedicated not-implemented errors (RANDOM precedent). - New TypeAST.h: enum/pointer/record decl nodes, designator chain (DesignatorExprAST + Index/Field/Deref accessors), AddrOfExprAST, DesignatorAssignStmtAST. Bare-name and single-index shapes degrade to their legacy nodes, so existing programs produce identical ASTs. - ParserType.cc: TYPE declarations (top-level only, enforced via a ParseBlock depth counter), DECLARE-only record bodies with local field-level error recovery. - Postfix [ ] . ^ chain in ParseIdentifierExpr; prefix ^ (address-of, designator operand only) in ParseUnary; assignment targets rebuilt on the same suffix parser. - Parameters: ParamDecl{name, type, mode, array rank/bounds} replaces the (name, type, byref) tuple. Pass mode is sticky across parameters and BYREF is rejected in FUNCTIONs, both per guide §8.3 (the old per-param reset made the guide's own SWAP example pass Y by value). ARRAY OF T / ARRAY[l:u,...] OF T parameter syntax parses; codegen rejects it until the parameter-passing phase lands. - CodeGen stubs report clear not-implemented errors for the new nodes so nothing is silently ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/CodeGen/CodeGen.cc18
-rw-r--r--lib/CodeGen/FunctionGen.cc41
-rw-r--r--lib/Lexer/Lexer.cc5
-rw-r--r--lib/Parser/Parser.cc146
-rw-r--r--lib/Parser/ParserFunction.cc101
-rw-r--r--lib/Parser/ParserType.cc130
6 files changed, 371 insertions, 70 deletions
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 4b9ebfb..8940e63 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -3,6 +3,7 @@
#include "cps/FileAST.h"
#include "cps/FileHandler.h"
#include "cps/Lexer.h"
+#include "cps/TypeAST.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Verifier.h"
#include <cstdarg>
@@ -619,6 +620,12 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
return FuncGen->emitCallExpr(Call, Args);
}
+ // TYPE-system stubs: parsed but not yet wired (phases C/D/E of the TYPE work).
+ if (dynamic_cast<DesignatorExprAST*>(Expr) || dynamic_cast<AddrOfExprAST*>(Expr)) {
+ reportError("Record field / pointer expressions are not implemented yet");
+ return nullptr;
+ }
+
return nullptr;
}
@@ -1039,4 +1046,15 @@ void CodeGen::emitStmt(StmtAST *Stmt) {
emitForStmt(ForStmt);
return;
}
+
+ // TYPE-system stubs: parsed but not yet wired (phases C/D of the TYPE work).
+ if (dynamic_cast<EnumTypeDeclAST*>(Stmt) || dynamic_cast<PointerTypeDeclAST*>(Stmt) ||
+ dynamic_cast<RecordTypeDeclAST*>(Stmt)) {
+ reportError("TYPE declarations are not implemented yet");
+ return;
+ }
+ if (dynamic_cast<DesignatorAssignStmtAST*>(Stmt)) {
+ reportError("Assignment to record fields / pointer targets is not implemented yet");
+ return;
+ }
}
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index df0a8b6..dc238b5 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -31,27 +31,24 @@ Type *FunctionGen::getLLVMType(const std::string &TypeName) {
return Resolved;
}
-void FunctionGen::createArgumentAllocas(Function *F, const std::vector<std::tuple<std::string, std::string, bool>> &Args) {
+void FunctionGen::createArgumentAllocas(Function *F, const std::vector<ParamDecl> &Params) {
Function::arg_iterator AI = F->arg_begin();
- for (unsigned Idx = 0, E = Args.size(); Idx != E; ++Idx, ++AI) {
- std::string ArgName = std::get<0>(Args[Idx]);
- std::string ArgTypeStr = std::get<1>(Args[Idx]);
- bool IsRef = std::get<2>(Args[Idx]);
-
+ for (const ParamDecl &P : Params) {
Value *ArgVal = &(*AI);
- ArgVal->setName(ArgName);
+ ++AI;
+ ArgVal->setName(P.Name);
- if (IsRef) {
- Symbols[ArgName] = {ArgVal, ArgTypeStr, false};
+ if (P.Mode == PassMode::ByRef) {
+ Symbols[P.Name] = {ArgVal, P.TypeName, false};
continue;
}
- Type *ArgType = getLLVMType(ArgTypeStr);
+ Type *ArgType = getLLVMType(P.TypeName);
IRBuilder<> TmpB(&F->getEntryBlock(), F->getEntryBlock().begin());
- AllocaInst *Alloca = TmpB.CreateAlloca(ArgType, nullptr, ArgName);
+ AllocaInst *Alloca = TmpB.CreateAlloca(ArgType, nullptr, P.Name);
Builder.CreateStore(ArgVal, Alloca);
- Symbols[ArgName] = {Alloca, ArgTypeStr, false};
+ Symbols[P.Name] = {Alloca, P.TypeName, false};
}
}
@@ -78,13 +75,19 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
Sig.ReturnTypeName = Proto->getReturnType();
std::vector<Type*> ArgTypes;
- for (const auto &Arg : Proto->getArgs()) {
- Type *T = getLLVMType(std::get<1>(Arg));
- if (std::get<2>(Arg)) {
+ for (const ParamDecl &P : Proto->getParams()) {
+ if (P.IsArray) {
+ fprintf(stderr, "Error: Array parameters are not implemented yet (parameter '%s' of %s)\n",
+ P.Name.c_str(), Proto->getName().c_str());
+ HadError = true;
+ return nullptr;
+ }
+ Type *T = getLLVMType(P.TypeName);
+ if (P.Mode == PassMode::ByRef) {
T = T->getPointerTo();
}
ArgTypes.push_back(T);
- Sig.Params.emplace_back(std::get<1>(Arg), std::get<2>(Arg));
+ Sig.Params.emplace_back(P.TypeName, P.Mode == PassMode::ByRef);
}
Type *RetType = getLLVMType(Proto->getReturnType());
@@ -106,8 +109,8 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
unsigned Idx = 0;
for (auto &Arg : F->args()) {
- if (Idx < Proto->getArgs().size()) {
- Arg.setName(std::get<0>(Proto->getArgs()[Idx++]));
+ if (Idx < Proto->getParams().size()) {
+ Arg.setName(Proto->getParams()[Idx++].Name);
}
}
return F;
@@ -131,7 +134,7 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST,
std::map<std::string, SymbolInfo> OldSymbols = Symbols;
Symbols.clear();
- createArgumentAllocas(TheFunction, Proto->getArgs());
+ createArgumentAllocas(TheFunction, Proto->getParams());
for (const auto &Stmt : FuncAST->getBody()) {
StmtEmitter(Stmt.get());
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index 3c56271..4b5acae 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -130,6 +130,11 @@ int Lexer::gettok() {
if (IdentifierStr == "APPEND") return tok_append;
if (IdentifierStr == "RANDOM") return tok_random;
+ if (IdentifierStr == "TYPE") return tok_type;
+ if (IdentifierStr == "ENDTYPE") return tok_endtype;
+ if (IdentifierStr == "SET") return tok_set;
+ if (IdentifierStr == "DEFINE") return tok_define;
+
return tok_identifier;
}
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index e67dda7..9563650 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -59,17 +59,56 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
getNextToken();
auto Args = ParseExprList(')', true);
if (!Args) return nullptr;
+ if (CurTok == '[' || CurTok == '.' || CurTok == '^') {
+ fprintf(stderr, "Error: record/array/pointer access on a function result is not supported at line %d\n",
+ Lex.getLine());
+ return nullptr;
+ }
return std::make_unique<CallExprAST>(IdName, std::move(*Args), Line);
}
- if (CurTok == '[') {
- getNextToken();
- auto Indices = ParseExprList(']', false);
- if (!Indices) return nullptr;
- return std::make_unique<ArrayAccessExprAST>(IdName, std::move(*Indices), Line);
- }
+ std::vector<DesignatorAccess> Accesses;
+ if (!ParseDesignatorSuffix(Accesses)) return nullptr;
- return std::make_unique<VariableExprAST>(IdName);
+ // Degrade rule: shapes that existed before designators keep their nodes.
+ if (Accesses.empty())
+ return std::make_unique<VariableExprAST>(IdName);
+ if (Accesses.size() == 1 && Accesses[0].Kind == AccessKind::Index)
+ return std::make_unique<ArrayAccessExprAST>(IdName, std::move(Accesses[0].Indices), Line);
+ return std::make_unique<DesignatorExprAST>(IdName, std::move(Accesses), Line);
+}
+
+// Postfix accessor chain after an identifier: '[' exprs ']' | '.' IDENT | '^'
+bool Parser::ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out) {
+ while (true) {
+ if (CurTok == '[') {
+ getNextToken();
+ auto Indices = ParseExprList(']', false);
+ if (!Indices) return false;
+ DesignatorAccess A;
+ A.Kind = AccessKind::Index;
+ A.Indices = std::move(*Indices);
+ Out.push_back(std::move(A));
+ } else if (CurTok == '.') {
+ getNextToken();
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "Error: Expected field name after '.' at line %d\n", Lex.getLine());
+ return false;
+ }
+ DesignatorAccess A;
+ A.Kind = AccessKind::Field;
+ A.FieldName = Lex.IdentifierStr;
+ Out.push_back(std::move(A));
+ getNextToken();
+ } else if (CurTok == '^') {
+ getNextToken();
+ DesignatorAccess A;
+ A.Kind = AccessKind::Deref;
+ Out.push_back(std::move(A));
+ } else {
+ return true;
+ }
+ }
}
std::string Parser::ParseTypeName(bool AllowVoid) {
@@ -158,6 +197,27 @@ std::unique_ptr<ExprAST> Parser::ParseUnary() {
return std::make_unique<UnaryExprAST>(tok_not, std::move(Operand));
}
+ // ^<designator> — address-of; the operand must be an lvalue shape.
+ if (CurTok == '^') {
+ int Line = Lex.getLine();
+ getNextToken();
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "Error: '^' (address-of) requires a variable, array element or record field at line %d\n",
+ Line);
+ return nullptr;
+ }
+ std::string BaseName = Lex.IdentifierStr;
+ getNextToken();
+ if (CurTok == '(') {
+ fprintf(stderr, "Error: cannot take the address of a function result at line %d\n", Line);
+ return nullptr;
+ }
+ std::vector<DesignatorAccess> Accesses;
+ if (!ParseDesignatorSuffix(Accesses)) return nullptr;
+ auto Target = std::make_unique<DesignatorExprAST>(BaseName, std::move(Accesses), Line);
+ return std::make_unique<AddrOfExprAST>(std::move(Target));
+ }
+
if (CurTok == '-') {
int Line = Lex.getLine();
getNextToken();
@@ -214,6 +274,7 @@ void Parser::syncToStatementStart() {
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:
+ case tok_type: case tok_define: case tok_endtype:
return;
default:
getNextToken();
@@ -229,6 +290,7 @@ std::vector<std::unique_ptr<StmtAST>> Parser::ParseBlock(std::initializer_list<i
return false;
};
+ ++BlockDepth; // 1 = top level (Parse() calls ParseBlock({}))
std::vector<std::unique_ptr<StmtAST>> Body;
while (!AtTerminator() && CurTok != tok_eof) {
if (auto Stmt = ParseStatement()) {
@@ -237,6 +299,7 @@ std::vector<std::unique_ptr<StmtAST>> Parser::ParseBlock(std::initializer_list<i
syncToStatementStart();
}
}
+ --BlockDepth;
return Body;
}
@@ -470,33 +533,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
return ParseDeclare();
}
else if (CurTok == tok_identifier) {
- std::string Name = Lex.IdentifierStr;
- int Line = Lex.getLine();
- getNextToken();
-
- if (CurTok == '[') {
- getNextToken();
- auto Indices = ParseExprList(']', false);
- if (!Indices) return nullptr;
-
- if (CurTok != tok_assign) {
- fprintf(stderr, "Error: Expected '<-' after array access in assignment\n");
- return nullptr;
- }
- getNextToken();
- auto Expr = ParseExpression();
- if (!Expr) return nullptr;
- return std::make_unique<ArrayAssignStmtAST>(Name, std::move(*Indices), std::move(Expr), Line);
- }
-
- if (CurTok != tok_assign) {
- fprintf(stderr, "Error: Expected '<-' after identifier '%s' at line %d\n", Name.c_str(), Line);
- return nullptr;
- }
- getNextToken();
- auto Expr = ParseExpression();
- if (!Expr) return nullptr;
- return std::make_unique<AssignStmtAST>(Name, std::move(Expr));
+ return ParseAssignStmt();
}
else if (CurTok == tok_input) {
getNextToken();
@@ -550,8 +587,49 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
else if (CurTok == tok_closefile) {
return ParseCloseFile();
}
+ else if (CurTok == tok_type) {
+ return ParseTypeDecl();
+ }
+ else if (CurTok == tok_define) {
+ fprintf(stderr, "Error: DEFINE (SET literals) is not implemented at line %d\n", Lex.getLine());
+ getNextToken();
+ return nullptr;
+ }
+ else if (CurTok == tok_endtype) {
+ fprintf(stderr, "Error: ENDTYPE without matching TYPE at line %d\n", Lex.getLine());
+ getNextToken();
+ return nullptr;
+ }
fprintf(stderr, "Error: Unexpected token at line %d when expecting a statement\n", Lex.getLine());
getNextToken(); // guarantee progress so error recovery cannot loop forever
return nullptr;
}
+
+// <designator> <- <expr>
+// Bare-name and single-index targets degrade to their legacy nodes so the
+// existing assignment code paths stay untouched.
+std::unique_ptr<StmtAST> Parser::ParseAssignStmt() {
+ std::string Name = Lex.IdentifierStr;
+ int Line = Lex.getLine();
+ getNextToken();
+
+ std::vector<DesignatorAccess> Accesses;
+ if (!ParseDesignatorSuffix(Accesses)) return nullptr;
+
+ if (CurTok != tok_assign) {
+ fprintf(stderr, "Error: Expected '<-' after identifier '%s' at line %d\n", Name.c_str(), Line);
+ return nullptr;
+ }
+ getNextToken();
+ auto Expr = ParseExpression();
+ if (!Expr) return nullptr;
+
+ if (Accesses.empty())
+ return std::make_unique<AssignStmtAST>(Name, std::move(Expr));
+ if (Accesses.size() == 1 && Accesses[0].Kind == AccessKind::Index)
+ return std::make_unique<ArrayAssignStmtAST>(Name, std::move(Accesses[0].Indices), std::move(Expr), Line);
+
+ auto Target = std::make_unique<DesignatorExprAST>(Name, std::move(Accesses), Line);
+ return std::make_unique<DesignatorAssignStmtAST>(std::move(Target), std::move(Expr));
+}
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index 7663f38..a46cd0f 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -1,45 +1,112 @@
#include "cps/Parser.h"
#include "cps/FunctionAST.h"
#include <cstdio>
-#include <tuple>
using namespace cps;
-std::optional<std::vector<std::tuple<std::string, std::string, bool>>> Parser::ParsePrototypeArgs() {
- std::vector<std::tuple<std::string, std::string, bool>> Args;
- if (CurTok != '(') return Args;
+// Array-parameter bounds live in the signature, not the AST, so they must be
+// integer literals (an optional leading '-' is allowed).
+bool Parser::ParseSignedIntBound(int64_t &Out) {
+ bool Negative = false;
+ if (CurTok == '-') {
+ Negative = true;
+ getNextToken();
+ }
+ if (CurTok != tok_number_int) {
+ fprintf(stderr, "Error: Array parameter bounds must be integer literals at line %d\n",
+ Lex.getLine());
+ return false;
+ }
+ Out = Negative ? -Lex.NumVal : Lex.NumVal;
+ getNextToken();
+ return true;
+}
+
+// ( [BYREF|BYVAL] <name> : <type>
+// | <name> : ARRAY OF <type> -- rank 1, bounds from the argument
+// | <name> : ARRAY[<int>:<int>,...] OF <type> , ... )
+// The pass mode is sticky across parameters (guide §8.3: "the BYVAL or BYREF
+// keyword need not be repeated"); unannotated parameters default to BYVAL
+// (§8.1). BYREF is procedure-only (§8.3), gated by AllowByRef.
+std::optional<std::vector<ParamDecl>> Parser::ParsePrototypeArgs(bool AllowByRef) {
+ std::vector<ParamDecl> Params;
+ if (CurTok != '(') return Params;
getNextToken();
+ PassMode Current = PassMode::ByVal;
while (CurTok != ')') {
- bool IsRef = false;
if (CurTok == tok_byref) {
- IsRef = true;
+ if (!AllowByRef) {
+ fprintf(stderr, "Error: BYREF parameters are not allowed in a FUNCTION (procedures only) at line %d\n",
+ Lex.getLine());
+ return std::nullopt;
+ }
+ Current = PassMode::ByRef;
getNextToken();
} else if (CurTok == tok_byval) {
- IsRef = false;
+ Current = PassMode::ByVal;
getNextToken();
}
if (CurTok != tok_identifier) {
- fprintf(stderr, "Error: Expected argument name\n");
+ fprintf(stderr, "Error: Expected argument name at line %d\n", Lex.getLine());
return std::nullopt;
}
- std::string Name = Lex.IdentifierStr;
+ ParamDecl P;
+ P.Name = Lex.IdentifierStr;
+ P.Mode = Current;
getNextToken();
if (CurTok != tok_colon) {
- fprintf(stderr, "Error: Expected ':' after argument name\n");
+ fprintf(stderr, "Error: Expected ':' after argument name at line %d\n", Lex.getLine());
return std::nullopt;
}
getNextToken();
- std::string Type = ParseTypeName(false);
- if (Type.empty()) {
- fprintf(stderr, "Error: Expected argument type for '%s'\n", Name.c_str());
+ if (CurTok == tok_array) {
+ getNextToken();
+ P.IsArray = true;
+ if (CurTok == '[') {
+ getNextToken();
+ while (true) {
+ int64_t Lo = 0, Hi = 0;
+ if (!ParseSignedIntBound(Lo)) return std::nullopt;
+ if (CurTok != tok_colon) {
+ fprintf(stderr, "Error: Expected ':' between array parameter bounds at line %d\n",
+ Lex.getLine());
+ return std::nullopt;
+ }
+ getNextToken();
+ if (!ParseSignedIntBound(Hi)) return std::nullopt;
+ P.DeclaredBounds.emplace_back(Lo, Hi);
+ if (CurTok == ']') break;
+ if (CurTok != ',') {
+ fprintf(stderr, "Error: Expected ',' or ']' in array parameter bounds at line %d\n",
+ Lex.getLine());
+ return std::nullopt;
+ }
+ getNextToken();
+ }
+ getNextToken(); // eat ']'
+ P.Rank = static_cast<int>(P.DeclaredBounds.size());
+ } else {
+ P.Rank = 1; // ARRAY OF T: bounds travel with the argument
+ }
+ if (CurTok != tok_of) {
+ fprintf(stderr, "Error: Expected OF after ARRAY in parameter '%s' at line %d\n",
+ P.Name.c_str(), Lex.getLine());
+ return std::nullopt;
+ }
+ getNextToken();
+ }
+
+ P.TypeName = ParseTypeName(false);
+ if (P.TypeName.empty()) {
+ fprintf(stderr, "Error: Expected argument type for '%s'\n", P.Name.c_str());
return std::nullopt;
}
- Args.emplace_back(Name, Type, IsRef);
+ Params.push_back(std::move(P));
if (CurTok == ')') break;
if (CurTok != ',') {
@@ -49,7 +116,7 @@ std::optional<std::vector<std::tuple<std::string, std::string, bool>>> Parser::P
getNextToken();
}
getNextToken();
- return Args;
+ return Params;
}
std::unique_ptr<StmtAST> Parser::ParseFunction() {
@@ -62,7 +129,7 @@ std::unique_ptr<StmtAST> Parser::ParseFunction() {
std::string Name = Lex.IdentifierStr;
getNextToken();
- auto Args = ParsePrototypeArgs();
+ auto Args = ParsePrototypeArgs(/*AllowByRef=*/false);
if (!Args) return nullptr;
std::string RetType = "INTEGER";
@@ -98,7 +165,7 @@ std::unique_ptr<StmtAST> Parser::ParseProcedure() {
std::string Name = Lex.IdentifierStr;
getNextToken();
- auto Args = ParsePrototypeArgs();
+ auto Args = ParsePrototypeArgs(/*AllowByRef=*/true);
if (!Args) return nullptr;
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID");
diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc
new file mode 100644
index 0000000..50ee53f
--- /dev/null
+++ b/lib/Parser/ParserType.cc
@@ -0,0 +1,130 @@
+#include "cps/Parser.h"
+#include "cps/TypeAST.h"
+#include <cstdio>
+
+using namespace cps;
+
+// TYPE <id> = (value1, value2, ...) enumerated type
+// TYPE <id> = ^<type> pointer type
+// TYPE <id> record type
+// DECLARE <field> : <type>
+// ...
+// ENDTYPE
+// TYPE <id> = SET OF <type> is recognised and rejected (RANDOM precedent).
+// TYPE is a top-level-only statement; a nested declaration is still consumed
+// in full so its body cannot be replayed as statements.
+std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
+ bool AtTopLevel = BlockDepth <= 1;
+ int Line = Lex.getLine();
+ getNextToken(); // eat TYPE
+
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "Error: Expected type name after TYPE at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ std::string Name = Lex.IdentifierStr;
+ getNextToken();
+
+ std::unique_ptr<StmtAST> Decl;
+
+ if (CurTok == tok_eq) {
+ getNextToken();
+
+ if (CurTok == '(') {
+ getNextToken();
+ std::vector<std::string> Values;
+ while (true) {
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "Error: Expected an enum value name in TYPE %s at line %d\n",
+ Name.c_str(), Lex.getLine());
+ return nullptr;
+ }
+ Values.push_back(Lex.IdentifierStr);
+ getNextToken();
+ if (CurTok == ')') break;
+ if (CurTok != ',') {
+ fprintf(stderr, "Error: Expected ',' or ')' in TYPE %s at line %d\n",
+ Name.c_str(), Lex.getLine());
+ return nullptr;
+ }
+ getNextToken();
+ }
+ getNextToken(); // eat ')'
+ Decl = std::make_unique<EnumTypeDeclAST>(Name, std::move(Values));
+ } else if (CurTok == '^') {
+ getNextToken();
+ std::string Pointee = ParseTypeName(false);
+ if (Pointee.empty()) return nullptr;
+ Decl = std::make_unique<PointerTypeDeclAST>(Name, Pointee);
+ } else if (CurTok == tok_set) {
+ fprintf(stderr, "Error: SET OF types are not implemented at line %d\n", Lex.getLine());
+ getNextToken();
+ return nullptr;
+ } else {
+ fprintf(stderr, "Error: Expected '(' or '^' after '=' in TYPE %s at line %d\n",
+ Name.c_str(), Lex.getLine());
+ return nullptr;
+ }
+ } else {
+ // Record body: DECLARE-only lines, so nothing here can leak into the
+ // statement chain. One bad field yields one diagnostic; parsing then
+ // resumes at the next field or ENDTYPE.
+ std::vector<RecordFieldDecl> Fields;
+ bool HadFieldError = false;
+ while (CurTok == tok_declare) {
+ if (!ParseRecordField(Fields)) {
+ HadFieldError = true;
+ while (CurTok != tok_declare && CurTok != tok_endtype && CurTok != tok_eof)
+ getNextToken();
+ }
+ }
+ if (CurTok != tok_endtype) {
+ fprintf(stderr, "Error: Expected DECLARE or ENDTYPE in TYPE %s at line %d\n",
+ Name.c_str(), Lex.getLine());
+ return nullptr; // token left for the outer recovery
+ }
+ getNextToken(); // eat ENDTYPE
+ if (Fields.empty()) {
+ fprintf(stderr, "Error: Record TYPE %s has no fields at line %d\n", Name.c_str(), Line);
+ return nullptr;
+ }
+ if (HadFieldError) return nullptr;
+ Decl = std::make_unique<RecordTypeDeclAST>(Name, std::move(Fields));
+ }
+
+ if (!AtTopLevel) {
+ fprintf(stderr, "Error: TYPE declarations are only allowed at top level at line %d\n", Line);
+ return nullptr;
+ }
+ return Decl;
+}
+
+// DECLARE <field> : <type> — one record field. Array fields are deferred.
+bool Parser::ParseRecordField(std::vector<RecordFieldDecl> &Out) {
+ getNextToken(); // eat DECLARE
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "Error: Expected field name after DECLARE at line %d\n", Lex.getLine());
+ return false;
+ }
+ std::string FieldName = Lex.IdentifierStr;
+ getNextToken();
+
+ if (CurTok != tok_colon) {
+ fprintf(stderr, "Error: Expected ':' after field name %s at line %d\n",
+ FieldName.c_str(), Lex.getLine());
+ return false;
+ }
+ getNextToken();
+
+ if (CurTok == tok_array) {
+ fprintf(stderr, "Error: Array fields inside records are not implemented at line %d\n",
+ Lex.getLine());
+ return false;
+ }
+
+ std::string TypeName = ParseTypeName(false);
+ if (TypeName.empty()) return false;
+
+ Out.push_back({FieldName, TypeName});
+ return true;
+}