aboutsummaryrefslogtreecommitdiff
path: root/lib/Parser
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 13:45:03 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 13:45:03 +0000
commit05cdcb6a1cc3d4de3a0b3d12d4babcd6438b4754 (patch)
treed3e482d215d9d5feba8b40f9de15d6962f1e5bac /lib/Parser
parentf5a2541ed3faf9a942b867e2d54577352153afce (diff)
Add record-derived classes with explicit field visibility
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'lib/Parser')
-rw-r--r--lib/Parser/Parser.cc8
-rw-r--r--lib/Parser/ParserType.cc61
2 files changed, 52 insertions, 17 deletions
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index e68b405..098ef82 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -275,6 +275,7 @@ void Parser::syncToStatementStart() {
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:
+ case tok_class: case tok_endclass:
return;
default:
getNextToken();
@@ -601,7 +602,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
else if (CurTok == tok_closefile) {
return ParseCloseFile();
}
- else if (CurTok == tok_type) {
+ else if (CurTok == tok_type || CurTok == tok_class) {
return ParseTypeDecl();
}
else if (CurTok == tok_define) {
@@ -614,6 +615,11 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
getNextToken();
return nullptr;
}
+ else if (CurTok == tok_endclass) {
+ fprintf(stderr, "error: ENDCLASS without matching CLASS 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
diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc
index ae671d5..358e3e2 100644
--- a/lib/Parser/ParserType.cc
+++ b/lib/Parser/ParserType.cc
@@ -11,15 +11,19 @@ using namespace cps;
// ...
// 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
+// CLASS <id> PUBLIC/PRIVATE <field> : <type> ... ENDCLASS
+// TYPE and CLASS are top-level-only; 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;
+ bool IsClass = CurTok == tok_class;
+ const char *Keyword = IsClass ? "CLASS" : "TYPE";
int Line = Lex.getLine();
- getNextToken(); // eat TYPE
+ getNextToken(); // eat TYPE or CLASS
if (CurTok != tok_identifier) {
- fprintf(stderr, "error: Expected type name after TYPE at line %d\n", Lex.getLine());
+ fprintf(stderr, "error: Expected type name after %s at line %d\n", Keyword, Lex.getLine());
+ if (IsClass) ParseRecordBody("<unnamed>", Line, tok_endclass);
return nullptr;
}
std::string Name = Lex.IdentifierStr;
@@ -27,7 +31,7 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
std::unique_ptr<StmtAST> Decl;
- if (CurTok == tok_eq) {
+ if (!IsClass && CurTok == tok_eq) {
getNextToken();
if (CurTok == '(') {
@@ -66,13 +70,16 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
return nullptr;
}
} else {
- auto Fields = ParseRecordBody(Name, Line, tok_endtype);
+ auto Fields = ParseRecordBody(Name, Line, IsClass ? tok_endclass : tok_endtype);
if (!Fields) return nullptr;
- Decl = std::make_unique<RecordTypeDeclAST>(Name, std::move(*Fields));
+ if (IsClass)
+ Decl = std::make_unique<ClassTypeDeclAST>(Name, std::move(*Fields));
+ else
+ 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);
+ fprintf(stderr, "error: %s declarations are only allowed at top level at line %d\n", Keyword, Line);
return nullptr;
}
return Decl;
@@ -81,23 +88,43 @@ std::unique_ptr<StmtAST> Parser::ParseTypeDecl() {
// Shared record body and field parsing. Fields are data, never statements.
std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
const std::string &Name, int Line, int EndToken) {
+ bool IsClass = EndToken == tok_endclass;
+ const char *Keyword = IsClass ? "CLASS" : "TYPE";
+ const char *EndKeyword = IsClass ? "ENDCLASS" : "ENDTYPE";
+ auto AtField = [&] {
+ return IsClass ? (CurTok == tok_public || CurTok == tok_private)
+ : CurTok == tok_declare;
+ };
std::vector<RecordFieldDecl> Fields;
bool HadFieldError = false;
- while (CurTok == tok_declare) {
- if (!ParseRecordField(Fields, true)) {
- HadFieldError = true;
- while (CurTok != tok_declare && CurTok != EndToken && CurTok != tok_eof)
+ while (CurTok != EndToken && CurTok != tok_eof) {
+ if (AtField()) {
+ // Record fields are always public; class fields have no default.
+ bool IsPublic = !IsClass || CurTok == tok_public;
+ if (ParseRecordField(Fields, IsPublic)) continue;
+ } else {
+ fprintf(stderr, "error: Expected %s or %s in %s %s at line %d\n",
+ IsClass ? "PUBLIC or PRIVATE" : "DECLARE", EndKeyword,
+ Keyword, Name.c_str(), Lex.getLine());
+ }
+ HadFieldError = true;
+ // Consume invalid bodies rather than replaying their contents as
+ // statements. Nested type declarations are consumed in full too.
+ while (!AtField() && CurTok != EndToken && CurTok != tok_eof) {
+ if (CurTok == tok_class || CurTok == tok_type)
+ ParseTypeDecl();
+ else
getNextToken();
}
}
if (CurTok != EndToken) {
- fprintf(stderr, "error: Expected DECLARE or ENDTYPE in TYPE %s at line %d\n",
- Name.c_str(), Lex.getLine());
+ fprintf(stderr, "error: Expected %s in %s %s at line %d\n",
+ EndKeyword, Keyword, Name.c_str(), Lex.getLine());
return std::nullopt;
}
getNextToken(); // eat body terminator
- if (Fields.empty()) {
- fprintf(stderr, "error: Record TYPE %s has no fields at line %d\n", Name.c_str(), Line);
+ if (Fields.empty() && !HadFieldError) {
+ fprintf(stderr, "error: %s %s has no fields at line %d\n", Keyword, Name.c_str(), Line);
return std::nullopt;
}
if (HadFieldError) return std::nullopt;
@@ -106,9 +133,11 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody(
// <field introducer> <field> : <type>. Array fields are deferred.
bool Parser::ParseRecordField(std::vector<RecordFieldDecl> &Out, bool IsPublic) {
+ const char *Introducer = CurTok == tok_declare ? "DECLARE"
+ : CurTok == tok_public ? "PUBLIC" : "PRIVATE";
getNextToken(); // eat field introducer
if (CurTok != tok_identifier) {
- fprintf(stderr, "error: Expected field name after DECLARE at line %d\n", Lex.getLine());
+ fprintf(stderr, "error: Expected field name after %s at line %d\n", Introducer, Lex.getLine());
return false;
}
std::string FieldName = Lex.IdentifierStr;