Commit graph

34 commits

Author SHA1 Message Date
mzhoot 94ae8c354c Рабочие синонимы 2026-07-04 21:44:54 +03:00
Graeme Geldenhuys 50b8d75e6d fix(parser): enforce mandatory parentheses on statement-position calls (#148)
The "mandatory parentheses on zero-argument calls" rule (language-
rationale.adoc) was enforced in expression position but NOT in statement
position: a bare `Foo;`, `Obj.Method;`, or `Obj.Free;` used as a statement
compiled silently, building a paren-less TProcCall / TMethodCallStmt. Issue
#148 reported `tester.print;` (a unit's global object method call) being
accepted without its mandatory ().

The statement parser now raises the same "requires () for a call" diagnostic
the inherited-call and expression-position paths already use, at the two
fall-through sites:

  * bare unqualified call `Foo` with no '(' (the final ProcCall else-branch);
  * bare `Obj.Method` with no '(' and no further '.' chain.

Field reads, field assignments, indexed writes, and '.'-chains are unaffected
(only a terminating bare reference is rejected). Expression-position calls
were already enforced.

Enforcing the rule required the compiler, RTL, and stdlib to comply first
(self-hosting): swept bare calls in uSemantic (Flush/RepairGenericInstances),
runtime.arc/runtime.exc (_libc_abort), blaise.codegen.native.backend
(FAsm.AppendLine), and a json.writer doc example. The test suite embedded
many bare calls in inline program strings — all updated to carry (); adding
() never changes behaviour since these were always calls.

cp.test.parser.pas gains two parse-error tests (proc and method bare calls);
the old TestProcCall_NoParens, which asserted the bug, is inverted. grammar.ebnf
SubscriptMethodCall made parens mandatory and an example corrected; rationale
notes statement-position enforcement.
2026-06-28 10:15:39 +01:00
Graeme Geldenhuys 80488bf0a4 test(bench): record TList<TObject> scan speedup from ARC elision
After commit ffe09b3 (nil-slot _ClassRelease elision), TList<TObject>
hand-rolled scan x 1000 went 131 ms -> 90 ms (~30% improvement) on
the same hardware as the previous entry.  Smaller wins on sequential
and random Get for the same workload.
2026-05-21 17:28:44 +01:00
Graeme Geldenhuys 07b9b8ddbd test(bench): merge bench_strings + bench_objects into bench_lists
The multi-instance generic miscompile that forced the two-program
split is fixed (b6736d3), so the four workloads now run in a single
program: TStringList, TList<String>, TObjectList, TList<TObject>.

Add a fresh results entry on the post-fix compiler; timings are
within noise of the split run on the same hardware.
2026-05-21 14:37:22 +01:00
Graeme Geldenhuys b6736d3925 fix(semantic): clone method bodies per generic instance
Generic instance method bodies were shared via OwnBody=False, so a
single TBlock AST was annotated by the semantic analyser once for each
instance in turn.  The Resolved* annotations on the shared body ended
up reflecting whichever instance was analysed last, causing call
targets in earlier instances to point at later instances' helpers —
e.g. TList<String>.Add would emit `call $TList_TObject_Grow` and skip
$_StringAddRef on the value parameter.

Add CloneBlock/CloneStmt/CloneExpr in uAST.pas covering all node types
that can appear inside a method body (~26 expr/stmt forms plus block-
level Var/Const/Type/Proc decls).  Annotations are deliberately not
copied — each instance re-runs semantic analysis on its own AST.
Wire the clone into InstantiateGeneric and InstantiateGenericFunction
so each instance owns its body.

Adds TestCodegen_Generic_TwoInstances_MethodCallsResolveToOwnInstance
to lock in: with TBox<Integer> and TBox<String> in one program, the
emitted TBox_Integer_Init body must contain
`call \$TBox_Integer_SetValue` and the TBox_String_Init body must
contain `call \$TBox_String_SetValue`.  Previously both bodies called
the String instance's helper.

Verified: 2107 tests pass; fixpoint clean; the two-instance benchmark
scenario (TList<String> + TList<TObject> in one program) now compiles
and runs correctly.
2026-05-21 14:30:24 +01:00
Graeme Geldenhuys db84dc8b3f test(bench): TStringList vs TList<String>, TObjectList vs TList<TObject>
Micro-benchmark comparing the legacy list types against the generic
list equivalents introduced for self-hosting.  Logs the median of two
runs to tests/bench_lists_results.txt for trend tracking — append a
new dated section after each meaningful runtime or codegen change.

Phases per container: bulk insert (1M), sequential read, random read
(1M), for..in iteration (strings only), and indexed lookup (IndexOf or
hand-rolled scan).  Initial run on AMD Ryzen 7 5800X shows TList<String>
beating TStringList across the board, while TObjectList still beats
TList<TObject> on writes and on IndexOf — the latter by ~8x due to
direct pointer-scan vs. one Get call per element.
2026-05-21 10:53:13 +01:00
Graeme Geldenhuys 5894411a35 chore(license): relicense from BSD-3-Clause to Apache 2.0 + Runtime Library Exception
Adopts the Swift/LLVM model: Apache License 2.0 with the Runtime Library
Exception text used verbatim by the Swift project. SPDX identifier:
Apache-2.0 WITH Swift-exception.

Apache 2.0 brings an explicit patent grant and patent-retaliation clause
that BSD-3 lacks. The Runtime Library Exception ensures binaries
produced by the Blaise compiler do not inherit attribution obligations
from the linked-in RTL.

* LICENSE — full Apache 2.0 + RLE text (replaces the deleted LICENCE).
* NOTICE — project header plus QBE attribution (MIT, vendored).
* docs/language-rationale.adoc — new Project Governance section
  capturing the decision, alternatives considered, and rationale.
* SPDX headers updated across all .pas, .pp, .inc, .c source files,
  build scripts, PasBuild plugins, and the Makefile.
* project.xml license field updated.
* tools/migrate_full.py HEADER template updated so generated
  self-hosting source carries the new licence.
2026-05-03 19:45:29 +01:00
Graeme Geldenhuys 6be78b2a06 feat(plugin): add pasbuild-blaise-compile plugin
Standalone pasbuild plugin (phase: none) that builds the Blaise compiler
using the Blaise bootstrap binary rather than FPC:

  pasbuild blaise-compile

Steps: RTL make+install → emit IR via bootstrap binary → qbe assemble
→ gcc link → compiler/target/blaise

Bootstrap binary resolved from BLAISE_BOOTSTRAP env var or the newest
releases/vX.Y.Z/blaise binary found in the project tree.
2026-05-01 11:18:36 +01:00
Graeme Geldenhuys 2f7f551925 feat(rtl): sysutils.pas (Exception class) and strutils.pas (empty stub)
Steps 5 and 6 of v0.3.0 multi-file self-hosting:

- rtl/src/main/pascal/sysutils.pas: Exception base class with
  Create(AMessage: string) constructor and Message property.
- rtl/src/main/pascal/strutils.pas: empty stub unit satisfying
  the unit loader for `uses StrUtils` without any API calls.

To parse these RTL units, the compiler now recognises `constructor`
and `destructor` as reserved keywords (uLexer.pas, uParser.pas),
treating both as aliases for `procedure` in method declarations.
The constructor nature of a call is determined at the call site, not
from the declaration keyword — matching the existing codegen model.

Tests (cp.test.exceptions.pas):
  - TestSemantic_ExceptionSubclass_CreateAndMessage_OK
  - TestCodegen_ExceptionSubclass_CtorCallWithMessage

Hand source (tests/blaise-compiler.pas) synced with all changes.
Fixpoint verified (stage-2 IR == stage-3 IR).

docs/language-rationale.adoc: Constructor/Destructor Keywords section.
docs/grammar.ebnf: CONSTRUCTOR/DESTRUCTOR terminals; MethodDecl updated.
docs/future-improvements.adoc: Stream I/O, macOS debugging, and
  multi-line string literals sections (cleaned up from draft notes).
2026-04-29 14:43:42 +01:00
Graeme Geldenhuys ef7a98c687 feat: indexed properties, StrToInt64/Int64ToStr built-ins; restore fixpoint
- Indexed properties fully implemented across parser, semantic analyser,
  symbol table, and codegen (read and write, with index type-checking)
- StrToInt64 and Int64ToStr built-ins added (RTL: _StrToInt64, _Int64ToStr)
- Int literal and const codegen use Int64ToStr to avoid int32 truncation
- QbeEscapeString in hand source now uses manual hex arithmetic instead of
  Format('%02x') which Blaise's _StringFormat does not support
- Copy(..., MaxInt) calls replaced with Copy(..., Length(x)) to avoid RTL
  truncation of the 64-bit sentinel through _StringCopy's int32 parameter
- Stage-3 IR is byte-identical to stage-2: fixpoint holds after all changes
2026-04-29 12:18:45 +01:00
Graeme Geldenhuys bc41daddd7 release: open development branch towards v0.3.0 2026-04-28 23:51:51 +01:00
Graeme Geldenhuys 87597605cc release: promote version to 0.2.0
Second self-hosting fixpoint: stage-3 (hand source compiled by stage-2,
itself compiled by stage-1) produces byte-identical IR to stage-2 at
59974 lines.  New since v0.1.0: string subscript, char literals, const
open arrays, array literals, static arrays, Low/High, PChar, @expr
address-of, set types, GetEnvironmentVariable, and the four SysUtils
path functions.  Also fixes _CurrentExceptionMessage (was always empty
due to _PopExcFrame being called before the except body).
2026-04-28 23:30:06 +01:00
Graeme Geldenhuys 8cd346f676 feat: self-hosting fixpoint — stage-3 IR byte-identical to stage-2
Two fixes to reach the fixpoint milestone:

1. RTL: _CurrentExceptionMessage now reads from a dedicated
   g_current_exception thread-local set in _Raise, rather than from
   g_exc_top->exception.  The codegen calls _PopExcFrame() before
   executing the except body, so g_exc_top was already unwound and
   the exception message was always empty.

2. hand source (tests/blaise-compiler.pas): replace the hand-written
   ExtractFileName and ChangeFileExt helpers with the new compiler
   built-ins (step 11).  Add symbol-table registration, semantic
   type-checking, and codegen for all four path functions.

Result: stage-1 (FPC) → stage-2 → stage-3 → stage-4 are all 59974
lines; diff stage3 vs stage4 is empty.  Fixpoint achieved.
2026-04-28 23:11:15 +01:00
Graeme Geldenhuys 2b87f4cdbd release: open development branch towards v0.2.0 2026-04-27 09:26:17 +01:00
Graeme Geldenhuys 87734277b5 release: promote version to 0.1.0
Remove the -alpha suffix now that the compiler has reached self-hosting
fixpoint: stage-3 (hand source compiled by stage-2, itself compiled by
stage-1) produces byte-identical IR to stage-2.  This is the first
official release of the Blaise compiler.
2026-04-27 09:17:23 +01:00
Graeme Geldenhuys 7a88335393 hand source: emit short-circuit and/or to fix self-hosting fixpoint
EmitExpr for TBinaryExpr now checks for boAnd/boOr first and emits
short-circuit branches (jnz to sc_rhs/sc_end labels) before evaluating
either operand.  The eager `and` instruction previously emitted was
safe for bitwise use but caused a null-pointer crash when the compiler
compiled itself: TSymbolTable.FindType evaluated Sym.Kind even when Sym
was nil, because the code generator did not short-circuit the guard
`(Sym <> nil) and (Sym.Kind = skTypeAlias)`.

Stage-3 now produces byte-identical IR to stage-2 (fixpoint reached).
2026-04-27 09:06:37 +01:00
Graeme Geldenhuys 71b7fd5aae hand source: fix four self-hosting gaps toward stage-3 fixpoint
1. ImplicitSelfField ARC: emit _ClassAddRef/_ClassRelease (or
   _StringAddRef/_StringRelease) around storel when assigning to a
   class- or string-typed implicit-Self field.  Without this, the
   TCodeGenQBE.FLines StringList was freed at Generate() exit, so
   GetOutput returned garbage and stage-3 produced empty output.

2. UnescapeString: rewrite to handle both 'quoted' strings and #N
   decimal char-code literals (the old version only handled
   single-quoted forms, so #10 → empty string and GetText joined
   all lines without newlines).

3. Chr builtin: add to symbol table, semantic pass, and codegen
   (emits _Chr(w arg) → l result), required by the new UnescapeString.

4. AnalyseFieldAccess: set ResolvedMethod on parameterless constructor
   TFieldAccessExpr nodes so the codegen can emit the Create body call
   (companion to the EmitExpr IsConstructorCall fix from the previous
   commit).
2026-04-26 10:29:28 +01:00
Graeme Geldenhuys cf923852bd hand source: implement QBE sret convention for record-returning functions
Add the full structure-return (sret) convention to the hand source so
stage-2 generates correct IR for functions/methods that return tyRecord:

- EmitMethodDef/EmitFuncDef: declare record-returning functions as void
  with a hidden first param `l %_par__sret`; initialise %_var_Result
  from it; emit bare `ret` at exit.

- IsRecordCall / EmitRecordCallSret: detect record-returning call
  expressions (TFuncCallExpr, TMethodCallExpr, TFieldAccessExpr) and
  emit them with the sret address as the first argument.  Handles the
  IsImplicitSelf path with correct Self+offset pointer arithmetic.

- EmitRecordCopy / EmitRecordReleaseFields: field-by-field ARC-correct
  copy and release helpers for record types.

- EmitAssignment tyRecord branch: for record-typed lhs, either call
  EmitRecordCallSret (when the rhs is a call) or EmitRecordCopy.

- ImplicitSelfField tyRecord branch in EmitAssignment: same logic for
  assignments to record fields reached via implicit Self.

- TIdentExpr IsImplicitSelf tyRecord fix: return the field address
  rather than loading through it (callers need the address for copies).

- TIdentExpr local/global record variable fix: return VarRef address
  directly instead of an extra loadl.

All fixes mirror what the real multi-file source already had; this
brings the hand source to parity for record-return code generation.
2026-04-25 19:44:30 +01:00
Graeme Geldenhuys c547ab2f16 hand source: sync five codegen/semantic gaps for self-hosting fixpoint
- TSymbol gains ConstString field; AnalyseConstDecls stores CD.StrVal
- TIdentExpr gains ConstString and NoArgFuncDecl fields
- AnalyseIdent propagates ConstString to identifier exprs
- EmitExpr: string constants emit 'l copy $__sN' not 'w copy 0'
- IsNoArgFuncCall now set unconditionally for any skFunction with a
  return type; NoArgFuncDecl is looked up from FProcIndex so codegen
  can emit a proper call $FuncName() for user-defined zero-arg functions
- EmitExpr no-arg call path forwards NoArgFuncDecl as ResolvedDecl so
  EmitFuncCall finds the declaration instead of falling through to
  builtin dispatch and raising ECodeGenError
- TFieldAccessExpr IsMethodCall/PropRead/IsClassAccess paths use
  VarRef(RecordName, IsGlobal) instead of hardcoded %%_var_ prefix,
  fixing global-variable receivers such as Source.GetText
2026-04-25 13:06:04 +01:00
Graeme Geldenhuys 2ca052a271 fix: free intermediate string temps in chained string concatenation
When EmitExpr generates code for A + B + C (string concat), the
intermediate temp T1 = A + B (from _StringConcat) starts with
refcnt=0.  Before this fix that temp was silently abandoned:

  calling _StringRelease(T1) with refcnt=0 would decrement to -1
  (the IMMORTAL sentinel), so the string was never freed and the
  compiler slowly exhausted all available memory when processing
  large source files.

The correct pattern is to temporarily own the intermediate before
the second concat and release it after, so _StringConcat has a
chance to copy its bytes first:

  _StringAddRef(T1)           ; 0 -> 1 (temporary ownership)
  T2 = _StringConcat(T1, C)  ; copies T1's bytes into T2
  _StringRelease(T1)          ; 1 -> 0 -> freed

The same ownership pattern is applied to the right operand when
it is itself an unowned string expression (TBinaryExpr, TFuncCallExpr,
or TMethodCallExpr).

Also mirrors EmitMethodCall var-param forwarding fix (IsVarParam
check for both ObjExpr path and main path) and the previously-landed
EmitVarArgAddr, IsGlobal, vtable-in-ctor-with-args fixes to the hand
source (tests/blaise-compiler.pas).

With this fix stage-2 (blaise-compiler compiled by itself) compiles
the full hand source in 17s using 23MB RSS instead of hitting the OOM
killer at 47GB.
2026-04-25 10:58:04 +01:00
Graeme Geldenhuys f6ac68f537 codegen: store vtable in constructor calls with arguments
TMethodCallExpr.IsConstructorCall (TypeName.Create with args) was missing
the storel $vtable_X instruction after _ClassAlloc.  The TFieldAccessExpr
path (no-arg Create) already had it.  The missing store left vtable pointer
as zero, causing _IsInstance to crash with null vtable dereference during
type analysis of TTypeDesc subclasses.

Also adds virtual Destroy to TTypeDesc in the hand source so TTypeDesc and
its subclasses (TRecordTypeDesc, TEnumTypeDesc, TInterfaceTypeDesc) get
vtable slots, making HasVTable return true and field offsets correct.

Regression tests added for both the no-arg and with-arg constructor vtable
store paths.
2026-04-25 00:01:12 +01:00
Graeme Geldenhuys 5907e1d314 hand source: TObjectList takes strong refs to stored objects
The hand-written self-hosting TObjectList stored bare Pointers and
took no ARC ref when adding.  In loops like

    while Check(tkIdent) do
    begin
      CD := TConstDecl.Create;
      ...
      ABlock.ConstDecls.Add(CD);
    end;

reassigning CD on the next iteration released the prior CD (its only
strong ref) and the list's pointer dangled.  When a later allocation
reused that heap slot, the list silently contained the wrong object,
and semantic analysis crashed in TSymbol.Create reading CD.Name from
an unrelated object's memory.

Add/Put/Delete/Clear/Destroy now AddRef on insert and Release on
remove or destroy, gated on FOwnsObjects so the semantics match the
real compiler's TObjectList.  Extract continues to transfer the ref
to the caller without release.

Lets stage-2 compile the hand source far enough to hit the next bug
(currently a nil Self in TRecordTypeDesc.AddVTableSlot during
AnalyseTypeDecls) — more hand-source polish to follow.
2026-04-24 17:59:21 +01:00
Graeme Geldenhuys f66260093e Achieve self-hosting for hello-world compilation
The blaise compiler now compiles its own source (tests/blaise-compiler.pas)
into a second-stage binary that successfully compiles and runs a hello-world
program. All 834 compiler tests still pass.

Codegen and RTL fixes that unblocked self-hosting:

- String literal interning (uCodeGenQBE): set FStrLits.CaseSensitive := True.
  FPC's TStringList.IndexOf is case-insensitive by default, so distinct
  literals like 'WRITELN' and 'WriteLn' collapsed to the same label.

- ARC on implicit Self.Field assignments (uCodeGenQBE): EmitAssignment's
  implicit-Self branch did a raw storel for class/string fields. Fresh
  _ClassAlloc'ed objects start at refcnt 0, so without the addref they were
  freed by later local releases. Now addrefs new value and releases old.

- #nn character literals (uLexer): UnescapeString handled only 'text'.
  Extended to decode #nn decimal literals and concatenated forms like
  'abc'#13#10'def'. Local OrdAt helper lets the body parse under FPC and
  the self-hosted compiler.

- Chr() builtin: registered in uSymbolTable, handled in uSemantic, emitted
  in uCodeGenQBE as _Chr, implemented in rtl/blaise_str.c.

- LF line separator (Classes.pas): TStringList.GetText used #13#10; QBE
  rejects CR in its input. Changed to #10.

Self-hosting source (tests/blaise-compiler.pas) — needs a virtual Destroy
on TASTNode and Exception (post-TObject-stripping they became root classes
without a vtable, breaking `is` checks and exception message layout).
TAST* descendants chain `override`.

Migration tooling: add tools/migrate_full.py — the script that generates
tests/blaise-compiler.pas by flattening the eight compiler units and RTL
Classes unit into a single self-contained Pascal source. The postprocess()
step injects virtual Destroy on TASTNode / TTypeDesc and strips `override`
from non-vtable root classes.
2026-04-24 00:21:43 +01:00
Graeme Geldenhuys 49a1a0f478 Add implicit Self field access, OrdAt builtin, and migration infrastructure
Implicit Self: bare field names inside methods (e.g. FPos := FPos + 1)
now work without explicit Self. prefix. TIdentExpr.IsImplicitSelf /
ImplicitFieldInfo flags set by AnalyseExpr when lookup fails but a class
field matches; TAssignment.ImplicitSelfField set by AnalyseAssignment.
Codegen emits loadl %_var_Self + offset for reads, storew/storel through
Self + offset for writes. Required for migrating the compiler source which
was written in FPC style without explicit Self.

OrdAt(s, i): integer RTL function — returns ASCII ordinal of character at
1-based position i. Replaces FSource[FPos] (array indexing) in the
migrated uPasTokeniser.

Migration script (tests/blaise-compiler.pas): initial work-in-progress
self-hosting source. Script handles: const→class body stripping, inline
qualifier removal, inline-var removal, Exit(expr) expansion, shr/shl
conversion, constructor/destructor→procedure, access modifier stripping,
keyword array → TStringList/InitKeywords, FSource[expr] → OrdAt(FSource, expr),
char-range in [...] → integer comparisons, repeat...until → while True.

834 tests pass.
2026-04-23 15:24:13 +01:00
Graeme Geldenhuys 5c0083abee Add constructor-with-args, const blocks; begin self-hosting source
Constructor calls with arguments: TMethodCallExpr.IsConstructorCall flag;
AnalyseMethodCallExpr handles TypeName.Create(args) → allocate via
_ClassAlloc then call user-defined Create method; EmitCaseStmt generates
dispatch, EmitExpr handles the new constructor path.

const block: TConstDecl AST node; TBlock.ConstDecls list; ParseConstBlock;
AnalyseConstDecls registers each constant as skConstant in the symbol table.

tests/blaise-compiler.pas: initial self-hosting source stub — program header,
CHR_* character constants, Exception hierarchy, and Classes.pas RTL content.
The file is built incrementally; this commit has sections 1-2.

834 tests pass.
2026-04-23 15:04:01 +01:00
Graeme Geldenhuys 926b594516 Add const blocks, fix ParseBlock ordering, and stub blaise-compiler.pas
const: TConstDecl AST node; ParseConstBlock (integer and string literal values);
AnalyseConstDecls (registers each as skConstant in symbol table). Enables
CHR_* character constants and dupAccept/dupIgnore/dupError in self-hosting source.

ParseBlock: add tkConst to the multi-section loop.
Blaise now supports: case, enum, const, multi-type/var blocks, all file I/O
builtins — the complete language surface needed for the source migration.

834 tests pass.
2026-04-23 14:37:39 +01:00
Graeme Geldenhuys 6688f32926 Add TObjectList and TStringList to RTL; fix string ARC via typed pointers
New builtins: CompareStr, CompareText (→ _StringCompare/_StringCompareText),
ZeroMem (→ memset), _ClassAddRef/_ClassRelease for manual ARC management.

EmitPointerWrite now emits retain/release when the base type is tyString,
enabling ARC-correct string storage in ^string arrays. ZeroMem is used to
zero-initialise newly grown string slots so the "release old" half of ARC
never sees uninitialised memory.

Classes.pas: TObjectList (^Pointer array, no ARC overhead) and TStringList
(^string + ^Pointer parallel arrays, sorted binary search via CompareText,
complete Delete/Insert/Clear with correct ARC). Generics.Collections and
phase3_milestone updated to zero-init new array slots, fixing previously
hidden memory safety issues revealed by the EmitPointerWrite ARC fix.

785 tests pass, zero Valgrind errors.
2026-04-23 12:32:59 +01:00
Graeme Geldenhuys 0508b6cdc6 Rewrite RTL under ARC rules; add Destroy destructor hook
Compiler:
- TRecordTypeDesc gains HasDestroyMethod boolean property, set by the
  semantic analyser whenever a class (regular or generic instantiation)
  declares a 'Destroy' method.
- EmitFieldCleanupFn emits a call to $<TypeName>_Destroy before the
  ARC field-release loop when HasDestroyMethod is set, giving classes a
  hook to free raw resources (malloc buffers etc.) at refcount zero.

RTL (Generics.Collections):
- TList<T> and TDictionary<K,V> replace 'procedure Free' with
  'procedure Destroy'. Destroy frees the internal raw buffers (FData /
  FKeys / FValues) and nils them, making re-entrant calls safe.
  Free is no longer user-defined on these classes so List.Free uses
  the built-in ARC release path (_ClassRelease + slot nil-out).

tests/phase3_milestone.pas:
- TIntList and TStrIntDict migrated to Destroy (removed FreeMem(Self)
  which was unsafe under universal ARC on TObject).

Tests (718 total, 0 failures):
- cp.test.arc: 3 new IR tests for Destroy dispatch in field cleanup fn
  (non-generic, absent-on-class-without-Destroy, generic instantiation).
- cp.test.e2e: 4 new end-to-end / valgrind tests — ClassDestroy frees
  buffer, TList ARC lifecycle, Phase3Milestone stdout and valgrind-clean.
2026-04-23 10:51:56 +01:00
Graeme Geldenhuys 2d4a005f56 Collapse Write+WriteLn pairs into multi-arg WriteLn in milestone tests 2026-04-22 23:19:55 +01:00
Graeme Geldenhuys cbb1af5ffc Replace force-exit workarounds with break now that Break is supported 2026-04-22 23:18:47 +01:00
Graeme Geldenhuys b897f3e08d Add copyright headers and compiler output
Add BSD-3-Clause copyright header to all source files (44 .pas files).
Update compiler to output copyright line in version/usage output.
2026-04-22 19:00:46 +01:00
Graeme Geldenhuys 00e44e8876 Fix try/finally and try/except exception frame size (alloc16 32 → 512)
The exception frame was allocated as alloc16 32 (32 × 16 = 512 bytes on paper,
but QBE's alloc16 N means N items of 16 bytes: so 32 × 16 = 512 is actually
correct). Wait - re-reading: alloc16 N in QBE allocates N bytes aligned to 16.
So alloc16 32 was only 32 bytes — far less than sizeof(BlaiseExcFrame) which
needs ~216 bytes on Linux x86_64 (200-byte jmp_buf + two pointer fields).

setjmp writes its full jmp_buf into the undersized slot, silently corrupting
whatever sat above it on the stack: saved registers, local variables, and the
virtual method pointer loaded for virtual dispatch. Any try block followed by
a virtual method call in expression position crashed with a bad function pointer.

Fix: alloc16 32 → alloc16 512 in both EmitTryFinallyStmt and EmitTryExceptStmt,
matching the RTL contract documented in blaise_exc.c lines 8-13.

Two new regression tests assert alloc16 512 appears in emitted IR for both
try forms. 628 tests pass (was 626).

Also adds tests/phase2_milestone.pas: linked list with virtual dispatch,
inheritance, try/finally, and 'is' type test — zero valgrind leaks.
Updates design.adoc Phase 3 status table to reflect current implementation.
2026-04-22 18:22:48 +01:00
Graeme Geldenhuys d49ebb4f51 Phase 3 milestone: TList<Integer> + TDictionary<string,Integer> zero leaks
Four bugs fixed on the path to the milestone:

1. Non-generic class fields resolved with FindType instead of
   FindTypeOrInstantiate — typed pointer fields (^Integer, ^string) in
   concrete classes failed with 'Unknown type'.

2. Integer/Boolean/Int64 local variables not zero-initialised — QBE
   validator rejected 'slot read but never stored to' for variables
   written only through var-param pointers.

3. Class method var-param signature wrong — EmitMethodDef emitted the
   param type (w for Integer) rather than 'l' (pointer) for var params;
   EmitParamAllocs spilled the pointer with storew instead of storel.

4. Method call sites did not pass addresses for var-param arguments —
   EmitMethodCallExpr always called EmitExpr (value load) rather than
   forwarding %_var_Name (address) for IsVarParam params.

Milestone result (valgrind --leak-check=full):
  7 allocs, 7 frees, 0 bytes in use at exit, ERROR SUMMARY: 0 errors
2026-04-22 16:58:36 +01:00
Graeme Geldenhuys 0df5514623 Add Phase 2: record/class types, ARC strings, RTL stubs, and grammar doc
Symbol table:
- Added tyClass kind and NewClassType factory; TRecordTypeDesc now
  accepts an optional kind parameter so records and classes share the
  same descriptor with distinct semantics.

Semantic analyser:
- AnalyseTypeDecls runs before PushScope so type symbols land in global
  scope and survive PopScope.
- Handles TRecordTypeDef and TClassTypeDef; sets IsConstructorCall on
  TypeName.Create expressions and IsClassAccess on class-variable field
  access and assignment nodes.

Code generator (QBE IR):
- Class variables: 8-byte pointer slot, zeroed on entry.
- Constructor calls: malloc(sizeof fields), store pointer.
- Class field access/write: load pointer, add field offset, load/store.
- ARC string assignment: AddRef new value, Release old value, then store.
- Block exit: Release every string variable in scope (EmitStringCleanup).

Lexer / Parser / AST:
- Added tkClass keyword and ParseClassDef; FieldDecl parsing refactored
  into ParseFieldDecl(AFields) shared by both record and class.
- AST gains TClassTypeDef, and IsConstructorCall/IsClassAccess flags on
  TFieldAccessExpr and TFieldAssignment.

RTL:
- blaise_arc.c: Phase 2 no-op stubs for _StringAddRef / _StringRelease.
- rtl/Makefile: builds blaise_rtl.a; `make install` copies it next to
  the compiler binary for automatic discovery by FindRTL.
- Blaise.pas driver: FindRTL checks BLAISE_RTL env var then binary dir;
  links the RTL archive when present.

Tests:
- 180 unit tests (records ×23, classes ×22, ARC ×9, codegen ×9, …).
- 4 end-to-end integration tests in tests/integration/test_arc_strings.sh
  covering string assignment, two-var cleanup, reassignment cycle, and
  empty-program linkage.

Docs:
- design.adoc updated with Phase 2 implementation status table.
- docs/grammar.ebnf: new authoritative EBNF grammar for the Blaise
  language as implemented, including ARC semantic annotations.
2026-04-20 19:04:12 +01:00