From 05cdcb6a1cc3d4de3a0b3d12d4babcd6438b4754 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 13 Oct 2026 13:45:03 +0000 Subject: Add record-derived classes with explicit field visibility Co-Authored-By: Claude Fable 5.1 --- lib/Parser/Parser.cc | 8 ++++++- lib/Parser/ParserType.cc | 61 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 17 deletions(-) (limited to 'lib/Parser') 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 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 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 = SET OF is recognised and rejected (RANDOM precedent). -// TYPE is a top-level-only statement; a nested declaration is still consumed +// CLASS PUBLIC/PRIVATE : ... 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 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("", Line, tok_endclass); return nullptr; } std::string Name = Lex.IdentifierStr; @@ -27,7 +31,7 @@ std::unique_ptr Parser::ParseTypeDecl() { std::unique_ptr Decl; - if (CurTok == tok_eq) { + if (!IsClass && CurTok == tok_eq) { getNextToken(); if (CurTok == '(') { @@ -66,13 +70,16 @@ std::unique_ptr 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(Name, std::move(*Fields)); + if (IsClass) + Decl = std::make_unique(Name, std::move(*Fields)); + else + Decl = std::make_unique(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 Parser::ParseTypeDecl() { // Shared record body and field parsing. Fields are data, never statements. std::optional> 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 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> Parser::ParseRecordBody( // : . Array fields are deferred. bool Parser::ParseRecordField(std::vector &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; -- cgit v1.3.1-4-g156e