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 --- AGENTS/ARCH.md | 3 +- AGENTS/CONVENTIONS.md | 19 +++++--- AGENTS/EXTENDING.md | 3 +- AGENTS/PITFALLS.md | 8 ++-- AGENTS/SUBSYSTEMS.md | 2 +- CMakeLists.txt | 1 + doc/language/cpsc-language-overview.7.md | 3 +- doc/language/keyword/cpsc-keyword-constant.7.md | 38 ++++++++++++++++ include/cps/AST.h | 10 +++++ include/cps/CodeGen.h | 11 +++++ include/cps/FunctionGen.h | 2 +- include/cps/Lexer.h | 1 + include/cps/Parser.h | 1 + include/cps/TypeSystem.h | 2 + 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 ++++++++++++++ 26 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 doc/language/keyword/cpsc-keyword-constant.7.md create mode 100644 lib/CodeGen/ConstantGen.cc diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md index 4fab3ee..ffde252 100644 --- a/AGENTS/ARCH.md +++ b/AGENTS/ARCH.md @@ -264,9 +264,10 @@ do not modify the real release for local verification. | `lib/Parser/ParserExpression.cc`, `ParserControlFlow.cc` | Expressions/designators and control-flow statement parsing (including ordered CASE branches) | | `lib/Parser/ParserFunction.cc` | FUNCTION/PROCEDURE/CALL/RETURN + parameter-list parsing | | `lib/Parser/ParserFile.cc` | OPENFILE/READFILE/WRITEFILE/CLOSEFILE parsing | -| `lib/Parser/ParserType.cc` | Type names, variable/array declarations, TYPE/ENDTYPE and CLASS/ENDCLASS declarations, shared field parsing, SET/DEFINE reserved stubs | +| `lib/Parser/ParserType.cc` | Type names, variable/array/constant declarations, TYPE/ENDTYPE and CLASS/ENDCLASS declarations, shared field parsing, SET/DEFINE reserved stubs | | `lib/CodeGen/CodeGen.cc` | Initialization, symbols, call arguments and main() compilation | | `lib/CodeGen/ExpressionGen.cc`, `StatementGen.cc`, `ControlFlowGen.cc` | Expression/statement dispatch and control-flow emission | +| `lib/CodeGen/ConstantGen.cc` | Literal constants, declaration conflicts and lexical binding restoration | | `lib/CodeGen/ValueTypeGen.cc`, `OutputGen.cc`, `Builtins.h` | Expression typing, coercion/declarations, scalar output and the shared builtin table | | `lib/CodeGen/Designator.cc`, `TypeCheckGen.cc` | TYPE pre-pass and lvalue access; type admission and user-kind binary operators | | `lib/CodeGen/CTypeGen.cc` | CodeGen methods for native C scalar conversions, casts, arithmetic and input/output | diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index 5b16242..93c6f24 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -93,12 +93,19 @@ only `realloc`); `CodeGen` also keeps its own `MallocFunc` for the char→STRING and INPUT STRING buffers — declared in `SetupExternalFunctions`, never fetched with a bare `getFunction("malloc")`. -**One flat symbol namespace, one table.** `Symbols` (name → `SymbolInfo{Storage, -TypeName, IsArray}`) in CodeGen is the only symbol table; register through -`registerSymbol()` (or directly, as `FunctionGen::createArgumentAllocas` does for -parameters) and read storage through `getNamedValue()`/`getSymbolInfo()`. There is no -scope stack. `FunctionGen` saves/clears/restores the table around a function body, but -`ArrayHandler::ArrayTable` is *not* scoped (see `PITFALLS.md`). +**One symbol namespace, one table.** `Symbols` (name → `SymbolInfo{Storage, +TypeName, IsArray, ConstantValue, ScopeID}`) in CodeGen is the only symbol table. +A non-null ConstantValue denotes a read-only CONSTANT with no Storage. Read it +through `emitExpr`; all writable/address-taking paths must reject it through +`requireWritable`. Register variables through `registerSymbol()` (parameters are +also bound directly by FunctionGen). `prepareSymbolDeclaration` diagnoses +same-scope conflicts involving constants before declaration emission. Unique +scope IDs distinguish sibling blocks; `emitScopedBlock` restores only bindings +introduced by constants or hiding constants, preserving other variables' existing +function-wide visibility. FunctionGen saves/restores Symbols and inherits only +previously visible top-level constants (ScopeID 0). CodeGen isolates constant +scope bookkeeping and array metadata around each function body. Arrays also +restore metadata at control-flow block exits. **Dispatch is by `dynamic_cast` chain, not virtual methods.** `CodeGen::emitStmt` and `emitExpr` walk a linear if-chain of casts. **A statement type missing from the chain is diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md index d03c088..5e104c8 100644 --- a/AGENTS/EXTENDING.md +++ b/AGENTS/EXTENDING.md @@ -9,7 +9,8 @@ unique_ptr children, move ctor, raw-ptr getters) → `ParseXxx` in the matching `Parser*.cc` file + branch in `ParseStatementImpl` (block statements parse their body with `ParseBlock({terminator tokens})`; control-flow parsers live in `ParserControlFlow.cc`; comma-separated expression lists use `ParseExprList(close, allowEmpty)`; add the new statement's leading keyword to -`syncToStatementStart`) → `dynamic_cast` branch in `CodeGen::emitStmt`. Guard every +`syncToStatementStart`) → `dynamic_cast` branch in `CodeGen::emitStmt`. Emit statement bodies through `emitScopedBlock` +so constants cannot escape their lexical block. Guard every fall-through branch in control-flow codegen with `if (!Builder->GetInsertBlock()->hasTerminator())` — a RETURN in the body may have already terminated the block. diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index 93564f1..58934ce 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -35,9 +35,11 @@ test programs. - **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check it at any new call site) and internally defaults to INTEGER for arithmetic binaries and unknown-function calls; OUTPUT reports an error for a nullptr result. +- **CONSTANT** accepts only literals (including negative numeric literals), never expressions or other names. Constants have lexical block scope and may shadow outer names; bindings hiding constants are restored on block exit too. Only previously declared top-level constants are inherited by functions. Constants have no writable storage: assignment, INPUT, READFILE targets, FOR variables, BYREF and address-of are rejected. - **CASE OF** reads its selector once and tests expression labels in source order, with inclusive TO ranges and an optional final OTHERWISE. Label headers occupy their own source line; bodies may begin after the colon or on subsequent lines. Comparisons reuse ordinary operator rules; records and whole arrays are unsupported. -- The lexer emits no newline tokens; the grammar is purely keyword-delimited. Bare - `RETURN` is recognized before block terminators, including CATCH and ENDTRY. +- The lexer emits no newline tokens; most grammar is keyword-delimited, while CASE + uses token line information to recognize labels. Bare `RETURN` is recognized + before block terminators and the next CASE label. - `verifyModule` runs at the end of `compile()`; invalid modules make the driver exit non-zero without writing IR. - The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca. The @@ -101,7 +103,7 @@ test programs. ## Not implemented (9618 features) -CONSTANT, SET types (`SET`/`DEFINE` are reserved with dedicated errors), +SET types (`SET`/`DEFINE` are reserved with dedicated errors), random files (OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — TYPE is done, so these are unblocked now; the RANDOM keyword is reserved and gives a dedicated parse error), DATE, INPUT into array elements, string escape sequences, diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index f693b1c..c553f8e 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -34,7 +34,7 @@ map (`FunctionGen::getSignature`); `CodeGen::marshalCallArgs` dispatches call ar on those *declared* `ParamSig`s, and `getExprTypeInfo` reads return types from the same map. Top-level prototypes are pre-registered at the start of `compile()` (after the TYPE pre-pass), so calls may precede definitions; calling a name with no signature is a -compile error (no extern is fabricated). Body emission is inverted: CodeGen injects a +compile error (no extern is fabricated). Functions inherit previously declared top-level CONSTANT values; local constants use the same Symbols table with lexical binding restoration. Body emission is inverted: CodeGen injects a `StmtEmitter` callback, and must restore its own insert point (and the exchanged ArrayTable) afterwards. diff --git a/CMakeLists.txt b/CMakeLists.txt index cd63165..965bb0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ add_library(CPSCodeGen STATIC lib/CodeGen/CodeGen.cc lib/CodeGen/ExpressionGen.cc lib/CodeGen/StatementGen.cc + lib/CodeGen/ConstantGen.cc lib/CodeGen/ControlFlowGen.cc lib/CodeGen/ValueTypeGen.cc lib/CodeGen/OutputGen.cc diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index b32b7bd..328fd70 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -22,6 +22,7 @@ The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions # KEYWORDS +- [cpsc-keyword-constant(7)](keyword/cpsc-keyword-constant.7.md): declare a named literal with block scope. - [cpsc-keyword-declare(7)](keyword/cpsc-keyword-declare.7.md): declare variables and arrays. - [cpsc-keyword-input(7)](keyword/cpsc-keyword-input.7.md): read a value from standard input. - [cpsc-keyword-output(7)](keyword/cpsc-keyword-output.7.md): print values to standard output. @@ -66,7 +67,7 @@ Closing and auxiliary keywords are documented with their main constructs: THEN/E # UNSUPPORTED FEATURES -CONSTANT, DATE, SET types and DEFINE, random-file operations (RANDOM/SEEK/GETRECORD/PUTRECORD), INT, RAND, INPUT into array elements or fields, string escapes, whole-array assignment or return, array fields, and class methods/constructors/destructors/inheritance (NEW/INHERITS/SUPER). +DATE, SET types and DEFINE, random-file operations (RANDOM/SEEK/GETRECORD/PUTRECORD), INT, RAND, INPUT into array elements or fields, string escapes, whole-array assignment or return, array fields, and class methods/constructors/destructors/inheritance (NEW/INHERITS/SUPER). The individual pages note relevant differences from the Cambridge 2026 pseudocode guide. This manual describes the compiler's current support. diff --git a/doc/language/keyword/cpsc-keyword-constant.7.md b/doc/language/keyword/cpsc-keyword-constant.7.md new file mode 100644 index 0000000..abc39cf --- /dev/null +++ b/doc/language/keyword/cpsc-keyword-constant.7.md @@ -0,0 +1,38 @@ +% CPSC-KEYWORD-CONSTANT(7) + +# NAME + +cpsc-keyword-constant - declare a named literal value + +# SYNOPSIS + +```text +CONSTANT identifier = literal +``` + +# DESCRIPTION + +Declare a read-only value whose type is inferred from its literal: INTEGER, REAL, BOOLEAN, CHAR, or STRING. A negative integer or real literal is allowed. As required by the Cambridge guide, variables, other constants, expressions, casts, and function calls cannot initialize a CONSTANT. + +A constant is visible from its declaration to the end of its enclosing block. A top-level constant is also available in functions and procedures defined after its declaration. Functions do not inherit constants from another function or from an enclosing statement block. + +Inner blocks may shadow outer names; leaving a block restores bindings hidden by constants or hiding constants. Each IF branch, loop body, TRY body, CATCH body, and CASE branch has its own constant scope. Ordinary variables otherwise retain their existing function scope. A declaration in the same scope cannot reuse a constant name, and a constant cannot reuse a variable or parameter name in that scope. Type names and enumerator names remain reserved; top-level constants cannot reuse function names. + +Constants may be read in expressions, used as CASE labels or array bounds, and passed BYVAL. Assignment, INPUT, READFILE targets, FOR control variables, BYREF arguments, and taking the address of a constant are rejected. + +# EXAMPLE + +```text +CONSTANT HourlyRate = 6.50 +CONSTANT DefaultText = "N/A" +OUTPUT HourlyRate, DefaultText +IF TRUE THEN + CONSTANT HourlyRate = 8.00 + OUTPUT HourlyRate +ENDIF +OUTPUT HourlyRate +``` + +# SEE ALSO + +[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-declare(7)](cpsc-keyword-declare.7.md), [cpsc-keyword-case(7)](cpsc-keyword-case.7.md). diff --git a/include/cps/AST.h b/include/cps/AST.h index 87c9341..1afc359 100644 --- a/include/cps/AST.h +++ b/include/cps/AST.h @@ -105,6 +105,16 @@ public: class ThrowStmtAST : public StmtAST {}; +class ConstantStmtAST : public StmtAST { + std::string Name; + std::unique_ptr Value; +public: + ConstantStmtAST(std::string Name, std::unique_ptr Value) + : Name(std::move(Name)), Value(std::move(Value)) {} + const std::string &getName() const { return Name; } + ExprAST *getValue() const { return Value.get(); } +}; + class DeclareStmtAST : public StmtAST { std::string Name; std::string Type; diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 4259d63..2ba5e79 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -13,6 +13,7 @@ #include #include +#include #include namespace cps { @@ -27,6 +28,16 @@ class CodeGen { std::unique_ptr TheModule; std::unique_ptr> Builder; std::map Symbols; + struct ConstantScope { + unsigned ID; + std::map> Saved; + }; + std::vector ConstantScopes = {{0, {}}}; + unsigned NextScopeID = 1; + void emitScopedBlock(const std::vector> &Body); + bool prepareSymbolDeclaration(const std::string &Name, bool IsConstant = false); + bool requireWritable(const SymbolInfo &Info, const std::string &Name); + void emitConstantStmt(ConstantStmtAST *Stmt); std::unique_ptr Types; std::unique_ptr Arrays; diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index a7cead5..80c91ea 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -81,7 +81,7 @@ public: llvm::Function *emitPrototype(PrototypeAST *Proto); - llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function &StmtEmitter); + llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function &StmtEmitter, unsigned ScopeID); llvm::Value *emitCallExpr(CallExprAST *Call, const std::vector &Args); diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h index 7f3d5b5..efedec8 100644 --- a/include/cps/Lexer.h +++ b/include/cps/Lexer.h @@ -14,6 +14,7 @@ enum Token { tok_input = -4, tok_output = -5, + tok_constant = -43, tok_case = -40, tok_endcase = -41, tok_otherwise = -42, diff --git a/include/cps/Parser.h b/include/cps/Parser.h index 921480d..0079c8b 100644 --- a/include/cps/Parser.h +++ b/include/cps/Parser.h @@ -57,6 +57,7 @@ class Parser { std::unique_ptr ParseForStmt(); std::unique_ptr ParseDeclare(); + std::unique_ptr ParseConstant(); std::unique_ptr ParseAssignStmt(); bool ParseDesignatorSuffix(std::vector &Out); diff --git a/include/cps/TypeSystem.h b/include/cps/TypeSystem.h index a07c75f..b615ed8 100644 --- a/include/cps/TypeSystem.h +++ b/include/cps/TypeSystem.h @@ -106,6 +106,8 @@ struct SymbolInfo { llvm::Value *Storage = nullptr; std::string TypeName; bool IsArray = false; + llvm::Constant *ConstantValue = nullptr; // Non-null for CONSTANT; no storage. + unsigned ScopeID = 0; }; class TypeSystem { 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