aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-16 10:04:27 +0000
committer_ov4 <ov4@ov4.org>2026-10-16 10:04:27 +0000
commit45c9d37eb27d204a6202b33cb37e890af0171357 (patch)
treedba23170b16053c5e1ec4bf78efc2a533cb94a4e
parent522742e9df7fc5ac596ce4aa53d41b31e4f6a030 (diff)
feat(control-flow): implement ordered CASE OF matching
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH.md4
-rw-r--r--AGENTS/EXTENDING.md4
-rw-r--r--AGENTS/PITFALLS.md3
-rw-r--r--doc/language/cpsc-language-overview.7.md5
-rw-r--r--doc/language/keyword/cpsc-keyword-case.7.md43
-rw-r--r--include/cps/AST.h16
-rw-r--r--include/cps/CodeGen.h9
-rw-r--r--include/cps/Lexer.h25
-rw-r--r--include/cps/Parser.h10
-rw-r--r--lib/CodeGen/CTypeGen.cc20
-rw-r--r--lib/CodeGen/ControlFlowGen.cc58
-rw-r--r--lib/CodeGen/ExpressionGen.cc59
-rw-r--r--lib/CodeGen/StatementGen.cc4
-rw-r--r--lib/CodeGen/TypeCheckGen.cc15
-rw-r--r--lib/Lexer/Lexer.cc46
-rw-r--r--lib/Parser/Parser.cc18
-rw-r--r--lib/Parser/ParserControlFlow.cc86
-rw-r--r--lib/Parser/ParserExpression.cc4
-rw-r--r--lib/Parser/ParserFunction.cc3
19 files changed, 356 insertions, 76 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 663ed40..4fab3ee 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -259,9 +259,9 @@ do not modify the real release for local verification.
| Path | Contents |
|---|---|
| `include/cps/` | All headers, included as `"cps/Foo.h"` |
-| `lib/Lexer/Lexer.cc` | Entire lexer: one function, `Lexer::gettok()` |
+| `lib/Lexer/Lexer.cc` | Token scanning and payload-preserving lookahead; `gettok()` consumes and `peekToken`/`peekLine` inspect buffered tokens |
| `lib/Parser/Parser.cc` | Entry point, statement dispatch, assignment parsing and error recovery |
-| `lib/Parser/ParserExpression.cc`, `ParserControlFlow.cc` | Expressions/designators and control-flow statement parsing |
+| `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 |
diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md
index 1a683ec..d03c088 100644
--- a/AGENTS/EXTENDING.md
+++ b/AGENTS/EXTENDING.md
@@ -24,8 +24,8 @@ branches do. Builtins are plain identifiers — nothing is needed in lexer/parse
**New binary operator:** token (if multi-char) → one line in `BinopPrecedence`
(Parser ctor; levels: OR=3, AND=5, comparisons=10, `+ - &`=20, `* / DIV MOD`=40;
-precedence must be > 0) → branch in `ArithmeticHandler::emitBinaryOp`, which dispatches
-on raw LLVM operand types. Exceptions in `CodeGen::emitExpr`: `&` coerces operands
+precedence must be > 0) → branch in the shared `CodeGen::emitBinaryOp` value dispatcher or its `ArithmeticHandler::emitBinaryOp` leaf, which dispatches
+on raw LLVM operand types. Exceptions in `CodeGen::emitBinaryOp`: `&` coerces operands
to STRING then calls `StringHandler::emitConcat`; STRING comparisons require two
STRING operands by pseudocode type then call `StringHandler::emitCompare`.
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index c740d2f..93564f1 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -35,6 +35,7 @@ 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.
+- **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.
- `verifyModule` runs at the end of `compile()`; invalid modules make the driver
@@ -100,7 +101,7 @@ test programs.
## Not implemented (9618 features)
-CASE OF, CONSTANT, SET types (`SET`/`DEFINE` are reserved with dedicated errors),
+CONSTANT, 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/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md
index 530eb45..b32b7bd 100644
--- a/doc/language/cpsc-language-overview.7.md
+++ b/doc/language/cpsc-language-overview.7.md
@@ -25,6 +25,7 @@ The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions
- [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.
+- [cpsc-keyword-case(7)](keyword/cpsc-keyword-case.7.md): match values or ranges in order.
- [cpsc-keyword-if(7)](keyword/cpsc-keyword-if.7.md): choose between branches.
- [cpsc-keyword-try(7)](keyword/cpsc-keyword-try.7.md): catch or throw exceptions.
- [cpsc-keyword-while(7)](keyword/cpsc-keyword-while.7.md): repeat while a condition is true.
@@ -46,7 +47,7 @@ The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions
- [cpsc-keyword-div(7)](keyword/cpsc-keyword-div.7.md): perform integer division.
- [cpsc-keyword-mod(7)](keyword/cpsc-keyword-mod.7.md): find the integer division remainder.
-Closing and auxiliary keywords are documented with their main constructs: THEN/ELSE/ENDIF with IF; CATCH/ENDTRY/THROW with TRY; DO/ENDWHILE with WHILE; UNTIL with REPEAT; TO/STEP/NEXT with FOR; RETURN/RETURNS/BYVAL/BYREF and function/procedure closing keywords with FUNCTION/PROCEDURE; READ/WRITE/APPEND with OPENFILE; ARRAY/OF and INTEGER/REAL/BOOLEAN/CHAR/STRING with DECLARE; ENDTYPE with TYPE; PUBLIC/PRIVATE/ENDCLASS with CLASS. TRUE/FALSE are BOOLEAN literals.
+Closing and auxiliary keywords are documented with their main constructs: THEN/ELSE/ENDIF with IF; OF/OTHERWISE/ENDCASE with CASE; CATCH/ENDTRY/THROW with TRY; DO/ENDWHILE with WHILE; UNTIL with REPEAT; TO/STEP/NEXT with FOR; RETURN/RETURNS/BYVAL/BYREF and function/procedure closing keywords with FUNCTION/PROCEDURE; READ/WRITE/APPEND with OPENFILE; ARRAY/OF and INTEGER/REAL/BOOLEAN/CHAR/STRING with DECLARE; ENDTYPE with TYPE; PUBLIC/PRIVATE/ENDCLASS with CLASS. TRUE/FALSE are BOOLEAN literals.
# BUILT-IN FUNCTIONS
@@ -65,7 +66,7 @@ Closing and auxiliary keywords are documented with their main constructs: THEN/E
# UNSUPPORTED FEATURES
-CASE OF, 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).
+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).
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-case.7.md b/doc/language/keyword/cpsc-keyword-case.7.md
new file mode 100644
index 0000000..d391d6c
--- /dev/null
+++ b/doc/language/keyword/cpsc-keyword-case.7.md
@@ -0,0 +1,43 @@
+% CPSC-KEYWORD-CASE(7)
+
+# NAME
+
+cpsc-keyword-case - match values or ranges in source order
+
+# SYNOPSIS
+
+```text
+CASE OF identifier
+ value : statements
+ lower TO upper : statements
+ OTHERWISE : statements
+ENDCASE
+```
+
+# DESCRIPTION
+
+Read the selector once, then test labels in source order. Execute only the first matching branch. If nothing matches, execute the optional final OTHERWISE branch, or continue after ENDCASE. There is no fall-through.
+
+Each label header must start on a new line and end with a colon on that line. Its body may start after the colon or on following lines and may contain multiple statements or nested blocks. Indentation is for readability.
+
+Labels and range endpoints may be expressions, including function calls. A single value uses ordinary equality; a TO range includes both endpoints and uses ordinary ordering. Evaluate the lower endpoint first and evaluate the upper endpoint only if the lower comparison succeeds. Do not evaluate later labels after a match. Unlike CONSTANT initializers, CASE labels are not restricted to literals.
+
+STRING comparisons use contents. Enumerations and explicit pointer types follow their ordinary comparison rules; pointers support equality only. Records and whole arrays cannot be compared. The compiler emits conditional branches and disables jump tables for functions containing CASE.
+
+# EXAMPLE
+
+```text
+DECLARE Move : CHAR
+INPUT Move
+CASE OF Move
+ 'W' : OUTPUT "up"
+ 'A' :
+ OUTPUT "left"
+ OUTPUT "turn"
+ OTHERWISE : OUTPUT "other"
+ENDCASE
+```
+
+# SEE ALSO
+
+[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-if(7)](cpsc-keyword-if.7.md).
diff --git a/include/cps/AST.h b/include/cps/AST.h
index e3c6bc5..87c9341 100644
--- a/include/cps/AST.h
+++ b/include/cps/AST.h
@@ -168,6 +168,22 @@ public:
const std::vector<std::unique_ptr<ExprAST>> &getExprs() const { return Exprs; }
};
+struct CaseBranch {
+ std::unique_ptr<ExprAST> Lower, Upper; // Null Lower denotes OTHERWISE.
+ std::vector<std::unique_ptr<StmtAST>> Body;
+ int Line;
+};
+
+class CaseStmtAST : public StmtAST {
+ std::string Selector;
+ std::vector<CaseBranch> Branches;
+public:
+ CaseStmtAST(std::string Selector, std::vector<CaseBranch> Branches)
+ : Selector(std::move(Selector)), Branches(std::move(Branches)) {}
+ const std::string &getSelector() const { return Selector; }
+ const std::vector<CaseBranch> &getBranches() const { return Branches; }
+};
+
class IfStmtAST : public StmtAST {
std::unique_ptr<ExprAST> Cond;
std::vector<std::unique_ptr<StmtAST>> ThenStmts;
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 72b96c6..4259d63 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -70,13 +70,17 @@ class CodeGen {
llvm::Value *convertNumericValue(llvm::Value *Val, const TypeInfo *Source,
const TypeInfo *Target);
llvm::Value *emitCast(CastExprAST *Cast);
- llvm::Value *emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const TypeInfo *Right);
+ llvm::Value *emitBinaryOp(int Op, llvm::Value *L, const TypeInfo *LT,
+ llvm::Value *R, const TypeInfo *RT, int Line);
+ llvm::Value *emitCBinaryOp(int Op, llvm::Value *L, const TypeInfo *Left,
+ llvm::Value *R, const TypeInfo *Right, int Line);
void emitCOutput(llvm::Value *Val, const TypeInfo *Info, bool AppendNewline);
void emitCInput(const SymbolInfo *Symbol, const TypeInfo *Info);
// TYPE-system registration (Designator.cc) and operators (TypeCheckGen.cc).
void runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements);
- llvm::Value *emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, const TypeInfo *RT);
+ llvm::Value *emitUserKindBinaryOp(int Op, llvm::Value *L, const TypeInfo *LT,
+ llvm::Value *R, const TypeInfo *RT, int Line);
// An addressable location and the pseudocode type stored there.
struct LValueInfo {
@@ -95,6 +99,7 @@ class CodeGen {
void emitDeclareStmt(DeclareStmtAST *Stmt);
void emitOutputValue(llvm::Value *Val, const TypeInfo *TypeInfo, bool AppendNewline = true);
+ void emitCaseStmt(CaseStmtAST *Stmt);
void emitIfStmt(IfStmtAST *Stmt);
void emitTryStmt(TryStmtAST *Stmt);
void emitWhileStmt(WhileStmtAST *Stmt);
diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h
index 2f6f231..7f3d5b5 100644
--- a/include/cps/Lexer.h
+++ b/include/cps/Lexer.h
@@ -2,6 +2,7 @@
#include <istream>
#include <string>
#include <cstdint>
+#include <deque>
namespace cps {
@@ -13,6 +14,10 @@ enum Token {
tok_input = -4,
tok_output = -5,
+ tok_case = -40,
+ tok_endcase = -41,
+ tok_otherwise = -42,
+
tok_if = -6,
tok_then = -7,
tok_else = -8,
@@ -105,18 +110,32 @@ class Lexer {
std::istream &Input;
int LastChar = ' ';
int CurrentLine = 1;
+ int TokenLine = 1;
bool HadError = false;
+ struct TokenData {
+ int Kind, Line;
+ std::string Identifier, String;
+ int64_t Integer;
+ double Real;
+ char Character;
+ };
+ std::deque<TokenData> Lookahead;
+ int readToken();
+ TokenData captureToken(int Kind) const;
+ void restoreToken(const TokenData &Token);
public:
explicit Lexer(std::istream &Input) : Input(Input) {}
std::string IdentifierStr;
std::string StringVal;
- int64_t NumVal;
- double RealVal;
+ int64_t NumVal = 0;
+ double RealVal = 0;
char CharVal = '\0';
int gettok();
- int getLine() const { return CurrentLine; }
+ int peekToken(unsigned Offset = 0);
+ int peekLine(unsigned Offset = 0);
+ int getLine() const { return TokenLine; }
bool hadError() const { return HadError; }
};
diff --git a/include/cps/Parser.h b/include/cps/Parser.h
index 5713587..921480d 100644
--- a/include/cps/Parser.h
+++ b/include/cps/Parser.h
@@ -16,7 +16,10 @@ namespace cps {
class Parser {
Lexer &Lex;
- int CurTok;
+ int CurTok = tok_eof;
+ bool StartsLine = true;
+ bool ParsingCaseLabel = false;
+ std::vector<unsigned> CaseBodyDepths;
unsigned NumErrors = 0;
unsigned BlockDepth = 0; // ParseBlock nesting; 1 = top level
@@ -41,7 +44,10 @@ class Parser {
std::unique_ptr<StmtAST> ParseStatement();
std::unique_ptr<StmtAST> ParseStatementImpl();
- std::vector<std::unique_ptr<StmtAST>> ParseBlock(std::initializer_list<int> Terminators);
+ std::vector<std::unique_ptr<StmtAST>> ParseBlock(std::initializer_list<int> Terminators, bool CaseBody = false);
+ bool isCaseLabel();
+ bool atCaseBoundary();
+ std::unique_ptr<StmtAST> ParseCaseStmt();
std::optional<std::vector<std::unique_ptr<ExprAST>>> ParseExprList(char CloseDelim, bool AllowEmpty);
void syncToStatementStart();
std::unique_ptr<StmtAST> ParseIfStmt();
diff --git a/lib/CodeGen/CTypeGen.cc b/lib/CodeGen/CTypeGen.cc
index 22ecb9f..a20306e 100644
--- a/lib/CodeGen/CTypeGen.cc
+++ b/lib/CodeGen/CTypeGen.cc
@@ -78,10 +78,10 @@ Value *CodeGen::emitCast(CastExprAST *Cast) {
return nullptr;
}
-Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const TypeInfo *Right) {
- int Op = Bin->getOp();
+Value *CodeGen::emitCBinaryOp(int Op, Value *L, const TypeInfo *Left,
+ Value *R, const TypeInfo *Right, int Line) {
if (!Left || !Right || !Left->isC() || !Right->isC()) {
- reportError("C and pseudocode operands require an explicit cast (line %d)", Bin->getLine());
+ reportError("C and pseudocode operands require an explicit cast (line %d)", Line);
return nullptr;
}
if (Left->isPointer() || Right->isPointer()) {
@@ -90,9 +90,6 @@ Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const Ty
reportError("C pointers support only = and <> between the same C pointer type");
return nullptr;
}
- Value *L = emitExpr(Bin->getLHS());
- Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
return Op == tok_eq ? Builder->CreateICmpEQ(L, R, "c_ptr_eq")
: Builder->CreateICmpNE(L, R, "c_ptr_ne");
}
@@ -102,12 +99,9 @@ Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const Ty
return nullptr;
}
if (Op == '&' || ((Op == tok_div || Op == tok_mod) && !Common->isIntegral())) {
- reportError("Operator is not supported for these C operand types (line %d)", Bin->getLine());
+ reportError("Operator is not supported for these C operand types (line %d)", Line);
return nullptr;
}
- Value *L = emitExpr(Bin->getLHS());
- Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
if (Op == tok_and || Op == tok_or) {
L = convertNumericValue(L, Left, resolveType("BOOLEAN"));
R = convertNumericValue(R, Right, resolveType("BOOLEAN"));
@@ -119,7 +113,7 @@ Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const Ty
if (Op == '/') {
L = convertNumericValue(L, Left, resolveType("REAL"));
R = convertNumericValue(R, Right, resolveType("REAL"));
- RuntimeChecker->emitDivZeroCheck(R, Bin->getLine());
+ RuntimeChecker->emitDivZeroCheck(R, Line);
return Builder->CreateFDiv(L, R, "c_real_div");
}
L = convertNumericValue(L, Left, Common);
@@ -142,7 +136,7 @@ Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const Ty
case '-': return Builder->CreateSub(L, R, "c_sub");
case '*': return Builder->CreateMul(L, R, "c_mul");
case tok_div: case tok_mod:
- RuntimeChecker->emitDivZeroCheck(R, Bin->getLine());
+ RuntimeChecker->emitDivZeroCheck(R, Line);
if (Op == tok_div)
return Common->CSigned ? Builder->CreateSDiv(L, R, "c_div")
: Builder->CreateUDiv(L, R, "c_div");
@@ -160,7 +154,7 @@ Value *CodeGen::emitCBinaryOp(BinaryExprAST *Bin, const TypeInfo *Left, const Ty
: Builder->CreateICmpUGE(L, R, "c_ge");
}
}
- reportError("Unsupported operator for C operands (line %d)", Bin->getLine());
+ reportError("Unsupported operator for C operands (line %d)", Line);
return nullptr;
}
diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc
index ebd5d3c..f9300dd 100644
--- a/lib/CodeGen/ControlFlowGen.cc
+++ b/lib/CodeGen/ControlFlowGen.cc
@@ -173,3 +173,61 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) {
Builder->SetInsertPoint(AfterBB);
}
+
+void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
+ VariableExprAST Selector(Stmt->getSelector());
+ if (isWholeArrayVar(&Selector)) {
+ reportError("CASE selector cannot be a whole array");
+ return;
+ }
+ const TypeInfo *SelectorType = getExprTypeInfo(&Selector);
+ Value *Selected = emitExpr(&Selector);
+ if (!Selected || !SelectorType) return;
+
+ auto *F = Builder->GetInsertBlock()->getParent();
+ F->addFnAttr("no-jump-tables", "true");
+ auto *After = BasicBlock::Create(*TheContext, "case_cont", F);
+ auto SavedArrays = Arrays->snapshotTable();
+ for (const auto &Branch : Stmt->getBranches()) {
+ auto *Body = BasicBlock::Create(*TheContext, "case_body", F);
+ auto *Next = BasicBlock::Create(*TheContext, "case_next", F);
+ auto Compare = [&](int Op, ExprAST *Label) -> Value * {
+ if (isWholeArrayVar(Label)) {
+ reportError("CASE label cannot be a whole array");
+ return nullptr;
+ }
+ const TypeInfo *LabelType = getExprTypeInfo(Label);
+ if (!LabelType) {
+ reportError("Cannot determine CASE label type at line %d", Branch.Line);
+ return nullptr;
+ }
+ Value *LabelValue = emitExpr(Label);
+ return emitBinaryOp(Op, Selected, SelectorType, LabelValue, LabelType, Branch.Line);
+ };
+ if (!Branch.Lower) {
+ Builder->CreateBr(Body);
+ } else {
+ Value *Match = Compare(Branch.Upper ? tok_ge : tok_eq, Branch.Lower.get());
+ if (!Match) {
+ // Finish the CFG even after a diagnostic; the driver discards it.
+ Builder->CreateBr(Next);
+ } else if (Branch.Upper) {
+ auto *Upper = BasicBlock::Create(*TheContext, "case_upper", F);
+ Builder->CreateCondBr(Match, Upper, Next);
+ Builder->SetInsertPoint(Upper);
+ Match = Compare(tok_le, Branch.Upper.get());
+ if (Match) Builder->CreateCondBr(Match, Body, Next);
+ else Builder->CreateBr(Next);
+ } else {
+ Builder->CreateCondBr(Match, Body, Next);
+ }
+ }
+ Builder->SetInsertPoint(Body);
+ for (const auto &S : Branch.Body) emitStmt(S.get());
+ if (!Builder->GetInsertBlock()->hasTerminator()) Builder->CreateBr(After);
+ Arrays->exchangeTable(SavedArrays);
+ Builder->SetInsertPoint(Next);
+ }
+ Builder->CreateBr(After);
+ Builder->SetInsertPoint(After);
+}
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 0d69408..9135d6c 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -117,38 +117,9 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
// '&' path: their admissible operations are decided by name here.
const TypeInfo *LT = getExprTypeInfo(Bin->getLHS());
const TypeInfo *RT = getExprTypeInfo(Bin->getRHS());
- if ((LT && LT->isC()) || (RT && RT->isC())) {
- return emitCBinaryOp(Bin, LT, RT);
- }
- if ((LT && LT->isUserKind()) || (RT && RT->isUserKind())) {
- return emitUserKindBinaryOp(Bin, LT, RT);
- }
-
Value *L = emitExpr(Bin->getLHS());
Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
- if (Bin->getOp() == '&') {
- L = coerceValueToType(L, resolveType("STRING"));
- R = coerceValueToType(R, resolveType("STRING"));
- if (!L || !R) return nullptr;
- return StrHandler->emitConcat(L, R);
- }
- // STRING and explicit pointers share LLVM's ptr representation;
- // string comparisons must dispatch on the pseudocode type.
- switch (Bin->getOp()) {
- case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge:
- if ((LT && LT->isString()) || (RT && RT->isString())) {
- if (!(LT && LT->isString() && RT && RT->isString())) {
- reportError("STRING comparison requires two STRING operands (line %d)", Bin->getLine());
- return nullptr;
- }
- return StrHandler->emitCompare(Bin->getOp(), L, R);
- }
- break;
- default:
- break;
- }
- return ArithHandler->emitBinaryOp(Bin->getOp(), L, R, Bin->getLine());
+ return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine());
}
if (auto *Call = dynamic_cast<CallExprAST*>(Expr)) {
@@ -271,3 +242,31 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
return nullptr;
}
+
+// Ordinary binary expressions and CASE use the same typed operations.
+Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT,
+ Value *R, const TypeInfo *RT, int Line) {
+ if (!L || !R) return nullptr;
+ if ((LT && LT->isC()) || (RT && RT->isC()))
+ return emitCBinaryOp(Op, L, LT, R, RT, Line);
+ if ((LT && LT->isUserKind()) || (RT && RT->isUserKind()))
+ return emitUserKindBinaryOp(Op, L, LT, R, RT, Line);
+ if (Op == '&') {
+ L = coerceValueToType(L, resolveType("STRING"));
+ R = coerceValueToType(R, resolveType("STRING"));
+ return L && R ? StrHandler->emitConcat(L, R) : nullptr;
+ }
+ switch (Op) {
+ case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge:
+ if ((LT && LT->isString()) || (RT && RT->isString())) {
+ if (!(LT && LT->isString() && RT && RT->isString())) {
+ reportError("STRING comparison requires two STRING operands (line %d)", Line);
+ return nullptr;
+ }
+ return StrHandler->emitCompare(Op, L, R);
+ }
+ break;
+ default: break;
+ }
+ return ArithHandler->emitBinaryOp(Op, L, R, Line);
+}
diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc
index f10d78b..8f8711b 100644
--- a/lib/CodeGen/StatementGen.cc
+++ b/lib/CodeGen/StatementGen.cc
@@ -248,6 +248,10 @@ void CodeGen::emitStmt(StmtAST *Stmt) {
return;
}
+ if (auto *Case = dynamic_cast<CaseStmtAST*>(Stmt)) {
+ emitCaseStmt(Case);
+ return;
+ }
if (auto *IfStmt = dynamic_cast<IfStmtAST*>(Stmt)) {
emitIfStmt(IfStmt);
return;
diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc
index a86fdb1..654afc8 100644
--- a/lib/CodeGen/TypeCheckGen.cc
+++ b/lib/CodeGen/TypeCheckGen.cc
@@ -121,8 +121,8 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) {
// Binary operators with at least one Enum/Record/Pointer operand. Decides
// admissibility by type name and emits directly; ArithmeticHandler stays
// ignorant of user kinds.
-Value *CodeGen::emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, const TypeInfo *RT) {
- int Op = Bin->getOp();
+Value *CodeGen::emitUserKindBinaryOp(int Op, Value *L, const TypeInfo *LT,
+ Value *R, const TypeInfo *RT, int Line) {
const char *LName = LT ? LT->Name.c_str() : "?";
const char *RName = RT ? RT->Name.c_str() : "?";
@@ -135,9 +135,6 @@ Value *CodeGen::emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, con
if ((LT && LT->isPointer()) || (RT && RT->isPointer())) {
bool SameName = LT && RT && LT->isPointer() && RT->isPointer() && LT->Name == RT->Name;
if (SameName && (Op == tok_eq || Op == tok_ne)) {
- Value *L = emitExpr(Bin->getLHS());
- Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
return Op == tok_eq ? Builder->CreateICmpEQ(L, R, "ptr_eq")
: Builder->CreateICmpNE(L, R, "ptr_ne");
}
@@ -152,9 +149,6 @@ Value *CodeGen::emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, con
switch (Op) {
case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge: {
if (!BothSameEnum) break;
- Value *L = emitExpr(Bin->getLHS());
- Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
switch (Op) {
case tok_eq: return Builder->CreateICmpEQ(L, R, "enum_eq");
case tok_ne: return Builder->CreateICmpNE(L, R, "enum_ne");
@@ -170,13 +164,10 @@ Value *CodeGen::emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, con
// The one legal arithmetic shape (guide §4.2): enum on the left,
// INTEGER on the right, result checked against the enum's range.
if (!(LT && LT->isEnum() && RT && RT->Kind == TypeKind::Integer)) break;
- Value *L = emitExpr(Bin->getLHS());
- Value *R = emitExpr(Bin->getRHS());
- if (!L || !R) return nullptr;
Value *Res = Op == '+' ? Builder->CreateAdd(L, R, "enum_step")
: Builder->CreateSub(L, R, "enum_step");
RuntimeChecker->emitEnumRangeCheck(Res, LT->asEnum()->ValueNames.size(),
- LT->Name, Bin->getLine());
+ LT->Name, Line);
return Res;
}
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index 31bafe1..fcc9a40 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -7,12 +7,14 @@
using namespace cps;
-int Lexer::gettok() {
+int Lexer::readToken() {
while (isspace(LastChar)) {
if (LastChar == '\n') CurrentLine++;
LastChar = Input.get();
}
+ TokenLine = CurrentLine;
+
if (LastChar == '"') {
StringVal.clear();
LastChar = Input.get();
@@ -86,6 +88,10 @@ int Lexer::gettok() {
if (IdentifierStr == "INPUT") return tok_input;
if (IdentifierStr == "OUTPUT") return tok_output;
+ if (IdentifierStr == "CASE") return tok_case;
+ if (IdentifierStr == "ENDCASE") return tok_endcase;
+ if (IdentifierStr == "OTHERWISE") return tok_otherwise;
+
if (IdentifierStr == "IF") return tok_if;
if (IdentifierStr == "THEN") return tok_then;
if (IdentifierStr == "ELSE") return tok_else;
@@ -225,7 +231,7 @@ int Lexer::gettok() {
} while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');
if (LastChar != EOF)
- return gettok();
+ return readToken();
} else {
LastChar = NextChar;
return '/';
@@ -239,3 +245,39 @@ int Lexer::gettok() {
LastChar = Input.get();
return ThisChar;
}
+
+Lexer::TokenData Lexer::captureToken(int Kind) const {
+ return {Kind, TokenLine, IdentifierStr, StringVal, NumVal, RealVal, CharVal};
+}
+
+void Lexer::restoreToken(const TokenData &Token) {
+ TokenLine = Token.Line;
+ IdentifierStr = Token.Identifier;
+ StringVal = Token.String;
+ NumVal = Token.Integer;
+ RealVal = Token.Real;
+ CharVal = Token.Character;
+}
+
+int Lexer::gettok() {
+ if (Lookahead.empty()) return readToken();
+ auto Token = std::move(Lookahead.front());
+ Lookahead.pop_front();
+ restoreToken(Token);
+ return Token.Kind;
+}
+
+int Lexer::peekToken(unsigned Offset) {
+ auto Current = captureToken(0);
+ while (Lookahead.size() <= Offset) {
+ int Kind = readToken();
+ Lookahead.push_back(captureToken(Kind));
+ }
+ restoreToken(Current);
+ return Lookahead[Offset].Kind;
+}
+
+int Lexer::peekLine(unsigned Offset) {
+ peekToken(Offset);
+ return Lookahead[Offset].Line;
+}
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index 3e8a096..58422fc 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -29,7 +29,10 @@ Parser::Parser(Lexer &L) : Lex(L) {
}
int Parser::getNextToken() {
- return CurTok = Lex.gettok();
+ int PreviousLine = CurTok == tok_eof ? 0 : Lex.getLine();
+ CurTok = Lex.gettok();
+ StartsLine = Lex.getLine() != PreviousLine;
+ return CurTok;
}
std::vector<std::unique_ptr<StmtAST>> Parser::Parse() {
@@ -43,6 +46,7 @@ void Parser::syncToStatementStart() {
while (CurTok != tok_eof) {
switch (CurTok) {
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:
case tok_else: case tok_endif: case tok_endwhile: case tok_until:
@@ -58,7 +62,7 @@ void Parser::syncToStatementStart() {
}
}
-std::vector<std::unique_ptr<StmtAST>> Parser::ParseBlock(std::initializer_list<int> Terminators) {
+std::vector<std::unique_ptr<StmtAST>> Parser::ParseBlock(std::initializer_list<int> Terminators, bool CaseBody) {
auto AtTerminator = [&] {
for (int T : Terminators) {
if (CurTok == T) return true;
@@ -67,14 +71,16 @@ std::vector<std::unique_ptr<StmtAST>> Parser::ParseBlock(std::initializer_list<i
};
++BlockDepth; // 1 = top level (Parse() calls ParseBlock({}))
+ if (CaseBody) CaseBodyDepths.push_back(BlockDepth);
std::vector<std::unique_ptr<StmtAST>> Body;
- while (!AtTerminator() && CurTok != tok_eof) {
+ while (!AtTerminator() && CurTok != tok_eof && !(CaseBody && isCaseLabel())) {
if (auto Stmt = ParseStatement()) {
Body.push_back(std::move(Stmt));
} else {
syncToStatementStart();
}
}
+ if (CaseBody) CaseBodyDepths.pop_back();
--BlockDepth;
return Body;
}
@@ -86,6 +92,12 @@ std::unique_ptr<StmtAST> Parser::ParseStatement() {
}
std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
+ if (CurTok == tok_case) return ParseCaseStmt();
+ if (CurTok == tok_endcase || CurTok == tok_otherwise) {
+ fprintf(stderr, "error: Unexpected CASE terminator at line %d\n", Lex.getLine());
+ getNextToken();
+ return nullptr;
+ }
if (CurTok == tok_declare) {
return ParseDeclare();
}
diff --git a/lib/Parser/ParserControlFlow.cc b/lib/Parser/ParserControlFlow.cc
index cb32466..d01e561 100644
--- a/lib/Parser/ParserControlFlow.cc
+++ b/lib/Parser/ParserControlFlow.cc
@@ -144,3 +144,89 @@ std::unique_ptr<StmtAST> Parser::ParseForStmt() {
return std::make_unique<ForStmtAST>(VarName, std::move(Start), std::move(End), std::move(Step), std::move(Body));
}
+
+// A label begins a source line and ends in ':' on that line. Lookahead
+// preserves token payloads; assignments and statement keywords are not labels.
+bool Parser::isCaseLabel() {
+ if (!StartsLine) return false;
+ int Line = Lex.getLine();
+ int Depth = 0;
+ for (unsigned I = 0;; ++I) {
+ int T = I == 0 ? CurTok : Lex.peekToken(I - 1);
+ int TokenLine = I == 0 ? Line : Lex.peekLine(I - 1);
+ if (TokenLine != Line) return false;
+ if (T == tok_colon) return Depth == 0;
+ switch (T) {
+ case '(': case '[': ++Depth; break;
+ case ')': case ']': if (--Depth < 0) return false; break;
+ case tok_identifier: case tok_number_int: case tok_number_real:
+ case tok_string_literal: case tok_char_literal: case tok_true: case tok_false:
+ case tok_integer_kw: case tok_real_kw: case tok_boolean_kw:
+ case tok_string_kw: case tok_char_kw: case tok_c_int: case tok_c_char:
+ case tok_c_string: case tok_c_double:
+ case tok_to: case tok_otherwise: case tok_and: case tok_or: case tok_not:
+ case tok_eq: case tok_ne: case tok_le: case tok_ge: case tok_div: case tok_mod:
+ case '+': case '-': case '*': case '/': case '&': case '^':
+ case '<': case '>': case '.': case ',': break;
+ default: return false;
+ }
+ }
+}
+
+bool Parser::atCaseBoundary() {
+ return !ParsingCaseLabel && !CaseBodyDepths.empty() &&
+ CaseBodyDepths.back() == BlockDepth && isCaseLabel();
+}
+
+std::unique_ptr<StmtAST> Parser::ParseCaseStmt() {
+ getNextToken();
+ if (CurTok != tok_of) {
+ fprintf(stderr, "error: Expected OF after CASE at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ getNextToken();
+ if (CurTok != tok_identifier) {
+ fprintf(stderr, "error: Expected identifier after CASE OF at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ std::string Selector = Lex.IdentifierStr;
+ getNextToken();
+ std::vector<CaseBranch> Branches;
+ bool SeenOtherwise = false;
+ while (CurTok != tok_endcase && CurTok != tok_eof) {
+ if (SeenOtherwise || !isCaseLabel()) {
+ fprintf(stderr, "error: Expected CASE label on a new line (OTHERWISE must be last) at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ CaseBranch Branch;
+ Branch.Line = Lex.getLine();
+ if (CurTok == tok_otherwise) {
+ SeenOtherwise = true;
+ getNextToken();
+ } else {
+ bool Saved = ParsingCaseLabel;
+ ParsingCaseLabel = true;
+ Branch.Lower = ParseExpression();
+ if (Branch.Lower && CurTok == tok_to) {
+ getNextToken();
+ Branch.Upper = ParseExpression();
+ if (!Branch.Upper) Branch.Lower.reset();
+ }
+ ParsingCaseLabel = Saved;
+ if (!Branch.Lower) return nullptr;
+ }
+ if (CurTok != tok_colon) {
+ fprintf(stderr, "error: Expected ':' after CASE label at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ getNextToken();
+ Branch.Body = ParseBlock({tok_endcase}, true);
+ Branches.push_back(std::move(Branch));
+ }
+ if (CurTok != tok_endcase || Branches.empty()) {
+ fprintf(stderr, "error: Expected nonempty CASE and ENDCASE at line %d\n", Lex.getLine());
+ return nullptr;
+ }
+ getNextToken();
+ return std::make_unique<CaseStmtAST>(std::move(Selector), std::move(Branches));
+}
diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc
index 3e2665f..d32c407 100644
--- a/lib/Parser/ParserExpression.cc
+++ b/lib/Parser/ParserExpression.cc
@@ -5,6 +5,7 @@
using namespace cps;
int Parser::GetTokPrecedence() {
+ if (atCaseBoundary()) return -1;
if (BinopPrecedence.find(CurTok) == BinopPrecedence.end()) return -1;
int TokPrec = BinopPrecedence[CurTok];
if (TokPrec <= 0) return -1;
@@ -28,7 +29,7 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
int Line = Lex.getLine();
getNextToken();
- if (CurTok == '(') {
+ if (CurTok == '(' && !atCaseBoundary()) {
getNextToken();
auto Args = ParseExprList(')', true);
if (!Args) return nullptr;
@@ -54,6 +55,7 @@ std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
// Postfix accessor chain after an identifier: '[' exprs ']' | '.' IDENT | '^'
bool Parser::ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out) {
while (true) {
+ if (atCaseBoundary()) return true;
if (CurTok == '[') {
getNextToken();
auto Indices = ParseExprList(']', false);
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index 25375f2..c47485e 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -222,7 +222,8 @@ std::unique_ptr<StmtAST> Parser::ParseReturnStmt() {
getNextToken();
std::unique_ptr<ExprAST> Expr = nullptr;
// A bare RETURN is recognized right before any block terminator.
- if (CurTok != tok_endif && CurTok != tok_else && CurTok != tok_endfunction &&
+ if (!atCaseBoundary() && CurTok != tok_endcase && CurTok != tok_otherwise &&
+ CurTok != tok_endif && CurTok != tok_else && CurTok != tok_endfunction &&
CurTok != tok_endprocedure && CurTok != tok_endwhile && CurTok != tok_until &&
CurTok != tok_next && CurTok != tok_catch && CurTok != tok_endtry &&
CurTok != tok_eof) {