diff options
| -rw-r--r-- | AGENTS/PITFALLS.md | 16 | ||||
| -rw-r--r-- | AGENTS/SUBSYSTEMS.md | 31 | ||||
| -rw-r--r-- | cmake/ManualAliases.cmake | 1 | ||||
| -rw-r--r-- | doc/language/cpsc-language-overview.7.md | 3 | ||||
| -rw-r--r-- | doc/language/function/cpsc-function-free.7.md | 6 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-class.7.md | 8 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-delete.7.md | 57 | ||||
| -rw-r--r-- | plugin/syntaxes/cie-pseudocode.tmLanguage.json | 2 |
8 files changed, 111 insertions, 13 deletions
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index 6ef422b..f2f4f7b 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -95,6 +95,20 @@ test programs. elements, BYREF, this and pointer dereferences receive a value copy from NEW; the temporary allocation leaks. Old address-of pointers retain the previous object. Private access and ordinary argument checks apply to automatic construction too. + DELETE is reserved; a parameterless PROCEDURE DELETE defines an ordinary method. + Explicit calls do not recurse into fields or affect Alive tracking. Keyword DELETE + validates an active allocation start before destroying the object and its fields, + then frees memory and leaves aliases dangling; FREE skips all destruction. + DECLARE storage and shallow BYVAL copies are destroyed in reverse declaration order + at normal function exit or when unwinding leaves the frame. Arrays reverse their + flat element order. Repeated DECLARE destroys its previous incarnation first. + Lifetimes are function-scoped, even for declarations inside blocks. NEW rebinding + keeps automatic destruction attached to original storage; heap objects are never + auto-destroyed. Main objects are destroyed only on normal program end. Alive is + cleared before destructor calls, preventing retries if a destructor throws. + Private DELETE access is checked for automatic destruction too. A destructor + throwing during unwinding propagates and interrupts remaining cleanup rather + than terminating as C++ would. - **Exception extensions.** TRY requires exactly one parameterless CATCH and ENDTRY. Bare THROW raises a new anonymous exception even inside CATCH; there is @@ -122,7 +136,7 @@ SET types (`SET`/`DEFINE` are reserved with dedicated errors), random files (OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — TYPE is done, so these are unblocked now; the RANDOM keyword is reserved and gives a dedicated parse error), DATE, string escape sequences, -array-typed record fields, whole-array RETURN, destructors/DELETE +array-typed record fields, whole-array RETURN and class inheritance (INHERITS/SUPER). 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 standard conflicts and missing diagnostic locations are fixed on diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index 1d13c57..1621f11 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -92,8 +92,22 @@ class parameters; `symbolAddress` loads their current object address for all val lvalue, address-of, receiver and BYREF paths. Field aliases, this and BYREF parameters bind addresses directly. Assignment-only `NewExprAST` uses checked MemoryHandler allocation (at least one byte for method-only classes), zero initialization and -construction, then rebinds reference slots or copies into other destinations. Ordinary assignment always copies. Heap lifetimes -remain unmanaged; destructors, DELETE and INHERITS/SUPER are unsupported. +construction, then rebinds reference slots or copies into other destinations. Ordinary assignment always copies. Heap objects require explicit keyword DELETE; INHERITS/SUPER remain unsupported. + +`ConstructorGen.cc` also owns `typeNeedsDestruction` and recursive `emitDestroy`: +ordinary DELETE method first, then fields in reverse declaration order. Explicit +DELETE method calls remain ordinary calls without field recursion. Keyword DELETE +resolves a class lvalue, validates its allocation start through MemoryHandler before +any destruction, destroys it, then releases it through FREE; aliases remain dangling. +Stack lifetimes use a per-function `StackObjects` list saved/restored around function +emission. DECLARE and shallow BYVAL copies share registration (the copy point is +marked), including arrays and records with class fields. Each record retains the +original address, an entry-initialized Alive flag and, for arrays, a runtime element +count in an entry slot. Array buffer addresses also live in entry slots so deferred +cleanup never references branch-local bound SSA values. Repeated DECLARE destroys +its previous live incarnation first. Alive becomes true only after construction or +copy completes and is cleared before destruction. Rebinding a reference slot with +NEW does not change the original address tracked for automatic destruction. **Runtime checks** (`RuntimeCheck`): array bounds, file misuse, enum ranges, null dereferences and array-parameter bounds contracts are checked at their emit sites. @@ -109,7 +123,16 @@ an entry-block alloca for its exception pointer. A potentially throwing call wit a handler or active catch emits an `invoke` and a landing pad that finishes catches being left and transfers to the nearest handler, or resumes unwinding when only cleanup is needed. CATCH uses `__cxa_begin_catch` and removes its own TRY target before emitting its body; -normal exit and RETURN call `__cxa_end_catch`. Bare THROW always creates a new +normal exit and RETURN call `__cxa_end_catch`. User functions enable cleanup +support before their first call: loop back edges can make later DECLARE sites alive +at earlier invokes. Without a local handler, cleanup landing pads end active catches +and resume unwinding. Returns (before catch cleanup) and cleanup resumes record exit +points with their handler/catch state; CodeGen fills reverse, Alive-guarded destruction +only when the function is fully emitted. Normal-exit destructor calls use that saved +state and may create further cleanup pads. During unwind filling, a reentrancy guard +emits plain calls; a throwing destructor propagates and interrupts remaining cleanup +instead of terminating. Main records only its normal return, leaving its final +uncaught-exception handler free of automatic object destruction. Bare THROW always creates a new anonymous integer exception, using `_ZTIi` internally without exposing a value. Generated main owns the final catch target, prints the generic uncaught message, and returns 1. Native binaries require the C++ runtime; EmbeddedRunner defines @@ -137,4 +160,4 @@ malloc/realloc buffer per line and strips one trailing `\r`; fopen modes are tex nothing is declared or emitted eagerly. Helper bodies are built with the *shared* Builder (RuntimeCheck emits through the same one), saving/restoring the insert block. -**Explicit memory** (`MemoryHandler`): lazily emits internal allocation/find/release/resize helpers and a linked list of live allocation start addresses. Explicit MALLOC/REALLOC and keyword NEW allocations enter the list; STRING/ARRAY internals retain their existing ownership. FREE validates non-null addresses, unlinks and frees the tracking node, releases the block and returns the original address. Zero-byte allocation returns null. REALLOC validates non-null addresses before its zero-size release path; null behaves as MALLOC. Positive-size resizing updates the node only after success, leaving the old block and record intact on failure. Node allocation precedes the user allocation; if the latter fails, its node is freed before the diagnostic and anonymous throw. Helpers use RuntimeCheck and ExceptionHandler, including source-line arguments and per-function unwind preparation. Sizes accept INTEGER and C integers and convert to native size_t. Address reuse can make stale aliases indistinguishable from current allocations; dereferences are not checked against this list. +**Explicit memory** (`MemoryHandler`): lazily emits internal allocation/find/release/resize helpers and a linked list of live allocation start addresses. Explicit MALLOC/REALLOC and keyword NEW allocations enter the list; STRING/ARRAY internals retain their existing ownership. `emitValidate` always calls the find helper, including for null, so keyword DELETE validates before running destructors. FREE validates non-null addresses, unlinks and frees the tracking node, releases the block and returns the original address. Zero-byte allocation returns null. REALLOC validates non-null addresses before its zero-size release path; null behaves as MALLOC. Positive-size resizing updates the node only after success, leaving the old block and record intact on failure. Node allocation precedes the user allocation; if the latter fails, its node is freed before the diagnostic and anonymous throw. Helpers use RuntimeCheck and ExceptionHandler, including source-line arguments and per-function unwind preparation. Sizes accept INTEGER and C integers and convert to native size_t. Address reuse can make stale aliases indistinguishable from current allocations; dereferences are not checked against this list. diff --git a/cmake/ManualAliases.cmake b/cmake/ManualAliases.cmake index e9418d8..452f8eb 100644 --- a/cmake/ManualAliases.cmake +++ b/cmake/ManualAliases.cmake @@ -31,4 +31,5 @@ set(CPSC_MANUAL_ALIASES "cpsc-keyword-public:cpsc-keyword-class" "cpsc-keyword-private:cpsc-keyword-class" "cpsc-keyword-endclass:cpsc-keyword-class" + "cpsc-keyword-new:cpsc-keyword-class" ) diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index 21fbaff..cb31d93 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -41,6 +41,7 @@ The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions - [cpsc-keyword-closefile(7)](keyword/cpsc-keyword-closefile.7.md): close a text file. - [cpsc-keyword-type(7)](keyword/cpsc-keyword-type.7.md): define enumerations, pointers, and records. - [cpsc-keyword-c_type(7)](keyword/cpsc-keyword-c_type.7.md): import native C types and explicitly cast values. +- [cpsc-keyword-delete(7)](keyword/cpsc-keyword-delete.7.md): destroy objects and release heap allocations. - [cpsc-keyword-class(7)](keyword/cpsc-keyword-class.7.md): define a class with fields and methods. - [cpsc-keyword-and(7)](keyword/cpsc-keyword-and.7.md): combine conditions with logical AND. - [cpsc-keyword-or(7)](keyword/cpsc-keyword-or.7.md): combine conditions with logical OR. @@ -70,7 +71,7 @@ Closing and auxiliary keywords are documented with their main constructs: THEN/E # UNSUPPORTED FEATURES -DATE, SET types and DEFINE, random-file operations (RANDOM/SEEK/GETRECORD/PUTRECORD), INT, RAND, string escapes, whole-array assignment or return, array fields, and class constructors/destructors/inheritance (NEW/INHERITS/SUPER). +DATE, SET types and DEFINE, random-file operations (RANDOM/SEEK/GETRECORD/PUTRECORD), INT, RAND, string escapes, whole-array assignment or return, array fields, and class inheritance (INHERITS/SUPER). The individual pages note relevant differences from the Cambridge 2026 pseudocode guide. This manual describes the compiler's current support. diff --git a/doc/language/function/cpsc-function-free.7.md b/doc/language/function/cpsc-function-free.7.md index e2c8f12..61a3558 100644 --- a/doc/language/function/cpsc-function-free.7.md +++ b/doc/language/function/cpsc-function-free.7.md @@ -12,11 +12,11 @@ FREE(pointer) # DESCRIPTION -FREE is a compiler extension. Its argument and return type are C `void*`. Explicitly cast other pointer types to a `void*` alias before passing them. A null pointer is accepted and returned. A non-null pointer must be the start of an active explicit MALLOC or REALLOC allocation; internal STRING/ARRAY storage, stack addresses, interior addresses and already released allocations are rejected with a source-line diagnostic on stdout and an anonymous exception. +FREE is a compiler extension. Its argument and return type are C `void*`. Explicitly cast other pointer types to a `void*` alias before passing them. A null pointer is accepted and returned. A non-null pointer must be the start of an active explicit MALLOC, REALLOC or keyword NEW allocation; internal STRING/ARRAY storage, stack addresses, interior addresses and already released allocations are rejected with a source-line diagnostic on stdout and an anonymous exception. FREE returns its input address after releasing the allocation. It does not clear the caller's variable. The returned address and all aliases are invalid for memory access. Calling FREE again on that address throws unless a subsequent allocation has reused the address. Tracking cannot distinguish stale aliases once an address is reused. -The result can be discarded with a standalone call. No user destructor is called. +The result can be discarded with a standalone call. No user destructor is called. Use keyword DELETE on a class object to destroy it and its fields before releasing its allocation. # EXAMPLE @@ -29,4 +29,4 @@ FREE(Memory) # SEE ALSO -[cpsc-function-malloc(7)](cpsc-function-malloc.7.md), [cpsc-function-realloc(7)](cpsc-function-realloc.7.md), [cpsc-keyword-function(7)](../keyword/cpsc-keyword-function.7.md), [cpsc-keyword-try(7)](../keyword/cpsc-keyword-try.7.md). +[cpsc-keyword-delete(7)](../keyword/cpsc-keyword-delete.7.md), [cpsc-function-malloc(7)](cpsc-function-malloc.7.md), [cpsc-function-realloc(7)](cpsc-function-realloc.7.md), [cpsc-keyword-function(7)](../keyword/cpsc-keyword-function.7.md), [cpsc-keyword-try(7)](../keyword/cpsc-keyword-try.7.md). diff --git a/doc/language/keyword/cpsc-keyword-class.7.md b/doc/language/keyword/cpsc-keyword-class.7.md index f0d2e12..a58e8a8 100644 --- a/doc/language/keyword/cpsc-keyword-class.7.md +++ b/doc/language/keyword/cpsc-keyword-class.7.md @@ -38,13 +38,15 @@ Classes retain record value semantics: zero initialization, whole-value copying, A constructor is a PROCEDURE named NEW, following guide section 10.2. It may be PUBLIC or PRIVATE; FUNCTION NEW and global PROCEDURE NEW are errors. Member names are case insensitive, so New and new also name the constructor. Only one NEW is allowed per class. Explicit `Object.NEW(arguments)`, `CALL Object.NEW(arguments)` and `this.NEW(arguments)` behave like ordinary member calls and may be repeated; they do not zero the object or reconstruct its fields. -`DECLARE Object : ClassName`, `DECLARE Object : ClassName()` and `DECLARE Object : ClassName(arguments)` zero the object's storage, construct its fields recursively in declaration order, then call its NEW procedure. These automatic DECLARE, field and array calls are compiler extensions. Array elements and class-valued fields in records or classes use parameterless construction, before the enclosing constructor runs. A required constructor argument cannot be omitted; supplying arguments to a class without NEW is an error. Empty parentheses are allowed for a class without NEW. Constructor arguments after ARRAY OF are unsupported. A declaration executed again, such as inside a loop, zeroes and constructs its original storage again. +`DECLARE Object : ClassName`, `DECLARE Object : ClassName()` and `DECLARE Object : ClassName(arguments)` zero the object's storage, construct its fields recursively in declaration order, then call its NEW procedure. These automatic DECLARE, field and array calls are compiler extensions. Array elements and class-valued fields in records or classes use parameterless construction, before the enclosing constructor runs. A required constructor argument cannot be omitted; supplying arguments to a class without NEW is an error. Empty parentheses are allowed for a class without NEW. Constructor arguments after ARRAY OF are unsupported. A declaration executed again, such as inside a loop, first destroys its previous live incarnation, then zeroes and constructs its original storage again. The keyword NEW is allowed only as the complete right side of assignment: `Target <- NEW ClassName`, `Target <- NEW ClassName()` or `Target <- NEW ClassName(arguments)`. Target must have exactly the named class type. NEW uses the checked MALLOC allocation path, zeroes storage and constructs fields before calling NEW. A method-only class receives at least one byte of heap storage so its object address is non-null. Allocation failure reports a runtime error and throws; constructor exceptions also propagate to TRY/CATCH. Private constructor access and argument checks follow ordinary member-call rules. When Target is a declared class variable, including a function local or BYVAL parameter, NEW rebinds that variable to the heap object. No `^` dereference is needed to access it. Address-of, BYREF calls and method calls use its current object. Pointers taken before rebinding continue to refer to the previous object. Ordinary `A <- B` remains a value copy into A's current object and does not rebind A. BYVAL parameters copy the caller's value and can rebind independently. -For an array element, field (including a bare field inside a method), BYREF parameter, this, or pointer dereference, NEW constructs a heap object and copies its value into the existing target. That temporary allocation is retained. There is no automatic reclamation, destructor or DELETE support; INHERITS, SUPER and array fields remain unsupported. +For an array element, field (including a bare field inside a method), BYREF parameter, this, or pointer dereference, NEW constructs a heap object and copies its value into the existing target. That temporary allocation is retained. INHERITS, SUPER and array fields remain unsupported. + +A parameterless PROCEDURE DELETE defines a destructor. Explicit DELETE method calls are ordinary calls; keyword DELETE validates a heap object, destroys it and its fields, then frees the allocation. DECLARE storage and BYVAL copies are automatically destroyed in reverse order at normal function exit and during unwinding; main-level objects are destroyed only at normal program end. NEW rebinding leaves automatic destruction attached to the original storage. See [cpsc-keyword-delete(7)](cpsc-keyword-delete.7.md) for lifetime and access rules. # EXAMPLE @@ -65,4 +67,4 @@ OUTPUT Item.Get() # SEE ALSO -[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-type(7)](cpsc-keyword-type.7.md), [cpsc-keyword-function(7)](cpsc-keyword-function.7.md), [cpsc-keyword-procedure(7)](cpsc-keyword-procedure.7.md), [cpsc-keyword-call(7)](cpsc-keyword-call.7.md). +[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-delete(7)](cpsc-keyword-delete.7.md), [cpsc-keyword-type(7)](cpsc-keyword-type.7.md), [cpsc-keyword-function(7)](cpsc-keyword-function.7.md), [cpsc-keyword-procedure(7)](cpsc-keyword-procedure.7.md), [cpsc-keyword-call(7)](cpsc-keyword-call.7.md). diff --git a/doc/language/keyword/cpsc-keyword-delete.7.md b/doc/language/keyword/cpsc-keyword-delete.7.md new file mode 100644 index 0000000..6ee6284 --- /dev/null +++ b/doc/language/keyword/cpsc-keyword-delete.7.md @@ -0,0 +1,57 @@ +% CPSC-KEYWORD-DELETE(7) + +# NAME + +cpsc-keyword-delete - destroy objects and release heap allocations + +# SYNOPSIS + +```text +CLASS ClassName + PROCEDURE DELETE() + statements + ENDPROCEDURE +ENDCLASS +DELETE Object +DELETE Pointer^ +DELETE Array[Index] +DELETE this +Object.DELETE() +CALL Object.DELETE() +``` + +# DESCRIPTION + +Destructors, keyword DELETE and automatic destruction are compiler extensions; the Cambridge 2026 guide defines NEW constructors but no destructors. A destructor is a parameterless PROCEDURE named DELETE inside a CLASS. It may be PUBLIC or PRIVATE. Member names are case insensitive; only one DELETE is allowed per class. FUNCTION DELETE, global PROCEDURE DELETE and destructor parameters are errors. + +An explicit `Object.DELETE()`, `CALL Object.DELETE()` or `this.DELETE()` is an ordinary method call. It calls only the method, without automatically destroying fields or releasing memory. It does not end the object's automatic lifetime. + +The statement `DELETE Object` requires an addressable class object. It first checks that the address is the start of an active tracked heap allocation, including allocations created by keyword NEW. Stack objects, internal array storage, interior addresses and previously released allocations fail with a source-line runtime diagnostic and an anonymous exception before any destructor runs. Address reuse can make a stale alias indistinguishable from a current allocation. + +After validation, DELETE calls the object's DELETE method if present, then recursively destroys its class-valued fields in reverse declaration order, including through record fields, and releases the allocation through FREE. The variable and all aliases remain dangling. If destruction throws, memory release is not reached. Heap objects are never automatically destroyed. + +Automatic destruction (RAII) applies to original storage created by DECLARE, including main-level declarations, arrays and records containing class values, and to BYVAL copies containing class values. Destruction occurs in reverse declaration order on RETURN, at the end of a function or procedure, and at normal program end. Array elements are destroyed in reverse flat element order. Block exit alone does not destroy these function-scoped lifetimes. A declaration executed again first destroys its previous live incarnation, then zeroes and constructs the original storage again. + +A completed construction or BYVAL copy marks the object alive. Destruction clears this state before calling the method, so a throwing destructor is never retried automatically. BYVAL copies are shallow. NEW rebinding changes the variable's current address, while automatic destruction still targets its original storage; the heap object requires explicit DELETE. + +Exception unwinding destroys live objects in each function frame being left before the caller's CATCH runs. A CATCH inside the same function retains its objects until function exit. An uncaught exception reaching main's final handler skips destruction of main-level objects. If a destructor throws during unwinding, its exception propagates and interrupts the remaining cleanup instead of terminating as C++ would. + +Ordinary method access rules apply to explicit, keyword and automatic destruction. A private DELETE makes destruction outside the owning class a compile error. + +# EXAMPLE + +```text +CLASS Item + PROCEDURE DELETE() + OUTPUT "destroyed" + ENDPROCEDURE +ENDCLASS +DECLARE Object : Item +Object <- NEW Item() +DELETE Object +// Normal program end also destroys Object's original stack storage. +``` + +# SEE ALSO + +[cpsc-keyword-class(7)](cpsc-keyword-class.7.md), [cpsc-keyword-declare(7)](cpsc-keyword-declare.7.md), [cpsc-keyword-try(7)](cpsc-keyword-try.7.md), [cpsc-function-free(7)](../function/cpsc-function-free.7.md). diff --git a/plugin/syntaxes/cie-pseudocode.tmLanguage.json b/plugin/syntaxes/cie-pseudocode.tmLanguage.json index 6cdd586..7416518 100644 --- a/plugin/syntaxes/cie-pseudocode.tmLanguage.json +++ b/plugin/syntaxes/cie-pseudocode.tmLanguage.json @@ -21,7 +21,7 @@ "name": "keyword.control.throw.cpp" }, { - "match": "(?<!\\w)(?:IF|THEN|ELSE|ENDIF|WHILE|DO|ENDWHILE|REPEAT|UNTIL|FOR|TO|STEP|NEXT|RETURN|CALL|NEW)(?!\\w)", + "match": "(?<!\\w)(?:IF|THEN|ELSE|ENDIF|WHILE|DO|ENDWHILE|REPEAT|UNTIL|FOR|TO|STEP|NEXT|RETURN|CALL|NEW|DELETE)(?!\\w)", "name": "keyword.control.cie-pseudocode" }, { |
