Commit graph

37 commits

Author SHA1 Message Date
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 1931b84bc8 fix(compiler): self-hosting fixpoint unblocks — Cardinal, PtrUInt, xor, _ClassRelease
Seven source-compatibility fixes required to get the Blaise compiler to
parse and analyse its own source during the self-hosting fixpoint attempt.
None of these affect compiled output for existing programs; all 1105 tests
still pass.

  Cardinal alias (uSymbolTable): maps to UInt32; used in uDebugOPDF.pas
  PtrUInt alias (uSymbolTable):  maps to Int64; used in uDebugOPDF.pas
  xor operator (uLexer/uAST/uParser/uSemantic/uCodeGenQBE): tkXor/boXor
    emit QBE 'xor'; used in uDebugOPDF.pas FNV hash
  _ClassRelease external (blaise_arc.pas interface): C function declared
    callable from Pascal; contnrs.pas calls it directly
  contnrs.pas uses blaise_arc: makes _ClassRelease visible
  not 7 → -8 (uDebugOPDF.pas): Blaise 'not' is Boolean-only; -8 = not 7
  TProcess.Options removed (Blaise.pas): Blaise RTL TProcess has no
    Options property; pipes are always active
  FOutput.Strings[] (uDebugOPDF.pas): Blaise TStringList has no default
    [] property; must use explicit .Strings[I] form

Stage-2 IR now generates: 88,526 lines, exit 0.
Remaining blocker: %_var_FProgram QBE error (see handover.txt).
2026-05-02 19:59:04 +01:00
Graeme Geldenhuys 0e08dc3a53 feat(debug): OPDF Step 6c — recPointer, recArray, recSet, recProperty, recInterface, recConstant, recUnitDirectory
- recPointer: emitted for typed/untyped pointer fields; TargetTypeID links to
  base type; CanonicalName extended for tyStaticArray and tyOpenArray
- recArray: static arrays emit IsDynamic=0 with TArrayBound (LowerBound,
  UpperBound as Int64 quads); dynamic/open arrays emit IsDynamic=1 without bounds
- recSet: BaseTypeID references the base enum; SizeInBytes from RawSize
- recProperty: emitted after each recClass; field-backed uses numeric offset
  (patField), method-backed uses linker label (patMethod)
- recInterface: TypeID + ParentTypeID + zeroed GUID + method descriptors with
  ReturnTypeID and ParamCount per method
- recConstant: iterates program-block ConstDecls; ckOrd embeds 8-byte Int64,
  ckString embeds raw bytes
- recUnitDirectory: first record after header; RecordOffset computed via GAS
  label arithmetic (.Lopdf_unit0_start - .Lopdf_start); RecordCount patched at
  end; header Flags set to OPDF_FLAG_HAS_DIRECTORY = 1
- uSymbolTable: add Properties: TObjectList read property to TRecordTypeDesc
- 12 new tests; 37 OPDF tests total; 1072 compiler tests all passing
2026-05-01 23:37:07 +01:00
Graeme Geldenhuys 584f0378e2 feat(rtl): migrate blaise_arc.c → blaise_arc.pas — Step 9c
Port all non-function-pointer ARC functions from C to Pascal:
  _StringAddRef, _StringRelease, _StringEquals, _StringConcat,
  TObject_Destroy, _ClassAddRef, _ClassFree

Remaining in C (blaise_arc_class.c — renamed from blaise_arc.c):
  _ClassAlloc and _ClassRelease — store and call a function pointer
  (the $_FieldCleanup_TypeName hook), which Blaise cannot express yet.

Compiler fixes required to support this port:
- uCodeGenQBE: EmitFuncDef now exits early for IsExternal declarations;
  previously it crashed (access violation) when iterating a nil body
- uSymbolTable: remove _ClassAddRef and _ClassRelease from RegisterBuiltins;
  they are internal RTL symbols emitted directly by the codegen, not
  user-callable builtins, and pre-registering them caused "Duplicate
  identifier" errors when the unit interface re-declared them

RTL Makefile: blaise_arc_class.c replaces blaise_arc.c in C_SRCS;
blaise_arc.pas added to PAS_OBJS with its own build driver.

All 1035 compiler tests pass.
2026-05-01 16:26:17 +01:00
Graeme Geldenhuys 5ec85211bd feat(compiler+rtl): multi-file self-hosting fixpoint — Step 11
Fixes required to reach byte-identical IR between stage-1 and stage-2
when compiling the multi-file Blaise.pas source.

Compiler fixes:
- uCodeGenQBE.pas: QbeEscapeString — replace Format('%02x',[C]) with
  manual hex encoding (Hi := C shr 4; Lo := C and 15; Chr(...));
  Blaise's _StringFormat does not support %x, causing non-ASCII bytes
  (e.g. UTF-8 em-dash U+2014) to be emitted as \%02x instead of \E2\80\94
- uCodeGenQBE.pas: EmitFuncCallExpr — route IntToStr to _Int64ToStr when
  the argument resolves to Int64 (QBE type 'l'), matching FPC's overloaded
  IntToStr resolution; prevents 64-bit values being truncated to Int32
  before string conversion
- uSymbolTable.pas: MaxInt changed from Int64 (9223372036854775807) to
  Integer (2147483647); all uses are Copy(S,N,MaxInt) meaning "rest of
  string", and 2147483647 is handled correctly by _StringCopy; avoids
  Int64 literal truncation/extsw issues in the self-hosted binary

RTL fixes:
- blaise_str.c: _StringCopy — negative count (64-bit MaxInt truncated to
  int32 = -1) now treated as "rest of string" rather than 0
- blaise_str.c: add _UpCase helper
- classes.pas: TStringList.GetText — add trailing #10 to match Delphi/FPC
  TStringList.Text semantics (each line including the last ends with LF);
  SaveToFile updated to not double-add the trailing newline

Tests: 1009 pass
Fixpoint: diff /tmp/multifile.ssa /tmp/multifile2.ssa → empty
2026-05-01 09:26:39 +01:00
Graeme Geldenhuys d20970f27b feat(compiler+rtl): multi-file self-hosting pipeline — Step 10
Fixes required to get the multi-file compiler binary (blaise-multi)
to compile Blaise.pas without crashing.

RTL fixes:
- contnrs.pas: TObjectList ARC — Add/Put/Delete/Clear/Destroy now call
  _ClassAddRef/_ClassRelease so stored objects survive beyond their
  creating scope; fixes premature-free of TTypeDecl nodes during
  AnalyseUnitForExport
- blaise_str.c: _StringCopy — treat negative count as "rest of string"
  instead of 0; 64-bit MaxInt (0x7fffffffffffffff) is truncated to -1
  when passed as a 32-bit w argument to _StringCopy, which was causing
  Copy(S, 2, MaxInt) to return empty string in FindTypeOrInstantiate
- blaise_str.c: add _UpCase C helper
- blaise_arc.c: minor ARC fixes

Compiler fixes:
- uAST.pas: add IsVarParam flag to TMethodCallStmt and TMethodCallExpr
- uSemantic.pas: set IsVarParam when object symbol is skVarParameter
- uCodeGenQBE.pas: emit double-dereference for method calls on var/out
  parameters — the local slot holds the caller's address, not the
  object pointer, so one extra loadl is needed to reach the receiver
- uSymbolTable.pas, uUnitLoader.pas, uParser.pas, uLexer.pas: various
  fixes discovered during multi-file pipeline testing
- Blaise.pas: improved error reporting

Tests: 1009 pass
2026-05-01 06:52:48 +01:00
Graeme Geldenhuys 53235aa6b6 feat(compiler): self-hosting fixpoint reached — Step 10
Parser, lexer, tokeniser, semantic analyser and symbol table extended
to compile the multi-file Blaise source with the Blaise compiler itself.
Stage-2 and stage-3 IR are byte-identical; fixpoint confirmed.

Changes to reach fixpoint:
- uLexer: source filename parameter for actionable parse error messages
- uParser: method directive loop (inline/stdcall/abstract/…), unit
  interface/implementation loops for var/const/type blocks, chained
  field-assignment (A.B.C := v), filename in all EParseError messages
- uPasTokeniser: full rewrite to remove FPC-only features (char sets,
  shr, Exit(value), private/public visibility) so Blaise can compile it
- uSymbolTable: remove unsupported default parameter values
- uSemantic: replace Exit(value) with Result+Exit; expand five `with`
  statement blocks to explicit typed local variables
- uAST, uCodeGenQBE: minor additions supporting the above
- uUnitLoader: pass filename to TLexer.Create
- classes.pas: inline dupIgnore literal (const section deferred)
2026-04-30 13:30:30 +01:00
Graeme Geldenhuys e568ce21f2 feat(rtl+compiler): Process.pas + C helper — Step 8
Add process management infrastructure for multi-file self-hosting:

rtl/src/main/c/blaise_process.c (new)
  - BlaiseProcess struct with per-handle exe, argv, pipe fd, pid
  - _ProcessCreate / _ProcessFree: alloc/dealloc
  - _ProcessSetExe / _ProcessAddArg: configure before Execute
  - _ProcessExecute: fork+exec with stdout+stderr piped
  - _ProcessRunning: WNOHANG waitpid check
  - _ProcessReadOutput: blocking read, returns '' on EOF
  - _ProcessWaitOnExit / _ProcessExitCode: reap child
  - Windows stubs via #ifdef _WIN32

rtl/src/main/pascal/Process.pas (new)
  - TProcess class backed by the above C helpers via built-ins
  - Execute, ReadOutput, WaitOnExit, Running, ExitCode, Parameters

compiler (uSymbolTable / uSemantic / uCodeGenQBE)
  - 9 new process built-ins: ProcessCreate, ProcessSetExe,
    ProcessAddArg, ProcessExecute, ProcessRunning, ProcessReadOutput,
    ProcessWaitOnExit, ProcessExitCode, ProcessFree

compiler/src/main/pascal/Blaise.pas
  - RunProcess: open-array → TStringList; loop uses ReadProcessChunk
    wrapper (FPC-compatible); call sites rebuilt with explicit TStringList
  - ReadProcessChunk wraps FPC's Output.Read for stage-1; will be
    replaced by Proc.ReadOutput at Step 10

Tests
  - cp.test.process.pas: 18 semantic + codegen tests for all built-ins
  - cp.test.e2e.pas: TestRun_ProcessBuiltins_CapturesOutput,
    TestRun_ProcessBuiltins_ExitCode (verify fork/exec/pipe at runtime)
2026-04-29 18:31:55 +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 81d9e9f320 feat: ChangeFileExt, ExtractFileName, ExtractFilePath, IncludeTrailingPathDelimiter as built-ins
Implements step 11 of the self-hosting feature list: the four SysUtils
path-manipulation functions needed by the compiler source.  Backed by C RTL
helpers in blaise_io.c; registered as compiler built-ins following the same
pattern as GetEnvVar.  Language-rationale records the decision and the
migration path to a Blaise-native SysUtils shim once multi-unit support lands.
2026-04-28 22:08:36 +01:00
Graeme Geldenhuys f1580333ac feat: GetEnvironmentVariable as built-in alias for GetEnvVar
Registers GetEnvironmentVariable (FPC/Delphi-compatible name) as a built-in
function alongside the existing GetEnvVar, both mapping to the RTL
_GetEnvVar C function. Required for self-hosting: Blaise.pas uses the
FPC name to locate the RTL and QBE binary at runtime.

No new RTL code — _GetEnvVar (blaise_io.c) handles both names.
Adds 2 tests in cp.test.selfhosting to verify semantic and codegen.
2026-04-28 19:14:37 +01:00
Graeme Geldenhuys 5152977987 feat: set types — set of TEnum, set literals, in, Include/Exclude, arithmetic
Implements Pascal bit-set types end-to-end:

- Lexer: tkSet, tkIn keywords
- AST: TSetTypeDef node; boIn binary operator
- Symbol table: tySet, TSetTypeDesc (w ≤32 members, l ≤64); NewSetType factory;
  Include/Exclude registered as built-in procedures
- Parser: ParseSetDef; in operator in expression parser
- Semantic: set type registration; set literal validation; boIn membership check;
  set arithmetic (+/-/*) and equality (=/≠) operator analysis; Include/Exclude
  argument validation
- Codegen: compile-time bitmask for set literals; shr+and for `in`; or/xor-and/and
  for union/difference/intersection; OR-in-place (Include), AND-NOT-in-place (Exclude)
- Tests: 32 new tests in cp.test.sets covering parse, semantic, and codegen
- Fix: rename Set→Assign method in genericmethodimpls test (set is now reserved)
- Docs: SetType/SetLiteral grammar rules; Set Types rationale section; resolve
  enum underlying type open question (confirmed w)
2026-04-28 19:14:28 +01:00
Graeme Geldenhuys c078e7c5de feat: PChar type + PChar(str) / string(pchar) casts
Adds tyPChar as a distinct opaque-pointer type kind (maps to l in QBE IR).
PChar(str) emits add str_ptr, 12 to skip the 12-byte ARC header, yielding
a valid C char*. string(pchar) calls _StringFromPChar (new RTL function in
blaise_str.c) which measures strlen, allocs an ARC string, and copies.
PChar(pchar_expr) is an identity cast. Variable declarations allocate an
8-byte nil-initialised pointer slot.

6 new tests; all 898 tests pass.
2026-04-28 13:20:43 +01:00
Graeme Geldenhuys c365b77f56 feat: static array array[L..H] of T — stack-allocated fixed-size arrays
Adds TStaticSubscriptAssign AST node, ParseTypeName extension for
array[L..H] of T syntax (tkDotDot token), TStaticArrayTypeDesc with
RawSize for correct element-footprint sizing (1 for Byte, 4 for Integer),
and full codegen: alloc4/alloc8 + memset zero-init, storeb/storew for
writes, loadub/loadw for reads, LowBound subtraction for non-zero ranges.

Also includes array literal ['a','b','c'] call-site support (step 4):
TArrayLiteralExpr, open-array formal matching, stack buffer allocation,
and codegen emitting two QBE args (data ptr + compile-time high index).

13 new static-array tests + 9 new array-literal tests; all 888 tests pass.
2026-04-27 17:22:29 +01:00
Graeme Geldenhuys 4feb1b3867 feat: const open array param (array of T) — two-register ABI
Adds support for open-array parameters of the form `const A: array of T`
using the standard Pascal two-register ABI: a data pointer (`%_par_A`)
and a high-index value (`%_par_A_high`, count − 1).

- uLexer: add tkArray keyword token
- uAST: add IsOpenArray / IsConstParam fields to TMethodParam
- uSymbolTable: add tyOpenArray kind, TOpenArrayTypeDesc, NewOpenArrayType
- uParser: extend ParseParamList to recognise `array of T` syntax
- uSemantic: add ResolveParamType helper; handle High/Low builtins (moved
  before the FProcIndex lookup so they fire without a symbol-table entry);
  open-array subscript in AnalyseStringSubscriptExpr; open-array
  compatibility in CheckTypesMatch
- uCodeGenQBE: emit two params + two alloc slots per open-array formal;
  High/Low intrinsic emission; pointer-arithmetic subscript; two-arg
  forwarding at call sites; exclude open arrays from ARC addref/release
- Tests: 16 new tests covering parser, semantic, and codegen paths
- docs/grammar.ebnf: add ParamType rule (ARRAY OF TypeName | TypeName)
- docs/language-rationale.adoc: document open-array ABI decision
2026-04-27 16:31:04 +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 fb89879f86 Add OrdAt builtin for character-level string access in self-hosting migration
_OrdAt(s, i): returns ASCII ordinal of character at 1-based position i.
Required for migrating uPasTokeniser to Blaise: replaces FSource[FPos] and
char set-membership checks with integer comparisons.
2026-04-23 13:50:30 +01:00
Graeme Geldenhuys 1e8ebf04f1 Add case statements and enum types — required for self-hosting migration
case statement: TCaseStmt + TCaseBranch AST nodes; tkCase/tkOf tokens;
ParseCaseStmt (multi-value branches, optional else); AnalyseCaseStmt
(ordinal selector check); EmitCaseStmt (ceqw dispatch chain + jmp).

enum types: TEnumTypeDef AST node; TEnumTypeDesc symbol-table descriptor
(tyEnum, ByteSize=4, QBE 'w'); ParseEnumDef; AnalyseTypeDecls enum path
registers each member as skConstant with ordinal value 0..N-1; IsNumeric,
IsOrdinal, ByteSize, AllocAlign, QbeTypeOf all updated for tyEnum.
Variable alloc path handles tyEnum identically to tyInteger.

These two features unblock the self-hosting source migration: the compiler
source uses TTokenKind, TTypeKind, TBinaryOp, and TSymbolKind enums
throughout, all wrapped in case dispatch.

834 tests pass, zero Valgrind errors.
2026-04-23 13:47:17 +01:00
Graeme Geldenhuys 637369aa10 Close self-hosting gaps: multi-section blocks, file I/O, CLI args, process
Parser: allow any number of type/var/procedure/function sections in any
order within a single block. Required for single-file concatenation of
compiler units during self-hosting migration.

RTL (blaise_io.c): _SetArgs, _ParamCount, _ParamStr, _ReadFile, _WriteFile,
_AppendFile, _FileExists, _GetEnvVar, _Exec, _Halt. All exposed as Blaise
builtins via uSymbolTable/uSemantic/uCodeGenQBE.

$main now emits (w %argc, l %argv) and calls _SetArgs at startup so
ParamStr/ParamCount work at runtime.

TIdentExpr: IsNoArgFuncCall flag for builtin functions called without parens
(e.g. ParamCount without ()). Codegen synthesises a temporary TFuncCallExpr
so the existing builtin dispatch handles it transparently.

IR comment: removed stale "(Phase 2)" tag.

815 tests pass, zero Valgrind errors.
2026-04-23 13:00:34 +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 3b9339ae70 Add Format() built-in with variadic tagged dispatch
Format(fmt, arg0, arg1, ...) emits a variadic call to the C RTL
function _StringFormat using tagged (tag, value) pairs after the
variadic marker '...':
  tag=0 → integer arg (w QBE type)
  tag=1 → string arg  (l QBE type)

The C implementation (blaise_str.c) scans the format string for
%d and %s specifiers across two passes (length calculation then fill)
and builds a new Blaise ARC-managed string.

7 new IR-level tests in cp.test.stringops; 3 new e2e tests verify:
  Format('val=%d', 42)         → 'val=42'
  Format('hello %s', 'world')  → 'hello world'
  Format('%s=%d', 'Alice', 30) → 'Alice=30'
All 765 tests pass.
2026-04-23 11:56:00 +01:00
Graeme Geldenhuys 3258679445 Add built-in string operation functions (Length, Pos, Copy, etc.)
Implements 8 string RTL functions as compiler built-ins:

Compiler changes (uSymbolTable, uSemantic, uCodeGenQBE):
  - Length(s)              → $_StringLength  → Integer
  - Pos(sub, s)            → $_StringPos     → Integer (1-based)
  - Copy(s, from, count)   → $_StringCopy    → string
  - UpperCase(s)           → $_StringUpperCase → string
  - LowerCase(s)           → $_StringLowerCase → string
  - SameText(s1, s2)       → $_StringSameText → Boolean
  - IntToStr(n)            → $_IntToStr      → string
  - StrToInt(s)            → $_StrToInt      → Integer

RTL (blaise_str.c + Makefile):
  Implements all 8 functions against the BlaiseStrHdr layout defined
  in blaise_arc.c. All return new strings with RefCount=0 (ARC picks
  them up at the assignment site).

Tests: 31 new IR-level tests in cp.test.stringops.pas;
       7 new e2e tests in cp.test.e2e.pas verifying correct runtime
       output (e.g. Length("hello")=5, Pos=7, Copy=ell, etc.).
All 755 tests pass.
2026-04-23 11:50:06 +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 6569c1f82b Implement [Weak] attribute for cycle-breaking under ARC
A weak reference is a slot that does not contribute to the target's
refcount and is automatically nil'd when the target is freed.  This
breaks reference cycles that would otherwise leak under universal ARC.

Attribute model (Delphi-compatible)

    type
      TNode = class
        Other: TNode;            // strong, default
        [Weak] Back: TNode;      // weak, no refcount, zeroed on free
      end;

The parser accepts `[Ident]` and `[Ident(args)]` attribute lists before
var and class-field declarations.  Attribute names are captured
verbatim on TVarDecl.Attributes / TFieldDecl.Attributes; unknown
attributes are accepted silently so user-defined attributes (which
will materialise with RTTI) don't need to wait for the syntax.

Semantic analysis resolves `[Weak]` with the Delphi convention that
the `Attribute` suffix is optional — `[Weak]` and `[WeakAttribute]`
both resolve to the recognised marker.  Applying [Weak] to a non-
reference type (Integer, string, record) is an error at declaration
time.  Resolved weakness is surfaced on TVarDecl.IsWeak / TFieldInfo.IsWeak
and TSymbol.IsWeak for codegen consumption.

Lexer: tkLBracket / tkRBracket added.

RTL (blaise_weak.c): global chained hash table maps target pointers to
lists of registered weak slot addresses.  _WeakAssign registers a new
slot (and unregisters any prior), _WeakClear unregisters and zeros,
_WeakZeroSlots nils all slots registered against a target.  The last
is called from _ClassRelease at refcount zero, before field cleanup
and free, so weak readers never see dangling memory.

Codegen: weak class/interface vars bypass addref/release entirely.
Variable assignment, field assignment, scope-exit cleanup, exception-
path cleanup, and per-class _FieldCleanup functions all branch on the
weak flag to emit _WeakAssign / _WeakClear against the slot address
instead of strong refcount operations.

Tests

  * cp.test.weakref.pas — 14 unit tests covering lexer, parser,
    semantic validation (including the suffix-drop rule), and codegen
    emission at every insertion point.
  * cp.test.e2e.TestRun_WeakRef_BreaksCycle_Valgrind — compile and run
    a two-node mutual-reference program with [Weak].  Without the
    attribute, valgrind reports both nodes as definitely lost; with it,
    the run is leak-free.

All 711 compiler tests pass.

Deferred: TCustomAttribute / WeakAttribute Pascal-side class
declarations.  The compiler resolves `[Weak]` purely by attribute
name, so the RTL classes are not load-bearing today.  They will be
added as empty marker types when RTTI arrives and RTTI-queryable
attributes become meaningful.
2026-04-23 10:15:00 +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 c07b28138f Add True and False as built-in Boolean constants
Registers True (1) and False (0) as skConstant symbols in the symbol table.
TIdentExpr gains IsConstant/ConstValue fields set by the semantic pass; codegen
emits 'copy N' rather than loading from a non-existent stack slot.

Idiomatic Boolean returns (Result := True / Result := False) now work in
all Blaise source including the RTL TDictionary.TryGetValue method.
6 new tests in cp.test.codegen.pas — 623 total, 0 failures.
2026-04-22 16:37:31 +01:00
Graeme Geldenhuys e30757fef3 Add generic interface support: IFoo<T> = interface...end
Implements full generic interface support through the compiler pipeline:

Parser: TGenericInterfaceDef AST node; IDENT<TypeParams> = interface...end
syntax; ParseGenericName helper for class(IFoo<T>) parent/implements lists.

AST: TGenericInterfaceDef (ParamNames + IntfDef) and TGenericInterfaceInstance
(mangled InstName + TypeDesc) tracked on TProgram.GenericIntfInstances.

Symbol table: TInterfaceTypeDesc extended with FReturnTypes parallel list so
interface dispatch expressions can resolve return types.

Semantic: InstantiateGenericInterface instantiates IFoo<T> on demand from a
template; FindTypeOrInstantiate falls through class→interface; parent-as-interface
detection moves IFoo<Integer> from ParentName to ImplementsNames; type-safe
guard in InstantiateGeneric prevents misidentifying interface templates as class
templates; AnalyseMethodCallExpr extended with interface dispatch path.

Codegen: EmitInterfaceDefs emits typeinfo for GenericIntfInstances using the
mangled name (IEqualityComparer_Integer); itab/impllist names QBEMangle interface
names; EmitExpr TMethodCallExpr handles interface dispatch via itab pointer.

13 new tests in cp.test.genericintfs covering parser, semantic, and codegen.
2026-04-22 10:17:30 +01:00
Graeme Geldenhuys 7e2f97f9ef Add pointer type infrastructure: ^T types, dereference, GetMem/FreeMem/ReallocMem
- uSymbolTable: tyPointer kind, TPointerTypeDesc(BaseType), GetMem/FreeMem/ReallocMem builtins
- uLexer/uPasTokeniser: tkCaret token for '^'; suppress ^X string escape (not in Clean Pascal)
- uParser: ParseTypeName handles '^TypeName' prefix (uses Self. for explicit recursion);
  TDerefExpr for P^ postfix; TPointerWriteStmt for P^ := V lhs
- uAST: TDerefExpr, TPointerWriteStmt nodes
- uSemantic: FindTypeOrInstantiate creates TPointerTypeDesc on demand for '^T';
  AnalyseDerefExpr, AnalysePointerWriteStmt; GetMem/ReallocMem/type-cast handling
- uCodeGenQBE: tyPointer→'l', EmitPointerWrite (storew/storel), TDerefExpr (loadw/loadl),
  GetMem→malloc, FreeMem→free, ReallocMem→realloc, pointer arithmetic (extsw + add/sub),
  type-cast via copy
- 15 new tests (556 total), all passing
2026-04-22 07:42:55 +01:00
Graeme Geldenhuys 80c2b21d6c Implement Pascal property declarations with field- and method-backed access
Adds full property support to class types: field-backed reads/writes redirect
at semantic analysis time; method-backed reads emit a getter call in QBE IR.
Read-only enforcement raises ESemanticError on write attempts. Soft-keyword
detection keeps 'property' out of TTokenKind. 14 new tests, 527 total.
2026-04-21 23:15:15 +01:00
Graeme Geldenhuys b34bfc574d Implement Phase 3 generics monomorphization (Delphi syntax)
Parse generic class declarations (`TBox<T>`) as TGenericTypeDef nodes.
Introduce one-token parser lookahead to disambiguate `<` in type
positions vs. comparison operators.

Demand-driven instantiation: when a var declaration references
`TBox<Integer>`, InstantiateGeneric clones the class AST with type-param
substitution, resolves all field/method types, analyses method bodies
with the concrete class type in scope, and registers the instance in
TProgram.GenericInstances.  Instantiated type symbols are defined in
the global scope so they survive inner scope pops.

Codegen emits typeinfo, vtable, and method bodies for each generic
instance using QBEMangle (`TBox<Integer>` → `TBox_Integer`).

19 new tests in cp.test.generics cover parser, semantic, and codegen
paths.  All 513 tests pass.
2026-04-21 23:02:23 +01:00
Graeme Geldenhuys 3365558265 Add is/as for interface types with _ImplementsInterface/_GetItab dispatch
Extend BlaiseTypeInfo with an impllist field (NULL-terminated {ti,itab}
pairs) so the runtime can walk a class's interface set.  Add
_ImplementsInterface and _GetItab to blaise_exc.c for the 'is IFoo'
and 'F := T as IFoo' operators respectively.

Compiler changes:
- uAST: ResolvedTargetType field on TIsExpr
- uSemantic: accept tyInterface on RHS of is/as; set ResolvedTargetType
- uCodeGenQBE: EmitIsExpr dispatches to _ImplementsInterface for interface
  targets; EmitAssignment handles F := T as IFoo via _GetItab + branch
- EmitTypeInfoDefs: extend to 2-field layout {parent, impllist}
- EmitInterfaceDefs: emit per-class impllist data blocks
- uSymbolTable: register IInterface as built-in interface type

10 new tests in cp.test.interfaces; 2 existing typeinfo tests updated
for new 2-field format.  494 tests, 0 failures.
2026-04-21 21:25:49 +01:00
Graeme Geldenhuys c371272811 Implement interface type declarations, class implements, and interface dispatch
Parser: TInterfaceTypeDef AST node; ParseInterfaceDef; ParseClassDef extended
to parse comma-separated implements list (first name = parent class, rest =
interfaces); ParseMethodDecl body remains optional.

Symbol table: tyInterface added to TTypeKind; TInterfaceTypeDesc with unsorted
(declaration-order) method list for correct itab slot indexing; FImplements
non-owning list on TRecordTypeDesc tracks class→interface pairs; TObject
pre-registered as built-in root class.

Semantic: AnalyseTypeDecls handles TInterfaceTypeDef (register, inherit parent
methods); verifies class implements all interface methods; CheckTypesMatch
extended for class→interface assignment; AnalyseMethodCall handles tyInterface
object vars via itab dispatch path; TAssignment carries ResolvedLhsType.

Codegen: EmitInterfaceDefs emits $typeinfo_IFoo and $itab_TFoo_IFoo data blocks;
EmitVarAllocs allocates two-slot fat pointer (_obj + _itab) for interface vars;
EmitAssignment stores both obj pointer and itab address on interface assignment;
EmitMethodCall dispatches via itab on tyInterface receiver.

15 new tests, 484 total, 0 failures.
2026-04-21 18:24:55 +01:00
Graeme Geldenhuys 0711f6d6f1 Add virtual method dispatch with vtable generation in QBE IR
- Add tkVirtual/tkOverride tokens to lexer (context-sensitive directives)
- Add IsVirtual, IsOverride, VTableSlot fields to TMethodDecl
- Add TVTableEntry and vtable management methods to TRecordTypeDesc
- TotalSize now includes 8-byte vptr offset when class has virtual methods
- Semantic pass pre-populates vtable before adding fields (so offsets are correct)
- Subtype assignment (TDerived := TBase) allowed via IsSubtypeOf helper
- CodeGen emits 'data $vtable_TypeName = { l $fn, ... }' data sections
- Constructor stores vtable pointer at instance offset 0 after malloc
- Virtual method calls use indirect dispatch via vtable slot load
- Static methods continue to use direct '$TypeName_MethodName' calls
2026-04-21 00:05:57 +01:00
Graeme Geldenhuys 7bc99b7502 Add var parameter support (pass-by-reference)
Parser recognises 'var' prefix in parameter lists; IsVarParam flag propagates
through TMethodParam → TIdentExpr/TAssignment AST nodes via semantic analysis.
Codegen uses pointer-typed (l) signatures, passes variable addresses at call
sites, and double-dereferences reads/writes inside function bodies. Also fixes
ParseBlock to allow interleaved var and proc/func declaration sections.
2026-04-20 23:18:23 +01:00
Graeme Geldenhuys 71b7e7896e Add class inheritance, self-referential types, and nil literal
- tyNil pseudo-type; nil literal compatible with any class type
- Two-pass type analysis so self-referential fields resolve correctly
- Parent fields copied into child layout with consecutive offsets
- FindMethodDecl walks parent chain; OwnerTypeName ensures codegen
  emits the defining class name (e.g. $TBase_SetX, not $TChild_SetX)
- ceql/cnel used for pointer comparisons instead of ceqw/cnew
- Multiple var sections now accepted in ParseBlock
- 19 new tests in cp.test.inherit (326 total, all passing)
2026-04-20 22:41:28 +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
Graeme Geldenhuys a561525ebd Add symbol table with scope nesting and built-in types
TSymbolTable manages a scope stack with case-insensitive lookup. Each
TScope holds a sorted TStringList (names → TSymbol) and chains to its
parent for identifier resolution. Built-in primitives (Integer, Int64,
UInt32, Byte, Boolean, string) and I/O procedures (Write, WriteLn) are
pre-registered in the global scope. 29 FPCUnit tests, all passing.
2026-04-20 17:52:09 +01:00