From e44bdc6a2a0cf624b08046ecc7e5c5d5d9d2d66c Mon Sep 17 00:00:00 2001 From: _ov4 Date: Fri, 16 Oct 2026 13:21:05 +0000 Subject: fix(compiler): correct CIE semantics and diagnostic locations Use standard WHILE headers and default-public class fields, resolve pseudocode identifiers without case sensitivity, and check signed i64 literal boundaries before conversion. Preserve source locations across recursive emission and function diagnostics. Add 22 regression cases and update compiler examples and documentation. Validated all 69 CTest entries with the original Linux Actions seed and verified the release package's embedded, native and IR output paths. --- AGENTS/CONVENTIONS.md | 17 +++++-- AGENTS/PITFALLS.md | 12 ++--- AGENTS/SUBSYSTEMS.md | 5 +- doc/language/cpsc-language-overview.7.md | 4 +- doc/language/function/cpsc-function-eof.7.md | 2 +- doc/language/keyword/cpsc-keyword-class.7.md | 4 +- doc/language/keyword/cpsc-keyword-readfile.7.md | 2 +- doc/language/keyword/cpsc-keyword-while.7.md | 6 +-- include/cps/AST.h | 27 ++++++---- include/cps/ArrayHandler.h | 11 ++-- include/cps/CodeGen.h | 7 ++- include/cps/FileAST.h | 16 ++---- include/cps/FunctionAST.h | 8 +-- include/cps/FunctionGen.h | 14 ++++-- include/cps/Identifier.h | 27 ++++++++++ include/cps/Lexer.h | 5 +- include/cps/Parser.h | 6 ++- include/cps/TypeAST.h | 6 +-- include/cps/TypeSystem.h | 5 +- lib/CodeGen/ArrayHandler.cc | 4 +- lib/CodeGen/Builtins.h | 3 +- lib/CodeGen/CTypeGen.cc | 6 +-- lib/CodeGen/CodeGen.cc | 6 ++- lib/CodeGen/ConstantGen.cc | 2 +- lib/CodeGen/ControlFlowGen.cc | 3 +- lib/CodeGen/Designator.cc | 3 ++ lib/CodeGen/ExpressionGen.cc | 5 +- lib/CodeGen/FunctionGen.cc | 58 ++++++++++++---------- lib/CodeGen/StatementGen.cc | 1 + lib/CodeGen/TypeCheckGen.cc | 7 +-- lib/CodeGen/TypeSystem.cc | 6 +-- lib/Lexer/Lexer.cc | 11 +++- lib/Parser/Parser.cc | 2 + lib/Parser/ParserControlFlow.cc | 12 ++--- lib/Parser/ParserExpression.cc | 42 ++++++++++++++-- lib/Parser/ParserFunction.cc | 6 +-- lib/Parser/ParserType.cc | 27 ++++++---- tests/FAILURES.md | 12 +++-- tests/README.md | 4 +- tests/i/class-default-public/notes.md | 2 +- tests/i/identifier-case/notes.md | 2 +- tests/i/identifier-domains/case.json | 3 ++ tests/i/identifier-domains/expected.txt | 7 +++ tests/i/identifier-domains/notes.md | 5 ++ tests/i/identifier-domains/program.cps | 43 ++++++++++++++++ tests/i/integer-literal-contexts/case.json | 3 ++ tests/i/integer-literal-contexts/expected.txt | 3 ++ tests/i/integer-literal-contexts/notes.md | 5 ++ tests/i/integer-literal-contexts/program.cps | 10 ++++ tests/i/loops/notes.md | 2 +- tests/i/loops/program.cps | 4 +- tests/i/text-files/notes.md | 2 +- tests/i/text-files/program.cps | 4 +- tests/ii/files/notes.md | 2 +- tests/ii/files/program.cps | 2 +- tests/iii/class-private-case/case.json | 8 +++ tests/iii/class-private-case/notes.md | 3 ++ tests/iii/class-private-case/program.cps | 5 ++ .../iii/diagnostic-call-after-arguments/case.json | 8 +++ tests/iii/diagnostic-call-after-arguments/notes.md | 3 ++ .../diagnostic-call-after-arguments/program.cps | 5 ++ tests/iii/diagnostic-multiline-name/case.json | 8 +++ tests/iii/diagnostic-multiline-name/notes.md | 3 ++ tests/iii/diagnostic-multiline-name/program.cps | 2 + tests/iii/diagnostic-nested-call/case.json | 8 +++ tests/iii/diagnostic-nested-call/notes.md | 3 ++ tests/iii/diagnostic-nested-call/program.cps | 3 ++ tests/iii/identifier-array-collision/case.json | 8 +++ tests/iii/identifier-array-collision/notes.md | 3 ++ tests/iii/identifier-array-collision/program.cps | 2 + tests/iii/identifier-builtin-collision/case.json | 8 +++ tests/iii/identifier-builtin-collision/notes.md | 3 ++ tests/iii/identifier-builtin-collision/program.cps | 3 ++ tests/iii/identifier-constant-collision/case.json | 8 +++ tests/iii/identifier-constant-collision/notes.md | 3 ++ .../iii/identifier-constant-collision/program.cps | 2 + tests/iii/identifier-enum-collision/case.json | 8 +++ tests/iii/identifier-enum-collision/notes.md | 3 ++ tests/iii/identifier-enum-collision/program.cps | 1 + tests/iii/identifier-field-collision/case.json | 8 +++ tests/iii/identifier-field-collision/notes.md | 3 ++ tests/iii/identifier-field-collision/program.cps | 4 ++ tests/iii/identifier-function-collision/case.json | 8 +++ tests/iii/identifier-function-collision/notes.md | 3 ++ .../iii/identifier-function-collision/program.cps | 6 +++ tests/iii/identifier-parameter-collision/case.json | 8 +++ tests/iii/identifier-parameter-collision/notes.md | 3 ++ .../iii/identifier-parameter-collision/program.cps | 2 + tests/iii/identifier-runtime-collision/case.json | 8 +++ tests/iii/identifier-runtime-collision/notes.md | 3 ++ tests/iii/identifier-runtime-collision/program.cps | 2 + tests/iii/identifier-type-collision/case.json | 8 +++ tests/iii/identifier-type-collision/notes.md | 3 ++ tests/iii/identifier-type-collision/program.cps | 2 + tests/iii/identifier-variable-collision/case.json | 8 +++ tests/iii/identifier-variable-collision/notes.md | 3 ++ .../iii/identifier-variable-collision/program.cps | 2 + tests/iii/integer-bound-overflow/case.json | 8 +++ tests/iii/integer-bound-overflow/notes.md | 3 ++ tests/iii/integer-bound-overflow/program.cps | 2 + tests/iii/integer-constant-overflow/case.json | 8 +++ tests/iii/integer-constant-overflow/notes.md | 3 ++ tests/iii/integer-constant-overflow/program.cps | 1 + tests/iii/integer-huge-overflow/case.json | 8 +++ tests/iii/integer-huge-overflow/notes.md | 3 ++ tests/iii/integer-huge-overflow/program.cps | 1 + tests/iii/integer-negative-overflow/case.json | 8 +++ tests/iii/integer-negative-overflow/notes.md | 3 ++ tests/iii/integer-negative-overflow/program.cps | 1 + tests/iii/integer-positive-overflow/case.json | 8 +++ tests/iii/integer-positive-overflow/notes.md | 3 ++ tests/iii/integer-positive-overflow/program.cps | 1 + tests/iii/while-do-rejected/case.json | 8 +++ tests/iii/while-do-rejected/notes.md | 3 ++ tests/iii/while-do-rejected/program.cps | 3 ++ 115 files changed, 614 insertions(+), 159 deletions(-) create mode 100644 include/cps/Identifier.h create mode 100644 tests/i/identifier-domains/case.json create mode 100644 tests/i/identifier-domains/expected.txt create mode 100644 tests/i/identifier-domains/notes.md create mode 100644 tests/i/identifier-domains/program.cps create mode 100644 tests/i/integer-literal-contexts/case.json create mode 100644 tests/i/integer-literal-contexts/expected.txt create mode 100644 tests/i/integer-literal-contexts/notes.md create mode 100644 tests/i/integer-literal-contexts/program.cps create mode 100644 tests/iii/class-private-case/case.json create mode 100644 tests/iii/class-private-case/notes.md create mode 100644 tests/iii/class-private-case/program.cps create mode 100644 tests/iii/diagnostic-call-after-arguments/case.json create mode 100644 tests/iii/diagnostic-call-after-arguments/notes.md create mode 100644 tests/iii/diagnostic-call-after-arguments/program.cps create mode 100644 tests/iii/diagnostic-multiline-name/case.json create mode 100644 tests/iii/diagnostic-multiline-name/notes.md create mode 100644 tests/iii/diagnostic-multiline-name/program.cps create mode 100644 tests/iii/diagnostic-nested-call/case.json create mode 100644 tests/iii/diagnostic-nested-call/notes.md create mode 100644 tests/iii/diagnostic-nested-call/program.cps create mode 100644 tests/iii/identifier-array-collision/case.json create mode 100644 tests/iii/identifier-array-collision/notes.md create mode 100644 tests/iii/identifier-array-collision/program.cps create mode 100644 tests/iii/identifier-builtin-collision/case.json create mode 100644 tests/iii/identifier-builtin-collision/notes.md create mode 100644 tests/iii/identifier-builtin-collision/program.cps create mode 100644 tests/iii/identifier-constant-collision/case.json create mode 100644 tests/iii/identifier-constant-collision/notes.md create mode 100644 tests/iii/identifier-constant-collision/program.cps create mode 100644 tests/iii/identifier-enum-collision/case.json create mode 100644 tests/iii/identifier-enum-collision/notes.md create mode 100644 tests/iii/identifier-enum-collision/program.cps create mode 100644 tests/iii/identifier-field-collision/case.json create mode 100644 tests/iii/identifier-field-collision/notes.md create mode 100644 tests/iii/identifier-field-collision/program.cps create mode 100644 tests/iii/identifier-function-collision/case.json create mode 100644 tests/iii/identifier-function-collision/notes.md create mode 100644 tests/iii/identifier-function-collision/program.cps create mode 100644 tests/iii/identifier-parameter-collision/case.json create mode 100644 tests/iii/identifier-parameter-collision/notes.md create mode 100644 tests/iii/identifier-parameter-collision/program.cps create mode 100644 tests/iii/identifier-runtime-collision/case.json create mode 100644 tests/iii/identifier-runtime-collision/notes.md create mode 100644 tests/iii/identifier-runtime-collision/program.cps create mode 100644 tests/iii/identifier-type-collision/case.json create mode 100644 tests/iii/identifier-type-collision/notes.md create mode 100644 tests/iii/identifier-type-collision/program.cps create mode 100644 tests/iii/identifier-variable-collision/case.json create mode 100644 tests/iii/identifier-variable-collision/notes.md create mode 100644 tests/iii/identifier-variable-collision/program.cps create mode 100644 tests/iii/integer-bound-overflow/case.json create mode 100644 tests/iii/integer-bound-overflow/notes.md create mode 100644 tests/iii/integer-bound-overflow/program.cps create mode 100644 tests/iii/integer-constant-overflow/case.json create mode 100644 tests/iii/integer-constant-overflow/notes.md create mode 100644 tests/iii/integer-constant-overflow/program.cps create mode 100644 tests/iii/integer-huge-overflow/case.json create mode 100644 tests/iii/integer-huge-overflow/notes.md create mode 100644 tests/iii/integer-huge-overflow/program.cps create mode 100644 tests/iii/integer-negative-overflow/case.json create mode 100644 tests/iii/integer-negative-overflow/notes.md create mode 100644 tests/iii/integer-negative-overflow/program.cps create mode 100644 tests/iii/integer-positive-overflow/case.json create mode 100644 tests/iii/integer-positive-overflow/notes.md create mode 100644 tests/iii/integer-positive-overflow/program.cps create mode 100644 tests/iii/while-do-rejected/case.json create mode 100644 tests/iii/while-do-rejected/notes.md create mode 100644 tests/iii/while-do-rejected/program.cps diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index 93c6f24..d1454e6 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -17,7 +17,12 @@ out-of-band via public `Lexer` fields (`IdentifierStr`, `StringVal`, `NumVal`, **Keywords are UPPERCASE and case-sensitive.** `declare` silently lexes as an identifier. Assignment is `<-` (ASCII only), equality is single `=`, inequality `<>`, -concatenation `&` (reaches the parser as raw ASCII 38 — no token). No escape sequences +concatenation `&` (reaches the parser as raw ASCII 38 — no token). Identifiers use +ASCII case-insensitive lookup/comparison through `IdentifierMap`, `IdentifierSet` +and `sameIdentifier`; retain source spelling in ASTs and diagnostics. This includes +functions, types, enum values, fields and builtins, but not C_TYPE import strings. +Integer tokens carry a checked unsigned magnitude through lookahead; the parser +applies the sign and i64 range check, including the minimum value. No escape sequences in string/char literals. Comments are `//` to end of line. **The lexer reads a caller-owned stream.** Construct it with `Lexer(std::istream &)`; @@ -43,7 +48,7 @@ sizeof. C alias declarations precede casts using their names; the parser tracks declared type names to distinguish `(Type)operand` from `(expression)`. **User-kind typing is nominal, and checked upstream only.** Enum/Record/Pointer -identity is name equality (`P1 = ^INTEGER` and `P2 = ^INTEGER` are incompatible; +identity is case-insensitive name equality (`P1 = ^INTEGER` and `P2 = ^INTEGER` are incompatible; same-layout records too). Their LLVM representations are indistinguishable from builtins, so values of these kinds must NEVER reach `ArithmeticHandler` or `coerceValueToType` without a prior name-level check — `CodeGen::emitCoercedExpr` @@ -113,7 +118,10 @@ silently ignored** — no error. Adding an AST node always means touching these **Compiler error style: print an `error:` line, set the component's error flag, and keep compiling.** No exceptions, no exit mid-compile. `Lexer`/`Parser`/`CodeGen` each -expose `hadError()`; `CodeGen::reportError(fmt, ...)` is the helper for codegen-side +expose `hadError()`; AST expressions/statements share source-line metadata. Parser +entry points record token locations. CodeGen saves/restores its diagnostic line +across recursive emission and coercion; `reportError` appends `at line N`. +`CodeGen::reportError(fmt, ...)` is the helper for codegen-side diagnostics (`ArrayHandler` reaches it through its `CodeGen &` parameter, `FunctionGen`/`ArithmeticHandler` hold a `bool &HadError` reference). The driver exits non-zero when any flag is set, printing `compilation terminated.` once. Errors that @@ -134,7 +142,8 @@ the declared return type. Top-level prototypes are pre-registered (after the TYP pre-pass) before statement emission, so call-before-definition is fine; a call with no registered signature is a compile error. Function names get no mangling, so `emitPrototype` rejects names that collide with runtime/libc symbols -(`isReservedRuntimeName`), builtins, or enum values. +(`isReservedRuntimeName`), builtins, or enum values. Signatures retain the first +declaration spelling for LLVM lookup, independently of call-site casing. **Parameter modes are sticky and BYREF is procedure-only (guide §8.3).** In `(BYREF a : T, b : T)` the `b` is BYREF too; unannotated parameters default to diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index 16fa16d..d2a24d4 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -75,9 +75,9 @@ test programs. already-invalid programs can hit it). - **CLASS subset (§10).** `CLASS`/`ENDCLASS`/`PUBLIC`/`PRIVATE` are reserved. - CLASS is a top-level record-derived value type with mandatory per-field access - keywords (no DECLARE). Unlike the guide's default-public rule, omitting access - is an error. TYPE fields cannot specify access and remain public. Private fields + CLASS is a top-level record-derived value type with optional per-field access + keywords (no DECLARE). Omitting access defaults to public, per the guide. + TYPE fields cannot specify access and remain public. Private fields cannot be accessed externally; whole-value initialization/copying is allowed. CLASS inherits record limitations, including no array fields or empty bodies. @@ -110,6 +110,6 @@ DATE, INPUT into array elements, string escape sequences, array-typed record fields, whole-array RETURN, class methods/constructors/destructors, NEW and class inheritance. Compiler fixtures now live in `tests/` (see TESTING.md). Nightly CI runs the compiler suites, packaged EMBEDDED smoke checks and existing -plugin tests. The initial compiler suite intentionally exposes existing standard -conflicts and missing diagnostic locations; see `tests/FAILURES.md` before interpreting -a failing baseline. Keep those failures visible until their implementations are fixed. +plugin tests. The initial standard conflicts and missing diagnostic locations are fixed on +Linux; see `tests/FAILURES.md` for the historical baseline and coverage gaps. +WHILE uses the standard header without DO; old WHILE DO programs must be migrated. diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index c553f8e..c64e30c 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -59,8 +59,9 @@ construction, which is why 0-based ordinals are load-bearing. `ClassTypeDeclAST` inherits `RecordTypeDeclAST`: CLASS declarations use the same two-pass registration, `TypeKind::Record`, `%record.NAME` layout and value semantics. Shared body/field parsing lives in `ParserType.cc`. TYPE fields always have -`IsPublic = true`; CLASS fields require an explicit PUBLIC/PRIVATE keyword and -carry that flag through `defineRecord` into `RecordFieldInfo`. Both the static +`IsPublic = true`; CLASS fields default to public or accept an explicit +PUBLIC/PRIVATE keyword and carry that flag through `defineRecord` into +`RecordFieldInfo`. Both the static designator type walk and `emitLValue` reject private field access, including address-of, array elements and pointer dereferences. There is no class method context or language-level inheritance yet. diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index 328fd70..233e9ef 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -6,7 +6,7 @@ cpsc-language-overview - supported cpsc pseudocode syntax # DESCRIPTION -Keywords and built-in function names are uppercase and case-sensitive. Indentation is for readability; blocks use explicit closing keywords. Identifiers begin with a letter and continue with letters, digits, or underscores. +Keywords are uppercase and case-sensitive. Identifiers, including built-in function names, are case insensitive. Indentation is for readability; blocks use explicit closing keywords. Identifiers begin with a letter and continue with letters, digits, or underscores. Assign with ASCII `<-`: `Count <- Count + 1`. Compare with `=`, `<>`, `<`, `>`, `<=`, and `>=`. STRING comparisons compare contents, case-sensitively. Use `+`, `-`, `*`, and `/` for arithmetic; `/` always returns REAL. Use `&` to join strings. @@ -48,7 +48,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; 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. +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; 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 diff --git a/doc/language/function/cpsc-function-eof.7.md b/doc/language/function/cpsc-function-eof.7.md index 2f7e271..18497cb 100644 --- a/doc/language/function/cpsc-function-eof.7.md +++ b/doc/language/function/cpsc-function-eof.7.md @@ -19,7 +19,7 @@ Return true when no further characters remain in a file opened FOR READ. The tes ```text DECLARE Line : STRING OPENFILE "notes.txt" FOR READ -WHILE NOT EOF("notes.txt") DO +WHILE NOT EOF("notes.txt") READFILE "notes.txt", Line OUTPUT Line ENDWHILE diff --git a/doc/language/keyword/cpsc-keyword-class.7.md b/doc/language/keyword/cpsc-keyword-class.7.md index 7e8f6e3..2d14387 100644 --- a/doc/language/keyword/cpsc-keyword-class.7.md +++ b/doc/language/keyword/cpsc-keyword-class.7.md @@ -15,9 +15,9 @@ ENDCLASS # DESCRIPTION -Declare a class at top level with at least one field. Every field explicitly requires PUBLIC or PRIVATE, without DECLARE. This differs from guide section 10's default-public rule. ENDCLASS closes the declaration. +Declare a class at top level with at least one field. Fields are public by default; PUBLIC or PRIVATE may be specified explicitly, without DECLARE. ENDCLASS closes the declaration. -Classes use record value semantics: zero initialization, whole-value copying, arrays, pointers, and ordinary function/procedure parameters. Only PUBLIC fields can be accessed externally. PRIVATE fields still initialize and copy with the whole value. Methods, constructors, destructors, NEW, INHERITS, and array fields are unsupported. +Classes use record value semantics: zero initialization, whole-value copying, arrays, pointers, and ordinary function/procedure parameters. Only public fields can be accessed externally. PRIVATE fields still initialize and copy with the whole value. Methods, constructors, destructors, NEW, INHERITS, and array fields are unsupported. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-readfile.7.md b/doc/language/keyword/cpsc-keyword-readfile.7.md index 200fc68..cdf96c5 100644 --- a/doc/language/keyword/cpsc-keyword-readfile.7.md +++ b/doc/language/keyword/cpsc-keyword-readfile.7.md @@ -19,7 +19,7 @@ Read one line from a file opened FOR READ, removing the line ending. The target ```text DECLARE Line : STRING OPENFILE "notes.txt" FOR READ -WHILE NOT EOF("notes.txt") DO +WHILE NOT EOF("notes.txt") READFILE "notes.txt", Line OUTPUT Line ENDWHILE diff --git a/doc/language/keyword/cpsc-keyword-while.7.md b/doc/language/keyword/cpsc-keyword-while.7.md index 5e6656f..1100b38 100644 --- a/doc/language/keyword/cpsc-keyword-while.7.md +++ b/doc/language/keyword/cpsc-keyword-while.7.md @@ -7,21 +7,21 @@ cpsc-keyword-while - repeat while a condition is true # SYNOPSIS ```text -WHILE condition DO +WHILE condition statements ENDWHILE ``` # DESCRIPTION -Test before each iteration; the body may run zero times. DO and ENDWHILE belong to this syntax. +Test before each iteration; the body may run zero times. ENDWHILE closes the loop. The WHILE header has no DO keyword. # EXAMPLE ```text DECLARE Count : INTEGER Count <- 1 -WHILE Count <= 3 DO +WHILE Count <= 3 OUTPUT Count Count <- Count + 1 ENDWHILE diff --git a/include/cps/AST.h b/include/cps/AST.h index 1afc359..5aadc21 100644 --- a/include/cps/AST.h +++ b/include/cps/AST.h @@ -6,8 +6,18 @@ namespace cps { -class ExprAST { +class SourceLocation { + int SourceLine = 0; public: + explicit SourceLocation(int Line = 0) : SourceLine(Line) {} + virtual ~SourceLocation() = default; + int getLine() const { return SourceLine; } + void setLine(int Line) { SourceLine = Line; } +}; + +class ExprAST : public SourceLocation { +public: + using SourceLocation::SourceLocation; virtual ~ExprAST() = default; }; @@ -56,13 +66,11 @@ public: class ArrayAccessExprAST : public ExprAST { std::string Name; std::vector> Indices; - int Line; public: ArrayAccessExprAST(const std::string &Name, std::vector> Indices, int Line) - : Name(Name), Indices(std::move(Indices)), Line(Line) {} + : ExprAST(Line), Name(Name), Indices(std::move(Indices)) {} const std::string &getName() const { return Name; } const std::vector> &getIndices() const { return Indices; } - int getLine() const { return Line; } }; class UnaryExprAST : public ExprAST { @@ -78,18 +86,17 @@ public: class BinaryExprAST : public ExprAST { int Op; std::unique_ptr LHS, RHS; - int Line; public: BinaryExprAST(int Op, std::unique_ptr LHS, std::unique_ptr RHS, int Line) - : Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)), Line(Line) {} + : ExprAST(Line), Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {} int getOp() const { return Op; } ExprAST *getLHS() const { return LHS.get(); } ExprAST *getRHS() const { return RHS.get(); } - int getLine() const { return Line; } }; -class StmtAST { +class StmtAST : public SourceLocation { public: + using SourceLocation::SourceLocation; virtual ~StmtAST() = default; }; @@ -154,14 +161,12 @@ class ArrayAssignStmtAST : public StmtAST { std::string Name; std::vector> Indices; std::unique_ptr Expr; - int Line; public: ArrayAssignStmtAST(const std::string &Name, std::vector> Indices, std::unique_ptr Expr, int Line) - : Name(Name), Indices(std::move(Indices)), Expr(std::move(Expr)), Line(Line) {} + : StmtAST(Line), Name(Name), Indices(std::move(Indices)), Expr(std::move(Expr)) {} const std::string &getName() const { return Name; } const std::vector> &getIndices() const { return Indices; } ExprAST *getExpr() const { return Expr.get(); } - int getLine() const { return Line; } }; class InputStmtAST : public StmtAST { diff --git a/include/cps/ArrayHandler.h b/include/cps/ArrayHandler.h index def174a..843bdc8 100644 --- a/include/cps/ArrayHandler.h +++ b/include/cps/ArrayHandler.h @@ -1,4 +1,5 @@ #pragma once +#include "cps/Identifier.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" @@ -28,10 +29,10 @@ class ArrayHandler { llvm::LLVMContext *TheContext; llvm::IRBuilder<> *Builder; llvm::Module *TheModule; - std::map *Symbols; + IdentifierMap *Symbols; TypeSystem &Types; - std::map ArrayTable; + IdentifierMap ArrayTable; RuntimeCheck &RuntimeChecker; llvm::FunctionCallee MallocFunc; @@ -63,7 +64,7 @@ public: ArrayHandler(llvm::LLVMContext &C, llvm::IRBuilder<> &B, llvm::Module &M, - std::map &Sym, + IdentifierMap &Sym, TypeSystem &TS, RuntimeCheck &RC); @@ -89,12 +90,12 @@ public: // Swap in a fresh table (function bodies get their own scope, mirroring // the Symbols save/clear/restore discipline); returns the previous table. - std::map exchangeTable(std::map NewTable); + IdentifierMap exchangeTable(IdentifierMap NewTable); // Copy for block-scoping: control-flow bodies snapshot before and restore // after, so an array declared inside a branch cannot be referenced past it // (its buffer setup and bound SSA values live inside the branch). - std::map snapshotTable() const { return ArrayTable; } + IdentifierMap snapshotTable() const { return ArrayTable; } // Callee side of an array parameter: rebuild metadata from the incoming // data pointer + per-dim bounds. MakeCopy (BYVAL) mallocs and memcpys the diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 2ba5e79..3c80eb8 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -1,4 +1,6 @@ #pragma once +#include "llvm/Support/SaveAndRestore.h" +#include "cps/Identifier.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" @@ -27,10 +29,11 @@ class CodeGen { std::unique_ptr TheContext; std::unique_ptr TheModule; std::unique_ptr> Builder; - std::map Symbols; + int DiagnosticLine = 0; + IdentifierMap Symbols; struct ConstantScope { unsigned ID; - std::map> Saved; + IdentifierMap> Saved; }; std::vector ConstantScopes = {{0, {}}}; unsigned NextScopeID = 1; diff --git a/include/cps/FileAST.h b/include/cps/FileAST.h index 04bd06a..a151597 100644 --- a/include/cps/FileAST.h +++ b/include/cps/FileAST.h @@ -9,55 +9,47 @@ namespace cps { class OpenFileStmtAST : public StmtAST { std::unique_ptr FileName; FileMode Mode; - int Line; public: OpenFileStmtAST(std::unique_ptr FileName, FileMode Mode, int Line) - : FileName(std::move(FileName)), Mode(Mode), Line(Line) {} + : StmtAST(Line), FileName(std::move(FileName)), Mode(Mode) {} ExprAST *getFileName() const { return FileName.get(); } FileMode getMode() const { return Mode; } - int getLine() const { return Line; } }; class ReadFileStmtAST : public StmtAST { std::unique_ptr FileName; std::string VarName; - int Line; public: ReadFileStmtAST(std::unique_ptr FileName, const std::string &VarName, int Line) - : FileName(std::move(FileName)), VarName(VarName), Line(Line) {} + : StmtAST(Line), FileName(std::move(FileName)), VarName(VarName) {} ExprAST *getFileName() const { return FileName.get(); } const std::string &getVarName() const { return VarName; } - int getLine() const { return Line; } }; class WriteFileStmtAST : public StmtAST { std::unique_ptr FileName; std::unique_ptr Data; - int Line; public: WriteFileStmtAST(std::unique_ptr FileName, std::unique_ptr Data, int Line) - : FileName(std::move(FileName)), Data(std::move(Data)), Line(Line) {} + : StmtAST(Line), FileName(std::move(FileName)), Data(std::move(Data)) {} ExprAST *getFileName() const { return FileName.get(); } ExprAST *getData() const { return Data.get(); } - int getLine() const { return Line; } }; class CloseFileStmtAST : public StmtAST { std::unique_ptr FileName; - int Line; public: CloseFileStmtAST(std::unique_ptr FileName, int Line) - : FileName(std::move(FileName)), Line(Line) {} + : StmtAST(Line), FileName(std::move(FileName)) {} ExprAST *getFileName() const { return FileName.get(); } - int getLine() const { return Line; } }; } // namespace cps diff --git a/include/cps/FunctionAST.h b/include/cps/FunctionAST.h index 66ac728..0c2d0b5 100644 --- a/include/cps/FunctionAST.h +++ b/include/cps/FunctionAST.h @@ -58,29 +58,25 @@ public: class CallExprAST : public ExprAST { std::string Callee; std::vector> Args; - int Line; public: CallExprAST(const std::string &Callee, std::vector> Args, int Line) - : Callee(Callee), Args(std::move(Args)), Line(Line) {} + : ExprAST(Line), Callee(Callee), Args(std::move(Args)) {} const std::string &getCallee() const { return Callee; } const std::vector> &getArgs() const { return Args; } - int getLine() const { return Line; } }; class CallStmtAST : public StmtAST { std::string Callee; std::vector> Args; - int Line; public: CallStmtAST(const std::string &Callee, std::vector> Args, int Line) - : Callee(Callee), Args(std::move(Args)), Line(Line) {} + : StmtAST(Line), Callee(Callee), Args(std::move(Args)) {} const std::string &getCallee() const { return Callee; } const std::vector> &getArgs() const { return Args; } - int getLine() const { return Line; } }; diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index 80c91ea..4bf4519 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -1,4 +1,5 @@ #pragma once +#include "cps/Identifier.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Module.h" #include "cps/FunctionAST.h" @@ -34,6 +35,7 @@ struct ParamSig { // dispatch on declared parameter types and modes instead of inferring them // back from LLVM types. struct FuncSig { + std::string Name; std::string ReturnTypeName; std::vector Params; }; @@ -55,10 +57,12 @@ private: TypeSystem &Types; ExceptionHandler &Exceptions; - std::map &Symbols; + IdentifierMap &Symbols; bool &HadError; + int &DiagnosticLine; + void reportError(const char *Fmt, ...); - std::map Signatures; + IdentifierMap Signatures; ArrayParamBinder BindArrayParam; void createArgumentAllocas(llvm::Function *F, const std::vector &Params); @@ -69,9 +73,9 @@ public: llvm::IRBuilder<> &B, TypeSystem &TS, ExceptionHandler &EH, - std::map &Sym, - bool &HadErrorFlag) - : Context(C), Module(M), Builder(B), Types(TS), Exceptions(EH), Symbols(Sym), HadError(HadErrorFlag) {} + IdentifierMap &Sym, + bool &HadErrorFlag, int &Line) + : Context(C), Module(M), Builder(B), Types(TS), Exceptions(EH), Symbols(Sym), HadError(HadErrorFlag), DiagnosticLine(Line) {} llvm::Type *getLLVMType(const std::string &TypeName); diff --git a/include/cps/Identifier.h b/include/cps/Identifier.h new file mode 100644 index 0000000..c35f636 --- /dev/null +++ b/include/cps/Identifier.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include + +namespace cps { + +inline unsigned char foldIdentifier(unsigned char C) { + return C >= 'A' && C <= 'Z' ? C + ('a' - 'A') : C; +} + +struct IdentifierLess { + bool operator()(const std::string &A, const std::string &B) const { + return std::lexicographical_compare(A.begin(), A.end(), B.begin(), B.end(), + [](unsigned char L, unsigned char R) { return foldIdentifier(L) < foldIdentifier(R); }); + } +}; + +inline bool sameIdentifier(const std::string &A, const std::string &B) { + return !IdentifierLess{}(A, B) && !IdentifierLess{}(B, A); +} + +template using IdentifierMap = std::map; +using IdentifierSet = std::set; + +} // namespace cps diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h index efedec8..749ae0b 100644 --- a/include/cps/Lexer.h +++ b/include/cps/Lexer.h @@ -25,7 +25,6 @@ enum Token { tok_endif = -9, tok_while = -10, - tok_do = -11, tok_endwhile = -12, tok_repeat = -13, @@ -116,7 +115,7 @@ class Lexer { struct TokenData { int Kind, Line; std::string Identifier, String; - int64_t Integer; + uint64_t Integer; double Real; char Character; }; @@ -129,7 +128,7 @@ public: std::string IdentifierStr; std::string StringVal; - int64_t NumVal = 0; + uint64_t NumVal = 0; double RealVal = 0; char CharVal = '\0'; diff --git a/include/cps/Parser.h b/include/cps/Parser.h index 0079c8b..fdbb99a 100644 --- a/include/cps/Parser.h +++ b/include/cps/Parser.h @@ -1,4 +1,5 @@ #pragma once +#include "cps/Identifier.h" #include "cps/Lexer.h" #include "cps/AST.h" #include "cps/TypeAST.h" @@ -24,7 +25,7 @@ class Parser { unsigned BlockDepth = 0; // ParseBlock nesting; 1 = top level std::map BinopPrecedence; - std::set TypeNames = { + IdentifierSet TypeNames = { "INTEGER", "REAL", "BOOLEAN", "STRING", "CHAR", "VOID", "C_INT", "C_CHAR", "C_STRING", "C_DOUBLE" }; @@ -33,7 +34,9 @@ class Parser { int GetTokPrecedence(); std::unique_ptr ParseExpression(); + std::unique_ptr ParseUnaryImpl(); std::unique_ptr ParseUnary(); + std::unique_ptr ParsePrimaryImpl(); std::unique_ptr ParsePrimary(); std::unique_ptr ParseBinOpRHS(int ExprPrec, std::unique_ptr LHS); @@ -71,6 +74,7 @@ class Parser { std::unique_ptr ParseCallStmt(); std::unique_ptr ParseReturnStmt(); std::optional> ParsePrototypeArgs(bool AllowByRef); + bool ParseIntegerValue(bool Negative, int64_t &Out); bool ParseSignedIntBound(int64_t &Out); std::unique_ptr ParseOpenFile(); diff --git a/include/cps/TypeAST.h b/include/cps/TypeAST.h index 873ca4a..edf2ee5 100644 --- a/include/cps/TypeAST.h +++ b/include/cps/TypeAST.h @@ -81,7 +81,7 @@ public: const std::vector &getFields() const { return Fields; } }; -// CLASS with explicitly PUBLIC/PRIVATE fields, ending in ENDCLASS. +// CLASS with default-public or explicit PUBLIC/PRIVATE fields, ending in ENDCLASS. // Storage and value semantics are inherited from record declarations. class ClassTypeDeclAST : public RecordTypeDeclAST { public: @@ -107,17 +107,15 @@ struct DesignatorAccess { class DesignatorExprAST : public ExprAST { std::string BaseName; std::vector Accesses; - int Line; public: DesignatorExprAST(const std::string &BaseName, std::vector Accesses, int Line) - : BaseName(BaseName), Accesses(std::move(Accesses)), Line(Line) {} + : ExprAST(Line), BaseName(BaseName), Accesses(std::move(Accesses)) {} const std::string &getBaseName() const { return BaseName; } const std::vector &getAccesses() const { return Accesses; } - int getLine() const { return Line; } }; // ^ — address-of. The operand is a designator by construction, diff --git a/include/cps/TypeSystem.h b/include/cps/TypeSystem.h index b615ed8..8bee6b9 100644 --- a/include/cps/TypeSystem.h +++ b/include/cps/TypeSystem.h @@ -1,4 +1,5 @@ #pragma once +#include "cps/Identifier.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/LLVMContext.h" @@ -112,8 +113,8 @@ struct SymbolInfo { class TypeSystem { llvm::LLVMContext &Context; - std::map Types; - std::map EnumConstants; + IdentifierMap Types; + IdentifierMap EnumConstants; void registerBuiltins(); const TypeInfo *registerType(const std::string &TypeName, diff --git a/lib/CodeGen/ArrayHandler.cc b/lib/CodeGen/ArrayHandler.cc index 40a330f..5eb8a52 100644 --- a/lib/CodeGen/ArrayHandler.cc +++ b/lib/CodeGen/ArrayHandler.cc @@ -12,7 +12,7 @@ using namespace cps; ArrayHandler::ArrayHandler(LLVMContext &C, IRBuilder<> &B, Module &M, - std::map &Sym, + IdentifierMap &Sym, TypeSystem &TS, RuntimeCheck &RC) : TheContext(&C), @@ -253,7 +253,7 @@ Value *ArrayHandler::emitElementAddress(ArrayAccessExprAST *Expr, CG, ElemTypeNameOut); } -std::map ArrayHandler::exchangeTable(std::map NewTable) { +IdentifierMap ArrayHandler::exchangeTable(IdentifierMap NewTable) { std::swap(ArrayTable, NewTable); return NewTable; } diff --git a/lib/CodeGen/Builtins.h b/lib/CodeGen/Builtins.h index 34e36a9..e7f60e4 100644 --- a/lib/CodeGen/Builtins.h +++ b/lib/CodeGen/Builtins.h @@ -1,4 +1,5 @@ #pragma once +#include "cps/Identifier.h" #include namespace cps::detail { @@ -29,7 +30,7 @@ inline constexpr BuiltinInfo Builtins[] = { inline const BuiltinInfo *findBuiltin(const std::string &Name) { for (const auto &B : Builtins) { - if (Name == B.Name) return &B; + if (sameIdentifier(Name, B.Name)) return &B; } return nullptr; } diff --git a/lib/CodeGen/CTypeGen.cc b/lib/CodeGen/CTypeGen.cc index a20306e..80a7d3b 100644 --- a/lib/CodeGen/CTypeGen.cc +++ b/lib/CodeGen/CTypeGen.cc @@ -81,7 +81,7 @@ Value *CodeGen::emitCast(CastExprAST *Cast) { 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)", Line); + reportError("C and pseudocode operands require an explicit cast"); return nullptr; } if (Left->isPointer() || Right->isPointer()) { @@ -99,7 +99,7 @@ Value *CodeGen::emitCBinaryOp(int Op, Value *L, const TypeInfo *Left, return nullptr; } if (Op == '&' || ((Op == tok_div || Op == tok_mod) && !Common->isIntegral())) { - reportError("Operator is not supported for these C operand types (line %d)", Line); + reportError("Operator is not supported for these C operand types"); return nullptr; } if (Op == tok_and || Op == tok_or) { @@ -154,7 +154,7 @@ Value *CodeGen::emitCBinaryOp(int Op, Value *L, const TypeInfo *Left, : Builder->CreateICmpUGE(L, R, "c_ge"); } } - reportError("Unsupported operator for C operands (line %d)", Line); + reportError("Unsupported operator for C operands"); return nullptr; } diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index b793657..dff4009 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -24,6 +24,7 @@ void CodeGen::reportError(const char *Fmt, ...) { va_start(Args, Fmt); fprintf(stderr, "error: "); vfprintf(stderr, Fmt, Args); + if (DiagnosticLine) fprintf(stderr, " at line %d", DiagnosticLine); fprintf(stderr, "\n"); va_end(Args); HadError = true; @@ -51,7 +52,7 @@ CodeGen::CodeGen() { *Types, *Exceptions, Symbols, - HadError); + HadError, DiagnosticLine); ArithHandler = std::make_unique(*TheContext, *Builder, HadError, *RuntimeChecker); @@ -158,12 +159,14 @@ void CodeGen::compile(const std::vector> &Statements) { // Pre-register every top-level FUNCTION/PROCEDURE prototype so calls may // precede definitions. for (const auto &Stmt : Statements) { + SaveAndRestore Location(DiagnosticLine, Stmt->getLine()); if (auto *FuncDef = dynamic_cast(Stmt.get())) { FuncGen->emitPrototype(FuncDef->getProto()); } } for (const auto &Stmt : Statements) { + SaveAndRestore Location(DiagnosticLine, Stmt->getLine()); emitStmt(Stmt.get()); } @@ -197,6 +200,7 @@ bool CodeGen::marshalCallArgs(const std::string &Callee, const std::vector> &ArgExprs, int Line, std::vector &Out) { + SaveAndRestore Location(DiagnosticLine, Line ? Line : DiagnosticLine); const FuncSig *Sig = FuncGen->getSignature(Callee); if (!Sig) { reportError("Call to undefined function %s", Callee.c_str()); diff --git a/lib/CodeGen/ConstantGen.cc b/lib/CodeGen/ConstantGen.cc index e9f81ae..e14e22f 100644 --- a/lib/CodeGen/ConstantGen.cc +++ b/lib/CodeGen/ConstantGen.cc @@ -19,7 +19,7 @@ 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) { + if (It->second.ScopeID == Scope.ID) { reportError("Declaration of %s conflicts with an existing declaration in this scope", Name.c_str()); return false; } diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc index d34f640..17f43f2 100644 --- a/lib/CodeGen/ControlFlowGen.cc +++ b/lib/CodeGen/ControlFlowGen.cc @@ -190,6 +190,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) { auto *After = BasicBlock::Create(*TheContext, "case_cont", F); auto SavedArrays = Arrays->snapshotTable(); for (const auto &Branch : Stmt->getBranches()) { + SaveAndRestore Location(DiagnosticLine, Branch.Line); auto *Body = BasicBlock::Create(*TheContext, "case_body", F); auto *Next = BasicBlock::Create(*TheContext, "case_next", F); auto Compare = [&](int Op, ExprAST *Label) -> Value * { @@ -199,7 +200,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) { } const TypeInfo *LabelType = getExprTypeInfo(Label); if (!LabelType) { - reportError("Cannot determine CASE label type at line %d", Branch.Line); + reportError("Cannot determine CASE label type"); return nullptr; } Value *LabelValue = emitExpr(Label); diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index afaf0a4..fcc6919 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -15,6 +15,7 @@ void CodeGen::runTypePrePass(const std::vector> &Statem std::string Err; for (const auto &S : Statements) { + SaveAndRestore Location(DiagnosticLine, S->getLine()); bool Ok = true; if (auto *C = dynamic_cast(S.get())) Ok = Types->defineCAlias(C->getName(), C->getExpression(), Err); @@ -28,6 +29,7 @@ void CodeGen::runTypePrePass(const std::vector> &Statem } for (const auto &S : Statements) { + SaveAndRestore Location(DiagnosticLine, S->getLine()); bool Ok = true; if (auto *E = dynamic_cast(S.get())) { Ok = Types->defineEnum(E->getName(), E->getValueNames(), Err); @@ -44,6 +46,7 @@ void CodeGen::runTypePrePass(const std::vector> &Statem // of its storage plus the type name stored there. Array indexing goes through // ArrayHandler's checked pipeline; dereferences emit the null-pointer check. bool CodeGen::emitLValue(ExprAST *E, LValueInfo &Out) { + SaveAndRestore Location(DiagnosticLine, E && E->getLine() ? E->getLine() : DiagnosticLine); if (auto *Var = dynamic_cast(E)) { const SymbolInfo *Info = getSymbolInfo(Var->getName()); if (!Info) { diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index f3dadb7..b3655fd 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -11,6 +11,7 @@ using namespace cps; using namespace cps::detail; Value *CodeGen::emitExpr(ExprAST *Expr) { + SaveAndRestore Location(DiagnosticLine, Expr && Expr->getLine() ? Expr->getLine() : DiagnosticLine); if (auto *Cast = dynamic_cast(Expr)) return emitCast(Cast); if (!Expr) return nullptr; @@ -126,6 +127,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { if (auto *Call = dynamic_cast(Expr)) { std::string Name = Call->getCallee(); if (const BuiltinInfo *B = findBuiltin(Name)) { + Name = B->Name; if (Call->getArgs().size() != B->Arity) { reportError("%s expects %u arg%s", B->Name, B->Arity, B->Arity == 1 ? "" : "s"); return nullptr; @@ -247,6 +249,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { // 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) { + SaveAndRestore Location(DiagnosticLine, Line ? Line : DiagnosticLine); if (!L || !R) return nullptr; if ((LT && LT->isC()) || (RT && RT->isC())) return emitCBinaryOp(Op, L, LT, R, RT, Line); @@ -261,7 +264,7 @@ Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT, 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); + reportError("STRING comparison requires two STRING operands"); return nullptr; } return StrHandler->emitCompare(Op, L, R); diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 957a1f5..a155f60 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -2,6 +2,7 @@ #include "cps/CodeGen.h" #include "cps/ExceptionHandler.h" #include +#include using namespace llvm; using namespace cps; @@ -19,16 +20,26 @@ static bool isReservedRuntimeName(const std::string &Name) { "__gxx_personality_seh0", "__gxx_personality_sj0", "_Unwind_Resume", "_ZTIi", }; for (const char *R : Reserved) { - if (Name == R) return true; + if (sameIdentifier(Name, R)) return true; } return false; } +void FunctionGen::reportError(const char *Fmt, ...) { + va_list Args; + va_start(Args, Fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, Fmt, Args); + if (DiagnosticLine) fprintf(stderr, " at line %d", DiagnosticLine); + fprintf(stderr, "\n"); + va_end(Args); + HadError = true; +} + Type *FunctionGen::getLLVMType(const std::string &TypeName) { Type *Resolved = Types.getLLVMType(TypeName); if (!Resolved) { - fprintf(stderr, "error: Unknown type %s\n", TypeName.c_str()); - HadError = true; + reportError("Unknown type %s", TypeName.c_str()); return Type::getInt64Ty(Context); } return Resolved; @@ -38,12 +49,10 @@ void FunctionGen::createArgumentAllocas(Function *F, const std::vectorarg_begin(); for (const ParamDecl &P : Params) { if (Types.lookupEnumConstant(P.Name)) { - fprintf(stderr, "error: parameter '%s' collides with an enum value\n", P.Name.c_str()); - HadError = true; + reportError("parameter '%s' collides with an enum value", P.Name.c_str()); } if (Types.resolve(P.Name)) { - fprintf(stderr, "error: parameter '%s' collides with a type name\n", P.Name.c_str()); - HadError = true; + reportError("parameter '%s' collides with a type name", P.Name.c_str()); } Value *ArgVal = &(*AI); ++AI; @@ -88,39 +97,35 @@ const FuncSig *FunctionGen::getSignature(const std::string &Name) const { Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { if (isReservedRuntimeName(Proto->getName())) { - fprintf(stderr, "error: '%s' is a reserved runtime symbol and cannot be a FUNCTION/PROCEDURE name\n", + reportError("'%s' is a reserved runtime symbol and cannot be a FUNCTION/PROCEDURE name", Proto->getName().c_str()); - HadError = true; return nullptr; } if (CodeGen::isBuiltinName(Proto->getName())) { - fprintf(stderr, "error: '%s' is a built-in function and cannot be redefined\n", + reportError("'%s' is a built-in function and cannot be redefined", Proto->getName().c_str()); - HadError = true; return nullptr; } if (Types.lookupEnumConstant(Proto->getName())) { - fprintf(stderr, "error: '%s' is an enum value and cannot be a FUNCTION/PROCEDURE name\n", + reportError("'%s' is an enum value and cannot be a FUNCTION/PROCEDURE name", Proto->getName().c_str()); - HadError = true; return nullptr; } if (Types.resolve(Proto->getName())) { - fprintf(stderr, "error: '%s' is a type name and cannot be a FUNCTION/PROCEDURE name\n", + reportError("'%s' is a type name and cannot be a FUNCTION/PROCEDURE name", Proto->getName().c_str()); - HadError = true; return nullptr; } FuncSig Sig; + Sig.Name = Proto->getName(); Sig.ReturnTypeName = Proto->getReturnType(); std::vector ArgTypes; for (const ParamDecl &P : Proto->getParams()) { const TypeInfo *Info = Types.resolve(P.TypeName); if (Info && Info->isVoid()) { - fprintf(stderr, "error: parameter %s cannot have void type\n", P.Name.c_str()); - HadError = true; + reportError("parameter %s cannot have void type", P.Name.c_str()); return nullptr; } ParamSig PS; @@ -148,11 +153,11 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { Type *RetType = getLLVMType(Proto->getReturnType()); FunctionType *FT = FunctionType::get(RetType, ArgTypes, false); - Function *F = Module.getFunction(Proto->getName()); + const FuncSig *Existing = getSignature(Proto->getName()); + Function *F = Module.getFunction(Existing ? Existing->Name : Proto->getName()); if (F && F->getFunctionType() != FT) { - fprintf(stderr, "error: Function %s conflicts with an existing declaration of a different type\n", + reportError("Function %s conflicts with an existing declaration of a different type", Proto->getName().c_str()); - HadError = true; return nullptr; } if (!F) { @@ -184,15 +189,14 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, if (!TheFunction) return nullptr; if (!TheFunction->empty()) { - fprintf(stderr, "error: Function %s cannot be redefined.\n", Proto->getName().c_str()); - HadError = true; + reportError("Function %s cannot be redefined.", Proto->getName().c_str()); return nullptr; } BasicBlock *BB = BasicBlock::Create(Context, "entry", TheFunction); Builder.SetInsertPoint(BB); - std::map OldSymbols = Symbols; + IdentifierMap OldSymbols = Symbols; Symbols.clear(); for (const auto &[Name, Info] : OldSymbols) { if (Info.ConstantValue && Info.ScopeID == 0) Symbols.emplace(Name, Info); @@ -209,7 +213,7 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, } if (!Builder.GetInsertBlock()->hasTerminator()) { - if (Proto->getReturnType() == "VOID") { + if (sameIdentifier(Proto->getReturnType(), "VOID")) { Builder.CreateRetVoid(); } else { Builder.CreateRet(Constant::getNullValue(TheFunction->getReturnType())); @@ -240,11 +244,13 @@ static Value *GenerateCall(llvm::Module &Module, } llvm::Value *FunctionGen::emitCallExpr(CallExprAST *Call, const std::vector &Args) { - return GenerateCall(Module, Exceptions, Call->getCallee(), Args, HadError); + const FuncSig *Sig = getSignature(Call->getCallee()); + return GenerateCall(Module, Exceptions, Sig ? Sig->Name : Call->getCallee(), Args, HadError); } void FunctionGen::emitCallStmt(CallStmtAST *Call, const std::vector &Args) { - GenerateCall(Module, Exceptions, Call->getCallee(), Args, HadError); + const FuncSig *Sig = getSignature(Call->getCallee()); + GenerateCall(Module, Exceptions, Sig ? Sig->Name : Call->getCallee(), Args, HadError); } void FunctionGen::emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal) { diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 8907cfa..3ea0670 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -9,6 +9,7 @@ using namespace llvm; using namespace cps; void CodeGen::emitStmt(StmtAST *Stmt) { + SaveAndRestore Location(DiagnosticLine, Stmt && Stmt->getLine() ? Stmt->getLine() : DiagnosticLine); if (!Stmt) return; if (auto *FuncDef = dynamic_cast(Stmt)) { diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc index 654afc8..690d005 100644 --- a/lib/CodeGen/TypeCheckGen.cc +++ b/lib/CodeGen/TypeCheckGen.cc @@ -33,6 +33,7 @@ std::string opToText(int Op) { // LLVM level); builtin targets additionally reject user-kind sources, then // keep the existing representation coercions unchanged. Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { + SaveAndRestore Location(DiagnosticLine, E && E->getLine() ? E->getLine() : DiagnosticLine); if (!E || !Target) return nullptr; // A whole-array reference evaluates to its raw data pointer, and @@ -94,7 +95,7 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { } return nullptr; } - if (Src->Name != Target->Name) { + if (!Src->sameType(Target)) { reportError("Cannot use a value of type %s where %s is expected", Src->Name.c_str(), Target->Name.c_str()); return nullptr; @@ -133,7 +134,7 @@ Value *CodeGen::emitUserKindBinaryOp(int Op, Value *L, const TypeInfo *LT, } if ((LT && LT->isPointer()) || (RT && RT->isPointer())) { - bool SameName = LT && RT && LT->isPointer() && RT->isPointer() && LT->Name == RT->Name; + bool SameName = LT && RT && LT->isPointer() && RT->isPointer() && LT->sameType(RT); if (SameName && (Op == tok_eq || Op == tok_ne)) { return Op == tok_eq ? Builder->CreateICmpEQ(L, R, "ptr_eq") : Builder->CreateICmpNE(L, R, "ptr_ne"); @@ -144,7 +145,7 @@ Value *CodeGen::emitUserKindBinaryOp(int Op, Value *L, const TypeInfo *LT, } // At least one enum operand from here on. - bool BothSameEnum = LT && RT && LT->isEnum() && RT->isEnum() && LT->Name == RT->Name; + bool BothSameEnum = LT && RT && LT->isEnum() && RT->isEnum() && LT->sameType(RT); switch (Op) { case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge: { diff --git a/lib/CodeGen/TypeSystem.cc b/lib/CodeGen/TypeSystem.cc index a837c9a..f4620dc 100644 --- a/lib/CodeGen/TypeSystem.cc +++ b/lib/CodeGen/TypeSystem.cc @@ -12,12 +12,12 @@ bool TypeInfo::sameType(const TypeInfo *Other) const { if (!Other) return false; if (isC() || Other->isC()) return isC() && Other->isC() && CIdentity == Other->CIdentity; - return Name == Other->Name; + return sameIdentifier(Name, Other->Name); } const RecordFieldInfo *RecordPayload::findField(const std::string &Name) const { for (const auto &F : Fields) { - if (F.first == Name) return &F.second; + if (sameIdentifier(F.first, Name)) return &F.second; } return nullptr; } @@ -172,7 +172,7 @@ bool TypeSystem::defineEnum(const std::string &Name, for (size_t I = 0; I < Values.size(); ++I) { const std::string &V = Values[I]; for (size_t J = 0; J < I; ++J) { - if (Values[J] == V) { + if (sameIdentifier(Values[J], V)) { Err = "Duplicate value '" + V + "' in enum " + Name; return false; } diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc index 59247cf..9203db1 100644 --- a/lib/Lexer/Lexer.cc +++ b/lib/Lexer/Lexer.cc @@ -1,5 +1,7 @@ #include "cps/Lexer.h" #include +#include +#include #include #include #include @@ -104,7 +106,6 @@ int Lexer::readToken() { if (IdentifierStr == "THROW") return tok_throw; if (IdentifierStr == "WHILE") return tok_while; - if (IdentifierStr == "DO") return tok_do; if (IdentifierStr == "ENDWHILE") return tok_endwhile; if (IdentifierStr == "REPEAT") return tok_repeat; @@ -175,7 +176,13 @@ int Lexer::readToken() { return tok_number_real; } - NumVal = strtoll(NumStr.c_str(), nullptr, 10); + auto Parsed = std::from_chars(NumStr.data(), NumStr.data() + NumStr.size(), NumVal); + if (Parsed.ec != std::errc{} || + NumVal > uint64_t(std::numeric_limits::max()) + 1) { + fprintf(stderr, "error: Integer literal is outside the i64 range at line %d\n", TokenLine); + HadError = true; + NumVal = 0; + } return tok_number_int; } diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index d8fecdc..f2fcbc8 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -86,7 +86,9 @@ std::vector> Parser::ParseBlock(std::initializer_list Parser::ParseStatement() { + int Line = Lex.getLine(); auto Stmt = ParseStatementImpl(); + if (Stmt && !Stmt->getLine()) Stmt->setLine(Line); if (!Stmt) ++NumErrors; return Stmt; } diff --git a/lib/Parser/ParserControlFlow.cc b/lib/Parser/ParserControlFlow.cc index d01e561..afa8bd8 100644 --- a/lib/Parser/ParserControlFlow.cc +++ b/lib/Parser/ParserControlFlow.cc @@ -27,7 +27,7 @@ std::unique_ptr Parser::ParseIfStmt() { if (!Cond) return nullptr; if (CurTok != tok_then) { - fprintf(stderr, "error: expected THEN\n"); + fprintf(stderr, "error: expected THEN at line %d\n", Lex.getLine()); return nullptr; } getNextToken(); @@ -41,7 +41,7 @@ std::unique_ptr Parser::ParseIfStmt() { } if (CurTok != tok_endif) { - fprintf(stderr, "error: expected ENDIF\n"); + fprintf(stderr, "error: expected ENDIF at line %d\n", Lex.getLine()); return nullptr; } getNextToken(); @@ -54,12 +54,6 @@ std::unique_ptr Parser::ParseWhileStmt() { auto Cond = ParseExpression(); if (!Cond) return nullptr; - if (CurTok != tok_do) { - fprintf(stderr, "error: expected DO after WHILE condition\n"); - return nullptr; - } - getNextToken(); - std::vector> Body = ParseBlock({tok_endwhile}); if (CurTok != tok_endwhile) { @@ -135,7 +129,7 @@ std::unique_ptr Parser::ParseForStmt() { return nullptr; } - if (Lex.IdentifierStr != VarName) { + if (!sameIdentifier(Lex.IdentifierStr, VarName)) { fprintf(stderr, "error: NEXT identifier '%s' does not match FOR variable '%s'\n", Lex.IdentifierStr.c_str(), VarName.c_str()); return nullptr; diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc index d32c407..b8952a8 100644 --- a/lib/Parser/ParserExpression.cc +++ b/lib/Parser/ParserExpression.cc @@ -1,6 +1,7 @@ #include "cps/Parser.h" #include "cps/FunctionAST.h" #include +#include using namespace cps; @@ -13,15 +14,29 @@ int Parser::GetTokPrecedence() { } +bool Parser::ParseIntegerValue(bool Negative, int64_t &Out) { + const uint64_t Max = uint64_t(std::numeric_limits::max()); + if (Lex.NumVal > Max + (Negative ? 1u : 0u)) { + fprintf(stderr, "error: Integer literal is outside the i64 range at line %d\n", Lex.getLine()); + getNextToken(); + return false; + } + Out = Negative && Lex.NumVal == Max + 1 + ? std::numeric_limits::min() + : Negative ? -static_cast(Lex.NumVal) : static_cast(Lex.NumVal); + getNextToken(); + return true; +} + std::unique_ptr Parser::ParseNumberExpr() { if (CurTok == tok_number_real) { auto Result = std::make_unique(Lex.RealVal); getNextToken(); return Result; } - auto Result = std::make_unique(Lex.NumVal); - getNextToken(); - return Result; + int64_t Value; + if (!ParseIntegerValue(false, Value)) return nullptr; + return std::make_unique(Value); } std::unique_ptr Parser::ParseIdentifierExpr() { @@ -109,7 +124,7 @@ std::unique_ptr Parser::ParseParenExpr() { auto V = ParseExpression(); if (!V) return nullptr; if (CurTok != ')') { - fprintf(stderr, "error: expected ')'\n"); + fprintf(stderr, "error: expected ')' at line %d\n", Lex.getLine()); return nullptr; } getNextToken(); @@ -117,6 +132,13 @@ std::unique_ptr Parser::ParseParenExpr() { } std::unique_ptr Parser::ParsePrimary() { + int Line = Lex.getLine(); + auto Expr = ParsePrimaryImpl(); + if (Expr && !Expr->getLine()) Expr->setLine(Line); + return Expr; +} + +std::unique_ptr Parser::ParsePrimaryImpl() { switch (CurTok) { case tok_identifier: return ParseIdentifierExpr(); case tok_number_int: return ParseNumberExpr(); @@ -148,6 +170,13 @@ std::unique_ptr Parser::ParsePrimary() { } std::unique_ptr Parser::ParseUnary() { + int Line = Lex.getLine(); + auto Expr = ParseUnaryImpl(); + if (Expr && !Expr->getLine()) Expr->setLine(Line); + return Expr; +} + +std::unique_ptr Parser::ParseUnaryImpl() { if (CurTok == tok_not) { getNextToken(); auto Operand = ParseUnary(); @@ -178,6 +207,11 @@ std::unique_ptr Parser::ParseUnary() { if (CurTok == '-') { getNextToken(); + if (CurTok == tok_number_int) { + int64_t Value; + if (!ParseIntegerValue(true, Value)) return nullptr; + return std::make_unique(Value); + } auto Operand = ParseUnary(); if (!Operand) return nullptr; return std::make_unique('-', std::move(Operand)); diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc index c47485e..fe53731 100644 --- a/lib/Parser/ParserFunction.cc +++ b/lib/Parser/ParserFunction.cc @@ -17,9 +17,7 @@ bool Parser::ParseSignedIntBound(int64_t &Out) { Lex.getLine()); return false; } - Out = Negative ? -Lex.NumVal : Lex.NumVal; - getNextToken(); - return true; + return ParseIntegerValue(Negative, Out); } // ( [BYREF|BYVAL] : @@ -107,7 +105,7 @@ std::optional> Parser::ParsePrototypeArgs(bool AllowByRef } for (const ParamDecl &Prev : Params) { - if (Prev.Name == P.Name) { + if (sameIdentifier(Prev.Name, P.Name)) { fprintf(stderr, "error: Duplicate parameter name '%s' at line %d\n", P.Name.c_str(), Lex.getLine()); return std::nullopt; diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc index 918b7f6..a57c8a6 100644 --- a/lib/Parser/ParserType.cc +++ b/lib/Parser/ParserType.cc @@ -112,19 +112,19 @@ std::optional> Parser::ParseRecordBody( const char *Keyword = IsClass ? "CLASS" : "TYPE"; const char *EndKeyword = IsClass ? "ENDCLASS" : "ENDTYPE"; auto AtField = [&] { - return IsClass ? (CurTok == tok_public || CurTok == tok_private) + return IsClass ? (CurTok == tok_public || CurTok == tok_private || CurTok == tok_identifier) : CurTok == tok_declare; }; std::vector Fields; bool HadFieldError = false; while (CurTok != EndToken && CurTok != tok_eof) { if (AtField()) { - // Record fields are always public; class fields have no default. - bool IsPublic = !IsClass || CurTok == tok_public; + // Class fields default to public, like record fields. + bool IsPublic = CurTok != tok_private; if (ParseRecordField(Fields, IsPublic)) continue; } else { fprintf(stderr, "error: Expected %s or %s in %s %s at line %d\n", - IsClass ? "PUBLIC or PRIVATE" : "DECLARE", EndKeyword, + IsClass ? "field, PUBLIC or PRIVATE" : "DECLARE", EndKeyword, Keyword, Name.c_str(), Lex.getLine()); } HadFieldError = true; @@ -155,7 +155,7 @@ std::optional> Parser::ParseRecordBody( bool Parser::ParseRecordField(std::vector &Out, bool IsPublic) { const char *Introducer = CurTok == tok_declare ? "DECLARE" : CurTok == tok_public ? "PUBLIC" : "PRIVATE"; - getNextToken(); // eat field introducer + if (CurTok != tok_identifier) getNextToken(); // optional class access keyword if (CurTok != tok_identifier) { fprintf(stderr, "error: Expected field name after %s at line %d\n", Introducer, Lex.getLine()); return false; @@ -213,7 +213,7 @@ std::string Parser::ParseTypeName(bool AllowVoid) { } if (CurTok == tok_identifier) { std::string TypeName = Lex.IdentifierStr; - if (!AllowVoid && TypeName == "VOID") { + if (!AllowVoid && sameIdentifier(TypeName, "VOID")) { fprintf(stderr, "error: VOID is not allowed here\n"); return ""; } @@ -321,9 +321,18 @@ std::unique_ptr Parser::ParseConstant() { 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)); + std::unique_ptr Value; + if (CurTok == tok_number_int) { + int Line = Lex.getLine(); + int64_t Integer; + if (!ParseIntegerValue(Negative, Integer)) return nullptr; + Value = std::make_unique(Integer); + Value->setLine(Line); + } else { + 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; diff --git a/tests/FAILURES.md b/tests/FAILURES.md index 83744e5..d88b850 100644 --- a/tests/FAILURES.md +++ b/tests/FAILURES.md @@ -1,10 +1,12 @@ # Compiler test findings -Baseline: Linux x86_64, Clang/LLVM 23.1.3, Release EMBEDDED, compiler sources at `e2c6570`. Tests do not modify compiler sources. The full suite has 45 compiler cases plus harness and portable-generator checks. Fixed output expectations are pending human review. +Baseline: Linux x86_64, Clang/LLVM 23.1.3, Release EMBEDDED, compiler sources at `e2c6570`. Tests do not modify compiler sources. The initial suite had 45 compiler cases plus harness and portable-generator checks. Fixed output expectations are pending human review. -## Observed failures +The expanded suite has 67 compiler cases plus harness and portable-generator checks (69 CTest entries). The 13 failures below were reproduced with the Actions seed `e1e789ac0249149a4aa1671f5f9260d488cb617a` and are now fixed on Linux Release EMBEDDED. Regression cases cover case-insensitive name domains and collisions, default/public/private fields, checked i64 literals in expressions/constants/parameter bounds, rejection of WHILE DO, and nested/multiline diagnostic locations. Existing fixed expectations and differential generators remain unchanged; WHILE fixtures now use the standard syntax. -| Case | Required behavior | Current behavior | +## Historical failures (fixed on Linux) + +| Case | Required behavior | Baseline behavior | |---|---|---| | i/class-default-public | Omitted access defaults to public (CIE p. 28). | Rejects the field unless PUBLIC/PRIVATE is explicit. | | i/identifier-case | Identifier lookup is case insensitive (CIE p. 7). | Reports the differently cased variable as unknown. | @@ -20,9 +22,9 @@ Baseline: Linux x86_64, Clang/LLVM 23.1.3, Release EMBEDDED, compiler sources at | iii/nominal-type-mismatch | Incompatible named pointers, line 7. | Correct cause, no line. | | iii/unknown-name | Undeclared name, line 2. | Correct cause, no line. | -Both native and `-r` paths expose the four i-group failures. All nine iii-group failures occur with both native/IR output requests, whether the destination was absent or already contained a sentinel. Failed compilation preserves the destination in these cases. The remaining 32 compiler cases passed locally, including seven groups of 10,000 random records plus 36 boundary records (70,036 total inputs, each checked through native and embedded execution). +At baseline, both native and `-r` paths exposed the four i-group failures. All nine iii-group failures occurred with both native/IR output requests, whether the destination was absent or already contained a sentinel. Failed compilation preserves the destination in these cases. The remaining 32 compiler cases passed locally, including seven groups of 10,000 random records plus 36 boundary records (70,036 total inputs, each checked through native and embedded execution). -The baseline test seed and exact command results are retained in `build/test-results`; subsequent CI reports use their own explicit push SHA. Platform results from Actions are the authority for Windows/macOS verification; local Linux results do not establish their behavior. +Current exact command results are retained in `build/test-results`; the replay uses the original Actions SHA as its seed. Subsequent CI reports use their own explicit push SHA. Platform results from Actions are the authority for Windows/macOS verification; local Linux results do not establish their behavior. ## Coverage gaps diff --git a/tests/README.md b/tests/README.md index 9024191..dd1407a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -9,7 +9,7 @@ ctest --test-dir build --output-on-failure --parallel 2 python3 tests/summarize.py build/test-results ``` -Use `ctest --test-dir build -L '^ii$' --output-on-failure` for one suite, or `-R '^i/arrays$'` for one case. CTest returns failure if any case fails; it continues through the other cases. [Current failures and coverage gaps](FAILURES.md) are intentionally visible, with no expected-failure exemptions. +Use `ctest --test-dir build -L '^ii$' --output-on-failure` for one suite, or `-R '^i/arrays$'` for one case. CTest returns failure if any case fails; it continues through the other cases. [Historical failures and coverage gaps](FAILURES.md) are intentionally visible, with no expected-failure exemptions. The suites are **i**: independently traced fixed outputs, **ii**: C++ differential tests with Perl inputs, and **iii**: rejected programs with required causes and source lines. Fixed expectations are **pending human review**; each case's notes explain the trace and distinguish CIE rules from project extensions. REAL calculations are observed through BOOLEAN/INTEGER output; REAL text and REAL-to-string formatting remain outside this suite. @@ -26,6 +26,6 @@ Use the actual SHA from a failed report in place of `FULL_COMMIT_SHA`. Clear `CP `build/test-results///` contains `result.json`, numbered command metadata, stdin/stdout/stderr, expected output and isolated work directories. Random mismatches also save the first differing record. Re-running a case replaces only that case's directory; summaries include any other retained results, so remove `build/test-results` before collecting a fresh full-suite report. Commands have finite timeouts (30 seconds normally, 120 for compilation, 180 for file-intensive executions); timeout and launch/crash failures fail the case. -In Actions, each compiler platform runs all three suites and summarizes results before its existing package smoke checks and upload. Failures retain inputs and logs as an Actions artifact for 14 days. Each platform publishes only after its own tests pass. These new tests expose existing defects, so compiler packages will remain unpublished until those defects are fixed; the independent plugin job can still publish. +In Actions, each compiler platform runs all three suites and summarizes results before its existing package smoke checks and upload. Failures retain inputs and logs as an Actions artifact for 14 days. Each platform publishes only after its own tests pass. The initial Linux failures have been fixed; any failing compiler test still blocks publication for that platform. The independent plugin job can publish separately. For adding cases and runner interfaces, see [AGENTS/TESTING.md](../AGENTS/TESTING.md). diff --git a/tests/i/class-default-public/notes.md b/tests/i/class-default-public/notes.md index 1f5c869..db762db 100644 --- a/tests/i/class-default-public/notes.md +++ b/tests/i/class-default-public/notes.md @@ -2,6 +2,6 @@ Human review: pending. Expected output was independently traced by AI, not captured from cpsc. -Basis: CIE p. 28: methods and properties are public unless otherwise stated. This conflicts with the current explicit-access requirement. +Basis: CIE p. 28: methods and properties are public unless otherwise stated. The compiler accepts omitted access as public. Trace: Value has default public access. Assigning and reading it must yield 7. A compile rejection is a failure, not an accepted alternative. diff --git a/tests/i/identifier-case/notes.md b/tests/i/identifier-case/notes.md index ac8c200..ac6e5e8 100644 --- a/tests/i/identifier-case/notes.md +++ b/tests/i/identifier-case/notes.md @@ -2,6 +2,6 @@ Human review: pending. Expected output was independently traced by AI, not captured from cpsc. -Basis: CIE p. 7: identifiers should be considered case insensitive. Current symbol lookup is case sensitive. +Basis: CIE p. 7: identifiers should be considered case insensitive. Symbol lookup must resolve both spellings to the same declaration. Trace: Counter and counter denote the same declared variable; the read yields 7. diff --git a/tests/i/identifier-domains/case.json b/tests/i/identifier-domains/case.json new file mode 100644 index 0000000..1f18635 --- /dev/null +++ b/tests/i/identifier-domains/case.json @@ -0,0 +1,3 @@ +{ + "exit_code": 0 +} diff --git a/tests/i/identifier-domains/expected.txt b/tests/i/identifier-domains/expected.txt new file mode 100644 index 0000000..95fa0cf --- /dev/null +++ b/tests/i/identifier-domains/expected.txt @@ -0,0 +1,7 @@ +4 +6 +7:TRUE +8:9 +5:3 +5 +3 diff --git a/tests/i/identifier-domains/notes.md b/tests/i/identifier-domains/notes.md new file mode 100644 index 0000000..1a03605 --- /dev/null +++ b/tests/i/identifier-domains/notes.md @@ -0,0 +1,5 @@ +# identifier-domains + +Human review: pending. Expected output was independently traced, not captured from cpsc. + +CIE pp. 7, 28: names ignore case and omitted class access is public. The array is changed by reference to 4 and 6; the node is changed through its pointer to 7; Summer is the second enumerator. Class fields hold 8 and 9. The string length is 5 and the native int cast is 3. A block shadows Limit with 5, then restores 3. The function and procedure calls precede their definitions. diff --git a/tests/i/identifier-domains/program.cps b/tests/i/identifier-domains/program.cps new file mode 100644 index 0000000..d1b684b --- /dev/null +++ b/tests/i/identifier-domains/program.cps @@ -0,0 +1,43 @@ +TYPE Node + DECLARE Value : INTEGER +ENDTYPE +TYPE Link = ^Node +TYPE Season = (Spring, Summer) +TYPE Signed = C_TYPE("int") +CLASS Box + Value : INTEGER + PUBLIC Other : INTEGER + PRIVATE Hidden : INTEGER +ENDCLASS +CONSTANT Limit = 3 +DECLARE Item : nODE +DECLARE Ptr : lINK +DECLARE Mode : sEASON +DECLARE Crate : bOX +DECLARE Items : ARRAY[1:2] OF INTEGER +DECLARE Index : INTEGER +item.value <- limit +pTR <- ^ITEM +ptr^.vALUE <- 7 +mode <- sUMMER +crate.value <- 8 +CRATE.other <- 9 +CALL cHANGE(iTEMS) +FOR Index <- 1 TO 2 + OUTPUT items[index] +NEXT iNDEX +OUTPUT rEADnODE(ITEM), ":", MODE = summer +OUTPUT crate.VALUE, ":", crate.OTHER +OUTPUT length("MiXeD"), ":", (sIGNED)3 +IF TRUE THEN + CONSTANT lIMIT = 5 + OUTPUT LIMIT +ENDIF +OUTPUT Limit +PROCEDURE Change(BYREF Values : ARRAY OF INTEGER) + vALUES[1] <- 4 + VALUES[2] <- 6 +ENDPROCEDURE +FUNCTION ReadNode(Value : NODE) RETURNS INTEGER + RETURN vALUE.Value +ENDFUNCTION diff --git a/tests/i/integer-literal-contexts/case.json b/tests/i/integer-literal-contexts/case.json new file mode 100644 index 0000000..1f18635 --- /dev/null +++ b/tests/i/integer-literal-contexts/case.json @@ -0,0 +1,3 @@ +{ + "exit_code": 0 +} diff --git a/tests/i/integer-literal-contexts/expected.txt b/tests/i/integer-literal-contexts/expected.txt new file mode 100644 index 0000000..e23c6cc --- /dev/null +++ b/tests/i/integer-literal-contexts/expected.txt @@ -0,0 +1,3 @@ +9223372036854775807 +-9223372036854775808:9223372036854775807 +0:-1:-3 diff --git a/tests/i/integer-literal-contexts/notes.md b/tests/i/integer-literal-contexts/notes.md new file mode 100644 index 0000000..6285895 --- /dev/null +++ b/tests/i/integer-literal-contexts/notes.md @@ -0,0 +1,5 @@ +# integer-literal-contexts + +Human review: pending. Expected output was independently traced, not captured from cpsc. + +The i64 endpoints are exact in constants, expressions and signed parameter bounds. The array has one element, containing Maximum. Ordinary unary negation still yields 0, -1 and -3. diff --git a/tests/i/integer-literal-contexts/program.cps b/tests/i/integer-literal-contexts/program.cps new file mode 100644 index 0000000..3ebc7fb --- /dev/null +++ b/tests/i/integer-literal-contexts/program.cps @@ -0,0 +1,10 @@ +CONSTANT Minimum = -9223372036854775808 +CONSTANT Maximum = 9223372036854775807 +DECLARE Values : ARRAY[-9223372036854775808:-9223372036854775808] OF INTEGER +Values[Minimum] <- Maximum +CALL Show(Values) +OUTPUT Minimum, ":", Maximum +OUTPUT -0, ":", -1, ":", -(1 + 2) +PROCEDURE Show(BYREF Items : ARRAY[-9223372036854775808:-9223372036854775808] OF INTEGER) + OUTPUT Items[-9223372036854775808] +ENDPROCEDURE diff --git a/tests/i/loops/notes.md b/tests/i/loops/notes.md index 6979d92..bdd322a 100644 --- a/tests/i/loops/notes.md +++ b/tests/i/loops/notes.md @@ -6,4 +6,4 @@ Basis: CIE pp. 20-21. Trace: FOR contributions are 0, 2, and 3+1-1=3. WHILE contributes 0+1+2=3. REPEAT contributes 10 once, then counts to 3. Total=18. -The project requires DO after WHILE; this fixture uses that spelling to exercise runtime semantics. The separate standard-while fixture tests the CIE spelling. +WHILE headers use the standard CIE spelling without DO. The separate standard-while fixture also checks this syntax. diff --git a/tests/i/loops/program.cps b/tests/i/loops/program.cps index a858e6c..d30b463 100644 --- a/tests/i/loops/program.cps +++ b/tests/i/loops/program.cps @@ -10,11 +10,11 @@ NEXT I FOR I <- 3 TO -1 STEP -2 Total <- Total + I NEXT I -WHILE FALSE DO +WHILE FALSE Total <- Total + 1000 ENDWHILE I <- 0 -WHILE I < 3 DO +WHILE I < 3 Total <- Total + I I <- I + 1 ENDWHILE diff --git a/tests/i/text-files/notes.md b/tests/i/text-files/notes.md index cc62aea..1b2ecad 100644 --- a/tests/i/text-files/notes.md +++ b/tests/i/text-files/notes.md @@ -6,4 +6,4 @@ Basis: CIE p. 25; CRLF input handling follows project text-file semantics. Trace: Input contains three lines, including an empty line and a last line without terminator. Empty-file EOF is true. WRITE replaces old contents; APPEND adds last after the existing blank line. -The project requires DO after WHILE; this fixture uses that spelling to exercise runtime semantics. The separate standard-while fixture tests the CIE spelling. +WHILE headers use the standard CIE spelling without DO. The separate standard-while fixture also checks this syntax. diff --git a/tests/i/text-files/program.cps b/tests/i/text-files/program.cps index 1d6d5bb..42632fd 100644 --- a/tests/i/text-files/program.cps +++ b/tests/i/text-files/program.cps @@ -2,7 +2,7 @@ DECLARE Line : STRING DECLARE Count : INTEGER OPENFILE "input.txt" FOR READ Count <- 0 -WHILE NOT EOF("input.txt") DO +WHILE NOT EOF("input.txt") READFILE "input.txt", Line OUTPUT "[", Line, "]" Count <- Count + 1 @@ -19,7 +19,7 @@ OPENFILE "result.txt" FOR APPEND WRITEFILE "result.txt", "last" CLOSEFILE "result.txt" OPENFILE "result.txt" FOR READ -WHILE NOT EOF("result.txt") DO +WHILE NOT EOF("result.txt") READFILE "result.txt", Line OUTPUT "[", Line, "]" ENDWHILE diff --git a/tests/ii/files/notes.md b/tests/ii/files/notes.md index 02831fa..a13a748 100644 --- a/tests/ii/files/notes.md +++ b/tests/ii/files/notes.md @@ -1,5 +1,5 @@ # files -CIE p. 25. Both programs exercise actual text files, including truncation, append, empty lines and EOF. Separate working directories avoid shared files. Pseudocode uses the project-required WHILE DO spelling; i/standard-while separately checks the standard spelling. +CIE p. 25. Both programs exercise actual text files, including truncation, append, empty lines and EOF. Separate working directories avoid shared files. Pseudocode uses standard WHILE headers without DO; i/standard-while separately checks this syntax. Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/files/program.cps b/tests/ii/files/program.cps index 9bc4aad..f825118 100644 --- a/tests/ii/files/program.cps +++ b/tests/ii/files/program.cps @@ -19,7 +19,7 @@ FOR Index <- 1 TO Count OPENFILE "roundtrip.txt" FOR READ Combined <- "" Lines <- 0 - WHILE NOT EOF("roundtrip.txt") DO + WHILE NOT EOF("roundtrip.txt") READFILE "roundtrip.txt", Line Combined <- Combined & "[" & Line & "]" Lines <- Lines + 1 diff --git a/tests/iii/class-private-case/case.json b/tests/iii/class-private-case/case.json new file mode 100644 index 0000000..6d425cd --- /dev/null +++ b/tests/iii/class-private-case/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Cannot\\ access\\ private\\ field\\ hIDDEN\\ of\\ type\\ Box", + "line": 5 + } + ] +} diff --git a/tests/iii/class-private-case/notes.md b/tests/iii/class-private-case/notes.md new file mode 100644 index 0000000..0eff0da --- /dev/null +++ b/tests/iii/class-private-case/notes.md @@ -0,0 +1,3 @@ +# class-private-case + +The compiler must reject this program: Cannot access private field hIDDEN of type Box. The diagnostic must identify line 5; compilation must preserve existing output. diff --git a/tests/iii/class-private-case/program.cps b/tests/iii/class-private-case/program.cps new file mode 100644 index 0000000..9ba8e02 --- /dev/null +++ b/tests/iii/class-private-case/program.cps @@ -0,0 +1,5 @@ +CLASS Box + PRIVATE Hidden : INTEGER +ENDCLASS +DECLARE Item : Box +OUTPUT item.hIDDEN diff --git a/tests/iii/diagnostic-call-after-arguments/case.json b/tests/iii/diagnostic-call-after-arguments/case.json new file mode 100644 index 0000000..2b00e17 --- /dev/null +++ b/tests/iii/diagnostic-call-after-arguments/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "BYREF\\ argument\\ to\\ Change\\ must\\ be\\ a\\ variable", + "line": 3 + } + ] +} diff --git a/tests/iii/diagnostic-call-after-arguments/notes.md b/tests/iii/diagnostic-call-after-arguments/notes.md new file mode 100644 index 0000000..9011f61 --- /dev/null +++ b/tests/iii/diagnostic-call-after-arguments/notes.md @@ -0,0 +1,3 @@ +# diagnostic-call-after-arguments + +The compiler must reject this program: BYREF argument to Change must be a variable. The diagnostic must identify line 3; compilation must preserve existing output. diff --git a/tests/iii/diagnostic-call-after-arguments/program.cps b/tests/iii/diagnostic-call-after-arguments/program.cps new file mode 100644 index 0000000..743a51c --- /dev/null +++ b/tests/iii/diagnostic-call-after-arguments/program.cps @@ -0,0 +1,5 @@ +PROCEDURE Change(BYVAL Text : STRING, BYREF Value : INTEGER) +ENDPROCEDURE +CALL Change( + LEFT("text", 2), + 1) diff --git a/tests/iii/diagnostic-multiline-name/case.json b/tests/iii/diagnostic-multiline-name/case.json new file mode 100644 index 0000000..e90b979 --- /dev/null +++ b/tests/iii/diagnostic-multiline-name/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Unknown\\ variable\\ name\\ Missing", + "line": 2 + } + ] +} diff --git a/tests/iii/diagnostic-multiline-name/notes.md b/tests/iii/diagnostic-multiline-name/notes.md new file mode 100644 index 0000000..28534c3 --- /dev/null +++ b/tests/iii/diagnostic-multiline-name/notes.md @@ -0,0 +1,3 @@ +# diagnostic-multiline-name + +The compiler must reject this program: Unknown variable name Missing. The diagnostic must identify line 2; compilation must preserve existing output. diff --git a/tests/iii/diagnostic-multiline-name/program.cps b/tests/iii/diagnostic-multiline-name/program.cps new file mode 100644 index 0000000..529dd9f --- /dev/null +++ b/tests/iii/diagnostic-multiline-name/program.cps @@ -0,0 +1,2 @@ +OUTPUT 1 + + Missing diff --git a/tests/iii/diagnostic-nested-call/case.json b/tests/iii/diagnostic-nested-call/case.json new file mode 100644 index 0000000..a00f88b --- /dev/null +++ b/tests/iii/diagnostic-nested-call/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "MID\\ expects\\ 3\\ args", + "line": 2 + } + ] +} diff --git a/tests/iii/diagnostic-nested-call/notes.md b/tests/iii/diagnostic-nested-call/notes.md new file mode 100644 index 0000000..252dec7 --- /dev/null +++ b/tests/iii/diagnostic-nested-call/notes.md @@ -0,0 +1,3 @@ +# diagnostic-nested-call + +The compiler must reject this program: MID expects 3 args. The diagnostic must identify line 2; compilation must preserve existing output. diff --git a/tests/iii/diagnostic-nested-call/program.cps b/tests/iii/diagnostic-nested-call/program.cps new file mode 100644 index 0000000..0987bed --- /dev/null +++ b/tests/iii/diagnostic-nested-call/program.cps @@ -0,0 +1,3 @@ +OUTPUT LENGTH( + MID("text", 1) +) diff --git a/tests/iii/identifier-array-collision/case.json b/tests/iii/identifier-array-collision/case.json new file mode 100644 index 0000000..4bb0e66 --- /dev/null +++ b/tests/iii/identifier-array-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Declaration\\ of\\ values\\ conflicts\\ with\\ an\\ existing\\ declaration\\ in\\ this\\ scope", + "line": 2 + } + ] +} diff --git a/tests/iii/identifier-array-collision/notes.md b/tests/iii/identifier-array-collision/notes.md new file mode 100644 index 0000000..e11a241 --- /dev/null +++ b/tests/iii/identifier-array-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-array-collision + +Names differing only in ASCII case cannot introduce a separate symbol. Require Declaration of values conflicts with an existing declaration in this scope at line 2, a failed compilation, and preservation of any existing output. diff --git a/tests/iii/identifier-array-collision/program.cps b/tests/iii/identifier-array-collision/program.cps new file mode 100644 index 0000000..94a8083 --- /dev/null +++ b/tests/iii/identifier-array-collision/program.cps @@ -0,0 +1,2 @@ +DECLARE Values : ARRAY[1:2] OF INTEGER +DECLARE values : INTEGER diff --git a/tests/iii/identifier-builtin-collision/case.json b/tests/iii/identifier-builtin-collision/case.json new file mode 100644 index 0000000..df7c2b6 --- /dev/null +++ b/tests/iii/identifier-builtin-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "'lENGTH'\\ is\\ a\\ built\\-in\\ function\\ and\\ cannot\\ be\\ redefined", + "line": 1 + } + ] +} diff --git a/tests/iii/identifier-builtin-collision/notes.md b/tests/iii/identifier-builtin-collision/notes.md new file mode 100644 index 0000000..85b97fe --- /dev/null +++ b/tests/iii/identifier-builtin-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-builtin-collision + +Names differing only in ASCII case cannot introduce a separate symbol. Require 'lENGTH' is a built-in function and cannot be redefined at line 1, a failed compilation, and preservation of any existing output. diff --git a/tests/iii/identifier-builtin-collision/program.cps b/tests/iii/identifier-builtin-collision/program.cps new file mode 100644 index 0000000..647aab9 --- /dev/null +++ b/tests/iii/identifier-builtin-collision/program.cps @@ -0,0 +1,3 @@ +FUNCTION lENGTH() RETURNS INTEGER + RETURN 1 +ENDFUNCTION diff --git a/tests/iii/identifier-constant-collision/case.json b/tests/iii/identifier-constant-collision/case.json new file mode 100644 index 0000000..bdb0040 --- /dev/null +++ b/tests/iii/identifier-constant-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Declaration\\ of\\ lIMIT\\ conflicts\\ with\\ an\\ existing\\ declaration\\ in\\ this\\ scope", + "line": 2 + } + ] +} diff --git a/tests/iii/identifier-constant-collision/notes.md b/tests/iii/identifier-constant-collision/notes.md new file mode 100644 index 0000000..d5ea85e --- /dev/null +++ b/tests/iii/identifier-constant-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-constant-collision + +Names differing only in ASCII case cannot introduce a separate symbol. Require Declaration of lIMIT conflicts with an existing declaration in this scope at line 2, a failed compilation, and preservation of any existing output. diff --git a/tests/iii/identifier-constant-collision/program.cps b/tests/iii/identifier-constant-collision/program.cps new file mode 100644 index 0000000..c202f59 --- /dev/null +++ b/tests/iii/identifier-constant-collision/program.cps @@ -0,0 +1,2 @@ +CONSTANT Limit = 3 +DECLARE lIMIT : INTEGER diff --git a/tests/iii/identifier-enum-collision/case.json b/tests/iii/identifier-enum-collision/case.json new file mode 100644 index 0000000..53f0cdc --- /dev/null +++ b/tests/iii/identifier-enum-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Duplicate\\ value\\ 'spring'\\ in\\ enum\\ Season", + "line": 1 + } + ] +} diff --git a/tests/iii/identifier-enum-collision/notes.md b/tests/iii/identifier-enum-collision/notes.md new file mode 100644 index 0000000..0fdb4de --- /dev/null +++ b/tests/iii/identifier-enum-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-enum-collision + +The compiler must reject this program: Duplicate value 'spring' in enum Season. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/identifier-enum-collision/program.cps b/tests/iii/identifier-enum-collision/program.cps new file mode 100644 index 0000000..bd80acc --- /dev/null +++ b/tests/iii/identifier-enum-collision/program.cps @@ -0,0 +1 @@ +TYPE Season = (Spring, spring) diff --git a/tests/iii/identifier-field-collision/case.json b/tests/iii/identifier-field-collision/case.json new file mode 100644 index 0000000..cb164c7 --- /dev/null +++ b/tests/iii/identifier-field-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Duplicate\\ field\\ 'value'\\ in\\ record\\ Box", + "line": 1 + } + ] +} diff --git a/tests/iii/identifier-field-collision/notes.md b/tests/iii/identifier-field-collision/notes.md new file mode 100644 index 0000000..d88ddfb --- /dev/null +++ b/tests/iii/identifier-field-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-field-collision + +The compiler must reject this program: Duplicate field 'value' in record Box. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/identifier-field-collision/program.cps b/tests/iii/identifier-field-collision/program.cps new file mode 100644 index 0000000..56597bf --- /dev/null +++ b/tests/iii/identifier-field-collision/program.cps @@ -0,0 +1,4 @@ +CLASS Box + Value : INTEGER + value : INTEGER +ENDCLASS diff --git a/tests/iii/identifier-function-collision/case.json b/tests/iii/identifier-function-collision/case.json new file mode 100644 index 0000000..7743273 --- /dev/null +++ b/tests/iii/identifier-function-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Function\\ value\\ cannot\\ be\\ redefined\\.", + "line": 4 + } + ] +} diff --git a/tests/iii/identifier-function-collision/notes.md b/tests/iii/identifier-function-collision/notes.md new file mode 100644 index 0000000..795b86d --- /dev/null +++ b/tests/iii/identifier-function-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-function-collision + +Names differing only in ASCII case cannot introduce a separate symbol. Require Function value cannot be redefined. at line 4, a failed compilation, and preservation of any existing output. diff --git a/tests/iii/identifier-function-collision/program.cps b/tests/iii/identifier-function-collision/program.cps new file mode 100644 index 0000000..cc7f6de --- /dev/null +++ b/tests/iii/identifier-function-collision/program.cps @@ -0,0 +1,6 @@ +FUNCTION Value() RETURNS INTEGER + RETURN 1 +ENDFUNCTION +FUNCTION value() RETURNS INTEGER + RETURN 2 +ENDFUNCTION diff --git a/tests/iii/identifier-parameter-collision/case.json b/tests/iii/identifier-parameter-collision/case.json new file mode 100644 index 0000000..17daa9a --- /dev/null +++ b/tests/iii/identifier-parameter-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Duplicate\\ parameter\\ name\\ 'value'", + "line": 1 + } + ] +} diff --git a/tests/iii/identifier-parameter-collision/notes.md b/tests/iii/identifier-parameter-collision/notes.md new file mode 100644 index 0000000..c752a2f --- /dev/null +++ b/tests/iii/identifier-parameter-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-parameter-collision + +The compiler must reject this program: Duplicate parameter name value. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/identifier-parameter-collision/program.cps b/tests/iii/identifier-parameter-collision/program.cps new file mode 100644 index 0000000..2cb5af6 --- /dev/null +++ b/tests/iii/identifier-parameter-collision/program.cps @@ -0,0 +1,2 @@ +PROCEDURE Bad(Value : INTEGER, value : INTEGER) +ENDPROCEDURE diff --git a/tests/iii/identifier-runtime-collision/case.json b/tests/iii/identifier-runtime-collision/case.json new file mode 100644 index 0000000..aa83876 --- /dev/null +++ b/tests/iii/identifier-runtime-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "'Printf'\\ is\\ a\\ reserved\\ runtime\\ symbol", + "line": 1 + } + ] +} diff --git a/tests/iii/identifier-runtime-collision/notes.md b/tests/iii/identifier-runtime-collision/notes.md new file mode 100644 index 0000000..ec125eb --- /dev/null +++ b/tests/iii/identifier-runtime-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-runtime-collision + +Names differing only in ASCII case cannot introduce a separate symbol. Require 'Printf' is a reserved runtime symbol at line 1, a failed compilation, and preservation of any existing output. diff --git a/tests/iii/identifier-runtime-collision/program.cps b/tests/iii/identifier-runtime-collision/program.cps new file mode 100644 index 0000000..6e526fd --- /dev/null +++ b/tests/iii/identifier-runtime-collision/program.cps @@ -0,0 +1,2 @@ +PROCEDURE Printf() +ENDPROCEDURE diff --git a/tests/iii/identifier-type-collision/case.json b/tests/iii/identifier-type-collision/case.json new file mode 100644 index 0000000..93f3914 --- /dev/null +++ b/tests/iii/identifier-type-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Type\\ first\\ is\\ already\\ defined", + "line": 2 + } + ] +} diff --git a/tests/iii/identifier-type-collision/notes.md b/tests/iii/identifier-type-collision/notes.md new file mode 100644 index 0000000..3483ceb --- /dev/null +++ b/tests/iii/identifier-type-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-type-collision + +The compiler must reject this program: Type first is already defined. The diagnostic must identify line 2; compilation must preserve existing output. diff --git a/tests/iii/identifier-type-collision/program.cps b/tests/iii/identifier-type-collision/program.cps new file mode 100644 index 0000000..d1f475f --- /dev/null +++ b/tests/iii/identifier-type-collision/program.cps @@ -0,0 +1,2 @@ +TYPE First = ^INTEGER +TYPE first = ^INTEGER diff --git a/tests/iii/identifier-variable-collision/case.json b/tests/iii/identifier-variable-collision/case.json new file mode 100644 index 0000000..b50d602 --- /dev/null +++ b/tests/iii/identifier-variable-collision/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Declaration\\ of\\ counter\\ conflicts\\ with\\ an\\ existing\\ declaration\\ in\\ this\\ scope", + "line": 2 + } + ] +} diff --git a/tests/iii/identifier-variable-collision/notes.md b/tests/iii/identifier-variable-collision/notes.md new file mode 100644 index 0000000..a25de06 --- /dev/null +++ b/tests/iii/identifier-variable-collision/notes.md @@ -0,0 +1,3 @@ +# identifier-variable-collision + +The compiler must reject this program: Declaration of counter conflicts with an existing declaration in this scope. The diagnostic must identify line 2; compilation must preserve existing output. diff --git a/tests/iii/identifier-variable-collision/program.cps b/tests/iii/identifier-variable-collision/program.cps new file mode 100644 index 0000000..fc0d743 --- /dev/null +++ b/tests/iii/identifier-variable-collision/program.cps @@ -0,0 +1,2 @@ +DECLARE Counter : INTEGER +DECLARE counter : INTEGER diff --git a/tests/iii/integer-bound-overflow/case.json b/tests/iii/integer-bound-overflow/case.json new file mode 100644 index 0000000..6421b0f --- /dev/null +++ b/tests/iii/integer-bound-overflow/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Integer\\ literal\\ is\\ outside\\ the\\ i64\\ range", + "line": 1 + } + ] +} diff --git a/tests/iii/integer-bound-overflow/notes.md b/tests/iii/integer-bound-overflow/notes.md new file mode 100644 index 0000000..1c3171a --- /dev/null +++ b/tests/iii/integer-bound-overflow/notes.md @@ -0,0 +1,3 @@ +# integer-bound-overflow + +The compiler must reject this program: Integer literal is outside the i64 range. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/integer-bound-overflow/program.cps b/tests/iii/integer-bound-overflow/program.cps new file mode 100644 index 0000000..6bdd156 --- /dev/null +++ b/tests/iii/integer-bound-overflow/program.cps @@ -0,0 +1,2 @@ +PROCEDURE Bad(Values : ARRAY[0:9223372036854775808] OF INTEGER) +ENDPROCEDURE diff --git a/tests/iii/integer-constant-overflow/case.json b/tests/iii/integer-constant-overflow/case.json new file mode 100644 index 0000000..6421b0f --- /dev/null +++ b/tests/iii/integer-constant-overflow/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Integer\\ literal\\ is\\ outside\\ the\\ i64\\ range", + "line": 1 + } + ] +} diff --git a/tests/iii/integer-constant-overflow/notes.md b/tests/iii/integer-constant-overflow/notes.md new file mode 100644 index 0000000..b13c24e --- /dev/null +++ b/tests/iii/integer-constant-overflow/notes.md @@ -0,0 +1,3 @@ +# integer-constant-overflow + +The compiler must reject this program: Integer literal is outside the i64 range. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/integer-constant-overflow/program.cps b/tests/iii/integer-constant-overflow/program.cps new file mode 100644 index 0000000..849c3eb --- /dev/null +++ b/tests/iii/integer-constant-overflow/program.cps @@ -0,0 +1 @@ +CONSTANT TooBig = 9223372036854775808 diff --git a/tests/iii/integer-huge-overflow/case.json b/tests/iii/integer-huge-overflow/case.json new file mode 100644 index 0000000..6421b0f --- /dev/null +++ b/tests/iii/integer-huge-overflow/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Integer\\ literal\\ is\\ outside\\ the\\ i64\\ range", + "line": 1 + } + ] +} diff --git a/tests/iii/integer-huge-overflow/notes.md b/tests/iii/integer-huge-overflow/notes.md new file mode 100644 index 0000000..acc9bfb --- /dev/null +++ b/tests/iii/integer-huge-overflow/notes.md @@ -0,0 +1,3 @@ +# integer-huge-overflow + +The compiler must reject this program: Integer literal is outside the i64 range. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/integer-huge-overflow/program.cps b/tests/iii/integer-huge-overflow/program.cps new file mode 100644 index 0000000..2e4ba62 --- /dev/null +++ b/tests/iii/integer-huge-overflow/program.cps @@ -0,0 +1 @@ +OUTPUT 9999999999999999999999999999999999999 diff --git a/tests/iii/integer-negative-overflow/case.json b/tests/iii/integer-negative-overflow/case.json new file mode 100644 index 0000000..6421b0f --- /dev/null +++ b/tests/iii/integer-negative-overflow/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Integer\\ literal\\ is\\ outside\\ the\\ i64\\ range", + "line": 1 + } + ] +} diff --git a/tests/iii/integer-negative-overflow/notes.md b/tests/iii/integer-negative-overflow/notes.md new file mode 100644 index 0000000..029a543 --- /dev/null +++ b/tests/iii/integer-negative-overflow/notes.md @@ -0,0 +1,3 @@ +# integer-negative-overflow + +The compiler must reject this program: Integer literal is outside the i64 range. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/integer-negative-overflow/program.cps b/tests/iii/integer-negative-overflow/program.cps new file mode 100644 index 0000000..e208ba6 --- /dev/null +++ b/tests/iii/integer-negative-overflow/program.cps @@ -0,0 +1 @@ +OUTPUT -9223372036854775809 diff --git a/tests/iii/integer-positive-overflow/case.json b/tests/iii/integer-positive-overflow/case.json new file mode 100644 index 0000000..6421b0f --- /dev/null +++ b/tests/iii/integer-positive-overflow/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Integer\\ literal\\ is\\ outside\\ the\\ i64\\ range", + "line": 1 + } + ] +} diff --git a/tests/iii/integer-positive-overflow/notes.md b/tests/iii/integer-positive-overflow/notes.md new file mode 100644 index 0000000..2c17ff4 --- /dev/null +++ b/tests/iii/integer-positive-overflow/notes.md @@ -0,0 +1,3 @@ +# integer-positive-overflow + +The compiler must reject this program: Integer literal is outside the i64 range. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/integer-positive-overflow/program.cps b/tests/iii/integer-positive-overflow/program.cps new file mode 100644 index 0000000..286c0eb --- /dev/null +++ b/tests/iii/integer-positive-overflow/program.cps @@ -0,0 +1 @@ +OUTPUT 9223372036854775808 diff --git a/tests/iii/while-do-rejected/case.json b/tests/iii/while-do-rejected/case.json new file mode 100644 index 0000000..9908d5e --- /dev/null +++ b/tests/iii/while-do-rejected/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Expected\\ '<\\-'\\ after\\ identifier\\ 'DO'", + "line": 1 + } + ] +} diff --git a/tests/iii/while-do-rejected/notes.md b/tests/iii/while-do-rejected/notes.md new file mode 100644 index 0000000..ed669b5 --- /dev/null +++ b/tests/iii/while-do-rejected/notes.md @@ -0,0 +1,3 @@ +# while-do-rejected + +The compiler must reject this program: Expected '<-' after identifier 'DO'. The diagnostic must identify line 1; compilation must preserve existing output. diff --git a/tests/iii/while-do-rejected/program.cps b/tests/iii/while-do-rejected/program.cps new file mode 100644 index 0000000..b733e21 --- /dev/null +++ b/tests/iii/while-do-rejected/program.cps @@ -0,0 +1,3 @@ +WHILE FALSE DO + OUTPUT 1 +ENDWHILE -- cgit v1.3.1-4-g156e