From e2c65706e61be09ddb7eeb28cd1957acd4317bcf Mon Sep 17 00:00:00 2001 From: _ov4 Date: Fri, 16 Oct 2026 10:09:47 +0000 Subject: feat(constants): implement literal constants with lexical scope Co-Authored-By: Claude Fable 5.1 --- lib/CodeGen/ArrayHandler.cc | 4 +++ lib/CodeGen/CodeGen.cc | 3 ++- lib/CodeGen/ConstantGen.cc | 59 +++++++++++++++++++++++++++++++++++++++++++ lib/CodeGen/ControlFlowGen.cc | 17 +++++++------ lib/CodeGen/Designator.cc | 2 ++ lib/CodeGen/ExpressionGen.cc | 1 + lib/CodeGen/FunctionGen.cc | 9 ++++++- lib/CodeGen/StatementGen.cc | 13 +++++++++- lib/CodeGen/ValueTypeGen.cc | 2 ++ lib/Lexer/Lexer.cc | 1 + lib/Parser/Parser.cc | 3 ++- lib/Parser/ParserType.cc | 33 ++++++++++++++++++++++++ 12 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 lib/CodeGen/ConstantGen.cc (limited to 'lib') diff --git a/lib/CodeGen/ArrayHandler.cc b/lib/CodeGen/ArrayHandler.cc index 6a89781..40a330f 100644 --- a/lib/CodeGen/ArrayHandler.cc +++ b/lib/CodeGen/ArrayHandler.cc @@ -33,6 +33,8 @@ void ArrayHandler::setupExternalFunctions() { } const ArrayMetadata *ArrayHandler::getMetadata(const std::string &Name) const { + auto Symbol = Symbols->find(Name); + if (Symbol == Symbols->end() || !Symbol->second.IsArray) return nullptr; auto It = ArrayTable.find(Name); if (It == ArrayTable.end()) { return nullptr; @@ -93,6 +95,8 @@ void ArrayHandler::emitArrayDeclare(ArrayDeclareStmtAST *Stmt, CodeGen &CG) { return; } + if (!CG.prepareSymbolDeclaration(Name)) return; + std::vector Lows; std::vector Highs; diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 6c6f682..b793657 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -117,7 +117,7 @@ void CodeGen::registerSymbol(const std::string &Name, Value *Storage, const std::string &TypeName, bool IsArray) { - Symbols[Name] = {Storage, TypeName, IsArray}; + Symbols[Name] = {Storage, TypeName, IsArray, nullptr, ConstantScopes.back().ID}; } const SymbolInfo *CodeGen::getSymbolInfo(const std::string &Name) const { @@ -243,6 +243,7 @@ bool CodeGen::marshalCallArgs(const std::string &Callee, reportError("Unknown variable %s in BYREF call", Var->getName().c_str()); return false; } + if (!requireWritable(*Info, Var->getName())) return false; // BYREF aliases the storage, so the types must match exactly. if (!Types->sameType(Info->TypeName, TypeName)) { reportError("BYREF argument %s to %s must be of type %s (got %s)", diff --git a/lib/CodeGen/ConstantGen.cc b/lib/CodeGen/ConstantGen.cc new file mode 100644 index 0000000..e9f81ae --- /dev/null +++ b/lib/CodeGen/ConstantGen.cc @@ -0,0 +1,59 @@ +#include "cps/CodeGen.h" + +using namespace llvm; +using namespace cps; + +// Only bindings introduced by constants or hiding constants are restored. +// Ordinary variables keep the compiler's existing function-wide visibility. +void CodeGen::emitScopedBlock(const std::vector> &Body) { + ConstantScopes.push_back({NextScopeID++, {}}); + for (const auto &S : Body) emitStmt(S.get()); + for (const auto &[Name, Previous] : ConstantScopes.back().Saved) { + if (Previous) Symbols[Name] = *Previous; + else Symbols.erase(Name); + } + ConstantScopes.pop_back(); +} + +bool CodeGen::prepareSymbolDeclaration(const std::string &Name, bool IsConstant) { + auto &Scope = ConstantScopes.back(); + auto It = Symbols.find(Name); + if (It != Symbols.end()) { + if ((IsConstant || It->second.ConstantValue) && It->second.ScopeID == Scope.ID) { + reportError("Declaration of %s conflicts with an existing declaration in this scope", Name.c_str()); + return false; + } + if (IsConstant || It->second.ConstantValue) Scope.Saved.emplace(Name, It->second); + } else if (IsConstant) { + Scope.Saved.emplace(Name, std::nullopt); + } + return true; +} + +bool CodeGen::requireWritable(const SymbolInfo &Info, const std::string &Name) { + if (!Info.ConstantValue) return true; + reportError("Constant %s is not a writable location", Name.c_str()); + return false; +} + +void CodeGen::emitConstantStmt(ConstantStmtAST *Stmt) { + const std::string &Name = Stmt->getName(); + if (Types->lookupEnumConstant(Name) || Types->resolve(Name)) { + reportError("Constant %s conflicts with an enum value or type name", Name.c_str()); + return; + } + if (ConstantScopes.back().ID == 0 && + (FuncGen->getSignature(Name) || isBuiltinName(Name))) { + reportError("Constant %s conflicts with a function name", Name.c_str()); + return; + } + if (!prepareSymbolDeclaration(Name, true)) return; + const TypeInfo *Type = getExprTypeInfo(Stmt->getValue()); + Value *Val = emitExpr(Stmt->getValue()); + auto *Literal = dyn_cast_or_null(Val); + if (!Type || !Literal) { + reportError("CONSTANT initializer must be a literal"); + return; + } + Symbols[Name] = {nullptr, Type->Name, false, Literal, ConstantScopes.back().ID}; +} diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc index f9300dd..d34f640 100644 --- a/lib/CodeGen/ControlFlowGen.cc +++ b/lib/CodeGen/ControlFlowGen.cc @@ -12,14 +12,14 @@ void CodeGen::emitTryStmt(TryStmtAST *Stmt) { auto SavedArrays = Arrays->snapshotTable(); Exceptions->pushHandler(CatchBB); - for (const auto &S : Stmt->getBody()) emitStmt(S.get()); + emitScopedBlock(Stmt->getBody()); auto *Exception = Exceptions->popHandler(); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(AfterBB); Arrays->exchangeTable(SavedArrays); Builder->SetInsertPoint(CatchBB); Exceptions->beginCatch(Exception); - for (const auto &S : Stmt->getCatchBody()) emitStmt(S.get()); + emitScopedBlock(Stmt->getCatchBody()); Exceptions->endCatch(); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(AfterBB); Arrays->exchangeTable(std::move(SavedArrays)); @@ -41,12 +41,12 @@ void CodeGen::emitIfStmt(IfStmtAST *Stmt) { // inside the branch, so a declaration must not survive it. auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(ThenBB); - for (const auto &S : Stmt->getThenStmts()) emitStmt(S.get()); + emitScopedBlock(Stmt->getThenStmts()); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(MergeBB); Arrays->exchangeTable(SavedArrays); Builder->SetInsertPoint(ElseBB); - for (const auto &S : Stmt->getElseStmts()) emitStmt(S.get()); + emitScopedBlock(Stmt->getElseStmts()); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(MergeBB); Arrays->exchangeTable(std::move(SavedArrays)); @@ -71,7 +71,7 @@ void CodeGen::emitWhileStmt(WhileStmtAST *Stmt) { // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); - for (const auto &S : Stmt->getBody()) emitStmt(S.get()); + emitScopedBlock(Stmt->getBody()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->hasTerminator()) @@ -92,7 +92,7 @@ void CodeGen::emitRepeatStmt(RepeatStmtAST *Stmt) { // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); - for (const auto &S : Stmt->getBody()) emitStmt(S.get()); + emitScopedBlock(Stmt->getBody()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(CondBB); @@ -115,6 +115,7 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { reportError("Unknown variable in FOR loop %s", VarName.c_str()); return; } + if (!requireWritable(*Symbol, VarName)) return; if (Symbol->TypeName != "INTEGER") { reportError("FOR loop variable %s must be INTEGER", VarName.c_str()); return; @@ -160,7 +161,7 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); - for (const auto &S : Stmt->getBody()) emitStmt(S.get()); + emitScopedBlock(Stmt->getBody()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(IncBB); @@ -223,7 +224,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) { } } Builder->SetInsertPoint(Body); - for (const auto &S : Branch.Body) emitStmt(S.get()); + emitScopedBlock(Branch.Body); if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(After); Arrays->exchangeTable(SavedArrays); Builder->SetInsertPoint(Next); diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index 6c00524..afaf0a4 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -54,6 +54,7 @@ bool CodeGen::emitLValue(ExprAST *E, LValueInfo &Out) { } return false; } + if (!requireWritable(*Info, Var->getName())) return false; if (Info->IsArray) { reportError("Whole array %s is not an assignable location; index it", Var->getName().c_str()); return false; @@ -84,6 +85,7 @@ bool CodeGen::emitLValue(ExprAST *E, LValueInfo &Out) { return false; } + if (!requireWritable(*Info, D->getBaseName())) return false; const auto &Accesses = D->getAccesses(); Value *Addr = Info->Storage; std::string TypeName = Info->TypeName; diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index 9135d6c..f3dadb7 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -45,6 +45,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { reportError("Unknown variable name %s", Var->getName().c_str()); return nullptr; } + if (Info->ConstantValue) return Info->ConstantValue; if (Info->IsArray) { return Info->Storage; } diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index ca8fa45..957a1f5 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -178,7 +178,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { } Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, - const std::function &StmtEmitter) { + const std::function &StmtEmitter, unsigned ScopeID) { PrototypeAST *Proto = FuncAST->getProto(); Function *TheFunction = emitPrototype(Proto); @@ -194,8 +194,15 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, std::map OldSymbols = Symbols; Symbols.clear(); + for (const auto &[Name, Info] : OldSymbols) { + if (Info.ConstantValue && Info.ScopeID == 0) Symbols.emplace(Name, Info); + } createArgumentAllocas(TheFunction, Proto->getParams()); + for (const auto &P : Proto->getParams()) { + auto It = Symbols.find(P.Name); + if (It != Symbols.end()) It->second.ScopeID = ScopeID; + } for (const auto &Stmt : FuncAST->getBody()) { StmtEmitter(Stmt.get()); diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 8f8711b..8907cfa 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -17,9 +17,12 @@ void CodeGen::emitStmt(StmtAST *Stmt) { // to the emitting function), mirroring FunctionGen's Symbols handling. auto SavedArrays = Arrays->exchangeTable({}); + auto SavedScopes = std::move(ConstantScopes); + ConstantScopes = {{NextScopeID++, {}}}; FuncGen->emitFunctionDef(FuncDef, [this](StmtAST *S) { this->emitStmt(S); - }); + }, ConstantScopes.back().ID); + ConstantScopes = std::move(SavedScopes); Arrays->exchangeTable(std::move(SavedArrays)); if (SavedBlock) Builder->SetInsertPoint(SavedBlock); @@ -83,6 +86,11 @@ void CodeGen::emitStmt(StmtAST *Stmt) { return; } + if (auto *Const = dynamic_cast(Stmt)) { + emitConstantStmt(Const); + return; + } + if (auto *Decl = dynamic_cast(Stmt)) { emitDeclareStmt(Decl); return; @@ -98,6 +106,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } return; } + if (!requireWritable(*Info, Assign->getName())) return; if (Info->IsArray) { reportError("Cannot assign array %s without indices", Assign->getName().c_str()); return; @@ -116,6 +125,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { reportError("Unknown variable name %s", In->getName().c_str()); return; } + if (!requireWritable(*Info, In->getName())) return; if (Info->IsArray) { reportError("INPUT for entire array %s is not supported", In->getName().c_str()); return; @@ -204,6 +214,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { reportError("Unknown variable name %s", Read->getVarName().c_str()); return; } + if (!requireWritable(*Info, Read->getVarName())) return; if (Info->IsArray) { reportError("READFILE into entire array %s is not supported", Read->getVarName().c_str()); return; diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc index 70def81..1ee4a2d 100644 --- a/lib/CodeGen/ValueTypeGen.cc +++ b/lib/CodeGen/ValueTypeGen.cc @@ -256,6 +256,8 @@ void CodeGen::emitDeclareStmt(DeclareStmtAST *Stmt) { return; } + if (!prepareSymbolDeclaration(Stmt->getName())) return; + Function *TheFunction = Builder->GetInsertBlock()->getParent(); AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, Info->LLVMType, Stmt->getName()); diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc index fcc9a40..59247cf 100644 --- a/lib/Lexer/Lexer.cc +++ b/lib/Lexer/Lexer.cc @@ -70,6 +70,7 @@ int Lexer::readToken() { while (isalnum((LastChar = Input.get())) || LastChar == '_') IdentifierStr += static_cast(LastChar); + if (IdentifierStr == "CONSTANT") return tok_constant; if (IdentifierStr == "DECLARE") return tok_declare; if (IdentifierStr == "INTEGER") return tok_integer_kw; if (IdentifierStr == "BOOLEAN") return tok_boolean_kw; diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index 58422fc..d8fecdc 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -45,7 +45,7 @@ std::vector> Parser::Parse() { void Parser::syncToStatementStart() { while (CurTok != tok_eof) { switch (CurTok) { - case tok_declare: case tok_identifier: case tok_input: case tok_output: + case tok_constant: case tok_declare: case tok_identifier: case tok_input: case tok_output: case tok_case: case tok_endcase: case tok_otherwise: case tok_if: case tok_while: case tok_repeat: case tok_for: case tok_function: case tok_procedure: case tok_call: case tok_return: @@ -98,6 +98,7 @@ std::unique_ptr Parser::ParseStatementImpl() { getNextToken(); return nullptr; } + if (CurTok == tok_constant) return ParseConstant(); if (CurTok == tok_declare) { return ParseDeclare(); } diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc index 6c7703c..918b7f6 100644 --- a/lib/Parser/ParserType.cc +++ b/lib/Parser/ParserType.cc @@ -297,3 +297,36 @@ std::unique_ptr Parser::ParseDeclare() { return std::make_unique(Name, TypeStr); } } + +std::unique_ptr Parser::ParseConstant() { + int Line = Lex.getLine(); + getNextToken(); + if (CurTok != tok_identifier) { + fprintf(stderr, "error: Expected identifier after CONSTANT at line %d\n", Line); + return nullptr; + } + std::string Name = Lex.IdentifierStr; + getNextToken(); + if (CurTok != tok_eq) { + fprintf(stderr, "error: Expected '=' in CONSTANT declaration at line %d\n", Line); + return nullptr; + } + getNextToken(); + bool Negative = CurTok == '-'; + if (Negative) getNextToken(); + bool Numeric = CurTok == tok_number_int || CurTok == tok_number_real; + bool Literal = Numeric || CurTok == tok_true || CurTok == tok_false || + CurTok == tok_string_literal || CurTok == tok_char_literal; + if (!Literal || (Negative && !Numeric)) { + fprintf(stderr, "error: CONSTANT initializer must be a literal at line %d\n", Line); + return nullptr; + } + auto Value = ParsePrimary(); + if (!Value) return nullptr; + if (Negative) Value = std::make_unique('-', std::move(Value)); + if (GetTokPrecedence() >= 0) { + fprintf(stderr, "error: CONSTANT initializer must be a literal, not an expression, at line %d\n", Line); + return nullptr; + } + return std::make_unique(std::move(Name), std::move(Value)); +} -- cgit v1.3.1-4-g156e