diff options
| author | _ov4 <ov4@ov4.org> | 2026-07-26 17:21:14 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-07-26 17:21:14 +0000 |
| commit | 3366a2e0fae05d9e792169e74471c7282bf18681 (patch) | |
| tree | 0cc4cacdac7d32ee7671d6799c52f74953c2007e | |
| parent | 0fef2986daa2945c9e3d050c0f750d56792b30bd (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>
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | include/cps/FunctionAST.h | 28 | ||||
| -rw-r--r-- | include/cps/FunctionGen.h | 2 | ||||
| -rw-r--r-- | include/cps/Lexer.h | 7 | ||||
| -rw-r--r-- | include/cps/Parser.h | 13 | ||||
| -rw-r--r-- | include/cps/TypeAST.h | 120 | ||||
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 18 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 41 | ||||
| -rw-r--r-- | lib/Lexer/Lexer.cc | 5 | ||||
| -rw-r--r-- | lib/Parser/Parser.cc | 146 | ||||
| -rw-r--r-- | lib/Parser/ParserFunction.cc | 101 | ||||
| -rw-r--r-- | lib/Parser/ParserType.cc | 130 |
12 files changed, 532 insertions, 80 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a3c29d..c195635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(CPSParser lib/Parser/Parser.cc lib/Parser/ParserFunction.cc lib/Parser/ParserFile.cc + lib/Parser/ParserType.cc ) add_library(CPSCodeGen diff --git a/include/cps/FunctionAST.h b/include/cps/FunctionAST.h index 613079c..f663867 100644 --- a/include/cps/FunctionAST.h +++ b/include/cps/FunctionAST.h @@ -1,27 +1,43 @@ #pragma once #include "cps/AST.h" +#include <cstdint> #include <vector> #include <string> #include <memory> -#include <tuple> +#include <utility> namespace cps { +enum class PassMode : uint8_t { ByVal, ByRef }; + +// One declared FUNCTION/PROCEDURE parameter. For array parameters TypeName +// is the ELEMENT type; DeclaredBounds holds literal per-dimension bounds for +// the bounded form (empty for `ARRAY OF T`, which is rank 1 with the bounds +// travelling alongside the argument). +struct ParamDecl { + std::string Name; + std::string TypeName; + PassMode Mode = PassMode::ByVal; + bool IsArray = false; + int Rank = 0; // 0 = scalar + std::vector<std::pair<int64_t, int64_t>> DeclaredBounds; +}; + class PrototypeAST { std::string Name; - std::vector<std::tuple<std::string, std::string, bool>> Args; + std::vector<ParamDecl> Params; std::string ReturnType; bool IsExternal; public: - PrototypeAST(const std::string &Name, - std::vector<std::tuple<std::string, std::string, bool>> Args, + PrototypeAST(const std::string &Name, + std::vector<ParamDecl> Params, const std::string &ReturnType, bool IsExternal = false) - : Name(Name), Args(std::move(Args)), ReturnType(ReturnType), IsExternal(IsExternal) {} + : Name(Name), Params(std::move(Params)), ReturnType(ReturnType), IsExternal(IsExternal) {} const std::string &getName() const { return Name; } - const std::vector<std::tuple<std::string, std::string, bool>> &getArgs() const { return Args; } + const std::vector<ParamDecl> &getParams() const { return Params; } const std::string &getReturnType() const { return ReturnType; } bool isExternal() const { return IsExternal; } }; diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index cce17c3..9bb6eef 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -30,7 +30,7 @@ class FunctionGen { std::map<std::string, FuncSig> Signatures; - void createArgumentAllocas(llvm::Function *F, const std::vector<std::tuple<std::string, std::string, bool>> &Args); + void createArgumentAllocas(llvm::Function *F, const std::vector<ParamDecl> &Params); public: FunctionGen(llvm::LLVMContext &C, diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h index 81fbd99..2af0260 100644 --- a/include/cps/Lexer.h +++ b/include/cps/Lexer.h @@ -78,7 +78,12 @@ enum Token { tok_read = -504, tok_write = -505, tok_append = -506, - tok_random = -507 + tok_random = -507, + + tok_type = -600, + tok_endtype = -601, + tok_set = -602, // reserved: SET types are recognised but not implemented + tok_define = -603 // reserved: DEFINE (set literals) likewise }; class Lexer { diff --git a/include/cps/Parser.h b/include/cps/Parser.h index 0ecb7f9..49f418c 100644 --- a/include/cps/Parser.h +++ b/include/cps/Parser.h @@ -1,12 +1,14 @@ #pragma once #include "cps/Lexer.h" #include "cps/AST.h" +#include "cps/TypeAST.h" +#include "cps/FunctionAST.h" +#include <cstdint> #include <vector> #include <initializer_list> #include <memory> #include <map> #include <optional> -#include <tuple> #include <string> namespace cps { @@ -15,6 +17,7 @@ class Parser { Lexer &Lex; int CurTok; unsigned NumErrors = 0; + unsigned BlockDepth = 0; // ParseBlock nesting; 1 = top level std::map<int, int> BinopPrecedence; @@ -42,12 +45,18 @@ class Parser { std::unique_ptr<StmtAST> ParseForStmt(); std::unique_ptr<StmtAST> ParseDeclare(); + std::unique_ptr<StmtAST> ParseAssignStmt(); + bool ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out); + + std::unique_ptr<StmtAST> ParseTypeDecl(); + bool ParseRecordField(std::vector<RecordFieldDecl> &Out); std::unique_ptr<StmtAST> ParseFunction(); std::unique_ptr<StmtAST> ParseProcedure(); std::unique_ptr<StmtAST> ParseCallStmt(); std::unique_ptr<StmtAST> ParseReturnStmt(); - std::optional<std::vector<std::tuple<std::string, std::string, bool>>> ParsePrototypeArgs(); + std::optional<std::vector<ParamDecl>> ParsePrototypeArgs(bool AllowByRef); + bool ParseSignedIntBound(int64_t &Out); std::unique_ptr<StmtAST> ParseOpenFile(); std::unique_ptr<StmtAST> ParseReadFile(); diff --git a/include/cps/TypeAST.h b/include/cps/TypeAST.h new file mode 100644 index 0000000..d349859 --- /dev/null +++ b/include/cps/TypeAST.h @@ -0,0 +1,120 @@ +#pragma once +#include "cps/AST.h" +#include <cstdint> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +namespace cps { + +// ---- User-defined type declarations (CIE 9618 §4.1) ---- + +// One field of a record TYPE body. Plain data, not a statement node, so the +// emitStmt chain can never execute a field by accident. +struct RecordFieldDecl { + std::string Name; + std::string TypeName; +}; + +// TYPE <name> = (value1, value2, ...) +class EnumTypeDeclAST : public StmtAST { + std::string Name; + std::vector<std::string> ValueNames; + +public: + EnumTypeDeclAST(const std::string &Name, std::vector<std::string> ValueNames) + : Name(Name), ValueNames(std::move(ValueNames)) {} + + const std::string &getName() const { return Name; } + const std::vector<std::string> &getValueNames() const { return ValueNames; } +}; + +// TYPE <name> = ^<pointee type> +class PointerTypeDeclAST : public StmtAST { + std::string Name; + std::string PointeeTypeName; + +public: + PointerTypeDeclAST(const std::string &Name, const std::string &PointeeTypeName) + : Name(Name), PointeeTypeName(PointeeTypeName) {} + + const std::string &getName() const { return Name; } + const std::string &getPointeeTypeName() const { return PointeeTypeName; } +}; + +// TYPE <name> +// DECLARE <field> : <type> +// ... +// ENDTYPE +class RecordTypeDeclAST : public StmtAST { + std::string Name; + std::vector<RecordFieldDecl> Fields; + +public: + RecordTypeDeclAST(const std::string &Name, std::vector<RecordFieldDecl> Fields) + : Name(Name), Fields(std::move(Fields)) {} + + const std::string &getName() const { return Name; } + const std::vector<RecordFieldDecl> &getFields() const { return Fields; } +}; + +// ---- Designators: chained accesses usable as lvalue or rvalue (§4.2) ---- + +enum class AccessKind : uint8_t { Index, Field, Deref }; + +// One postfix accessor: [indices], .Field, or ^ (dereference). +struct DesignatorAccess { + AccessKind Kind; + std::vector<std::unique_ptr<ExprAST>> Indices; // Index only + std::string FieldName; // Field only +}; + +// <base name> followed by one or more accessors, e.g. Form[i].YearGroup, p^. +// Bare names and single-index accesses keep their legacy nodes (parser +// degrade rule); a designator node always carries at least one accessor +// beyond those shapes, except as the operand of AddrOfExprAST. +class DesignatorExprAST : public ExprAST { + std::string BaseName; + std::vector<DesignatorAccess> Accesses; + int Line; + +public: + DesignatorExprAST(const std::string &BaseName, + std::vector<DesignatorAccess> Accesses, + int Line) + : BaseName(BaseName), Accesses(std::move(Accesses)), Line(Line) {} + + const std::string &getBaseName() const { return BaseName; } + const std::vector<DesignatorAccess> &getAccesses() const { return Accesses; } + int getLine() const { return Line; } +}; + +// ^<designator> — address-of. The operand is a designator by construction, +// so an lvalue is guaranteed at parse time. +class AddrOfExprAST : public ExprAST { + std::unique_ptr<DesignatorExprAST> Target; + +public: + explicit AddrOfExprAST(std::unique_ptr<DesignatorExprAST> Target) + : Target(std::move(Target)) {} + + DesignatorExprAST *getTarget() const { return Target.get(); } +}; + +// <designator> <- <expr> for targets beyond a bare name or single indexing +// (those degrade to AssignStmtAST / ArrayAssignStmtAST in the parser). +class DesignatorAssignStmtAST : public StmtAST { + std::unique_ptr<DesignatorExprAST> Target; + std::unique_ptr<ExprAST> Expr; + +public: + DesignatorAssignStmtAST(std::unique_ptr<DesignatorExprAST> Target, + std::unique_ptr<ExprAST> Expr) + : Target(std::move(Target)), Expr(std::move(Expr)) {} + + DesignatorExprAST *getTarget() const { return Target.get(); } + ExprAST *getExpr() const { return Expr.get(); } +}; + +} // namespace cps 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; +} |
