Commit graph

52 commits

Author SHA1 Message Date
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 6ef9cd8c7e Add end-to-end test harness; fix EmitDataSection skipping format strings
The existing fptest suite is IR-only: tests generate QBE text and grep for
expected strings. That harness missed two runtime bugs — the alloc16 32
exception frame (fixed in 00e44e8) and, now, an EmitDataSection early-exit
that omitted unconditional printf format strings when the program had no
string literals.

cp.test.e2e.pas is a new test unit that compiles Pascal through the full
pipeline (Lexer -> Parser -> Semantic -> CodeGenQBE -> qbe -> cc -> native
binary), executes the result, and asserts on stdout, exit code, or valgrind
output. It walks up from CWD to locate vendor/qbe and rtl so it runs both
from the project root and from compiler/target/ (where PasBuild invokes
TestRunner). Scratch artefacts live under compiler/target/test-e2e/ and
are preserved on failure for post-mortem debugging. Tests Ignore()
gracefully when qbe, the RTL, or valgrind is not available.

Initial cases: bare try/finally, try/finally with locals (stack-corruption
regression for the alloc16 bug), nested try/finally, virtual dispatch in
expression position inside try/finally, Phase 2 milestone stdout, and
Phase 2 milestone valgrind-clean.

On first run the virtual-dispatch case surfaced the latent EmitDataSection
bug: the procedure exited early when FStrLits.Count = 0, skipping the
$__fmt_d_nl / $__fmt_nl definitions that WriteLn(Integer) and bare WriteLn
always reference. Any program that printed an integer without also
containing a string literal failed to link. Fix: emit the format strings
unconditionally; the literals loop is the only block now gated on count.

634/634 tests green, ~0.9 s total.
2026-04-22 18:43:15 +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 266a42b05d Add _StringEquals RTL helper and string equality codegen
blaise_arc.c: content-aware _StringEquals compares two Blaise string pointers
by length then memcmp; nil treated as empty string; returns int32 1/0.

Codegen: string '=' and '<>' now route through _StringEquals instead of
ceql (pointer equality) — the '<>' case negates by ceqw result, 0.

3 new tests in cp.test.codegen.pas confirm IR contains $_StringEquals.
626 total tests, 0 failures.
2026-04-22 16:40:17 +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 76f7e104b3 Add TDictionary<K,V> generic map to RTL with tests
Linear-scan hash map backed by two parallel arrays (FKeys: ^K, FValues: ^V).
Operations: Grow, FindKey, Add (upsert), TryGetValue, ContainsKey, Remove.

Key equality uses '=' on the monomorphized type; integer keys work directly
via ceqw in the generated IR.  String key support is deferred until a
content-aware RTL equality helper is added.

TryGetValue avoids True/False constants (not yet in builtins) by computing
the return value via comparison: Result := Idx >= 0.

13 tests across parser, semantic, and codegen layers.  617 total, 0 failures.
2026-04-22 15:58:22 +01:00
Graeme Geldenhuys 3bbb67be0f Support generic class method implementations in separate blocks
Adds the standard Pascal unit structure for generic classes where method
bodies live in the implementation section rather than inline in the class
declaration:

  procedure TList<T>.Add(Value: T);
  begin ... end;

Parser: after reading an IDENT, if '< IDENT (> | ,)' follows, type params
are parsed tentatively; a subsequent DOT marks them as owner type params
(OwnerTypeParams on TMethodDecl) rather than the method's own type params.

Semantic: LinkGenericClassMethodImpls looks up the TGenericTypeDef template
by base name, finds the matching forward declaration, and transfers the body
before instantiation runs.  LinkClassMethodImpls gains a guard to skip
generic-owner entries so they are not mistakenly looked up in FMethodIndex.

RTL: Generics.Collections and Generics.Defaults refactored from inline
bodies to proper interface/implementation split.  604 tests, 0 failures.
2026-04-22 15:36:32 +01:00
Graeme Geldenhuys 15c94646a6 Add Generics.Defaults RTL unit with tests
Adds IEqualityComparer<T> and IComparer<T> generic interface declarations
alongside TIntegerEqualityComparer and TIntegerComparer concrete implementations.

13 tests cover parse, semantic, and codegen for generic interface instantiation,
itab emission, and interface dispatch.  All 595 tests pass.
2026-04-22 14:40:10 +01:00
Graeme Geldenhuys 3289be5313 Add unary minus and two-token lookahead to expression parser
Synthesise '-X' as '0 - X' in ParseFactor.  The recursive call uses
Self.ParseFactor because a bare self-name in {$mode objfpc} reads the
Result variable rather than recursing — leaving NegNode.Right pointing
at uninitialised stack memory and crashing on teardown.

Add FLookahead2 / PeekKind2 so the generic-type-args heuristic can
require '<' Ident ('>'|',') before treating '<' as opening a generic
instantiation — otherwise expressions like 'if A < B then' are misread
as generic invocations.
2026-04-22 14:27:06 +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 f05982f8c5 Rename Collections.pas to Generics.Collections.pas; support dotted uses names
Parser: extend UsesClause to accept dotted unit names (IDENT { DOT IDENT }),
so 'uses Generics.Collections;' parses correctly. The dot-separated
segments are joined into a single string in UsedUnits (e.g. 'Generics.Collections').

RTL: rename Collections.pas → Generics.Collections.pas and update the
unit header to 'unit Generics.Collections' to match FPC's naming and
mirror Delphi's System.Generics.Collections for source-level compatibility.

Grammar: add UnitName rule documenting the dotted-name syntax and its
path-separator semantics.

Tests: 1 new parser test (TestProgramWithDottedUnitName); 569 total, 0 failures.
2026-04-22 08:57:47 +01:00
Graeme Geldenhuys 6c3de2b39a Add TList<T> RTL unit and complete test suite with Grow/Delete/Clear
Collections.pas: full TList<T> implementation in Blaise source —
Add (with dynamic Grow via realloc), Get, Delete (shift-left), Clear,
Free, and Count property. FData: ^T backed by a flat typed pointer array;
SizeOf(T) used for element-stride arithmetic in Add/Get/Delete.

Tests: 12 tests in cp.test.tlist covering parser, semantic (SizeOf,
nil/Pointer compat, ^T instantiation), and codegen (SizeOf literal,
storew/loadw through typed pointers, calloc for Create, realloc for Grow).
2026-04-22 08:48:33 +01:00
Graeme Geldenhuys 19eaeb35ff Implement TList<T> foundations: SizeOf, ^T substitution, pointer compat
Compiler additions needed for TList<T> generic dynamic list:

- SubstTypeParam helper: handles ^T → ^Integer substitution in generic
  instantiation (the direct SameText check missed caret-prefixed types)
- Type param scope: push T=Integer bindings before analysing generic
  method bodies so that SizeOf(T) and local 'var P: ^T' resolve correctly
- SizeOf(TypeName) built-in: semantic returns Integer type with arg's
  ResolvedType set; codegen emits 'copy N' using TTypeDesc.ByteSize
- Pointer type compatibility: nil → ^T, Pointer ↔ ^T, and ^T ↔ ^T with
  same BaseType are now allowed in CheckTypesMatch and comparisons
- Constructor allocation changed from malloc to calloc so that all class
  fields (including FData/FCount/FCapacity in TList<T>) start zeroed

Tests: 11 new tests in cp.test.tlist.pas; 5 existing malloc assertions
updated to calloc; 567 total, 0 failures.
2026-04-22 08:36:31 +01:00
Graeme Geldenhuys 0cc28d395e Update grammar.ebnf to Phase 3: generics, interfaces, properties, pointers
Rewrote the EBNF grammar to reflect all language features implemented
through Phase 3 of the Blaise compiler:

- Full token vocabulary: IS, AS, NIL, VIRTUAL, OVERRIDE, TRY, EXCEPT,
  FINALLY, RAISE, UNIT, INTERFACE, IMPLEMENTATION, DOWNTO, CARET,
  NOT_EQUALS, LESS_EQ, GREATER_EQ, and soft keywords PROPERTY/READ/WRITE
- Unit grammar: UNIT, InterfaceSection, ImplementationSection, UsesClause
- Generic class definitions: CLASS < TypeParamList >
- Interface definitions with optional parent
- Property declarations with soft-keyword read/write accessors
- StandaloneDecl for TTypeName.Method implementation form
- Pointer type syntax: ^TypeName (recursive), pointer dereference (^),
  PointerWriteStmt (P^ := Expr)
- Complete statement hierarchy: if/while/for/try-finally/try-except/raise
- Complete expression hierarchy: Expr → Comparison → Additive → Term → Factor
- Factor includes NIL, generic function calls, type casts (via IDENT LPAREN)
- Updated built-ins section (GetMem/FreeMem/ReallocMem, Pointer type)
- Semantic disambiguation notes for generics, virtual dispatch, interfaces
- ARC implicit operations section (string RC, block exit, exception paths)
2026-04-22 07:50:41 +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 a3a57df8a7 Implement standalone generic function monomorphization
Adds demand-driven instantiation for 'function Name<T>(Param: T): T' syntax.
Templates registered on declaration; concrete instances created on first call
site (e.g. Identity<Integer>) with substituted param/return types, shared
body analysis, and QBE-mangled emission ('Identity_Integer'). 14 new tests,
541 total.
2026-04-21 23:26:52 +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 ffcb1a041f Update Phase 3 interface status and design notes in design doc
Mark is/as interface operators, IInterface built-in, and 2-field typeinfo
(with impllist) as Done.  Expand TYPEID entry to cover the impllist
data layout.
2026-04-21 21:51:24 +01:00
Graeme Geldenhuys 514c0fb122 doc: readme status update 2026-04-21 21:49:22 +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 c13901a2e2 Add Phase 3 implementation status section to design doc
Covers interfaces (10 items) and generics (6 items), each as a separate
subsection with Pending status rows and implementation notes. Records the
agreed implementation order (interfaces before generics) and the Phase 3
milestone (TList<Integer> + TDictionary<string,Integer>, zero valgrind leaks).
2026-04-21 17:56:04 +01:00
Graeme Geldenhuys 4e54c12cad Move macOS ARM64 target to Phase 5 alongside self-hosting
Cannot be tested on Linux; Darwin linking has enough quirks (libSystem,
fat-binary RTL) that shipping untested support would just break the first
macOS user. Phase 5 is the right home — LLVM backend work and CI/CD pipeline
for all platforms are already there.

Phase 2 is now fully complete on Linux x86_64.
2026-04-21 17:35:08 +01:00
Graeme Geldenhuys 5840346a98 Add separate method implementations and Free built-in; Phase 2 milestone reached
Separate method implementations: `procedure TFoo.Bar(...)` can now appear
outside the class definition. Parser detects qualified names in ParseMethodDecl
and makes the body optional for forward-only class declarations. Semantic pass
links standalone bodies back to class method declarations via LinkClassMethodImpls
before AnalyseMethodBodies runs, so all existing method analysis and codegen paths
are reused unchanged.

Free built-in: `Obj.Free` with no user-defined Free method emits `call $free(l ptr)`.
Semantic pass recognises the call and sets ResolvedMethod := nil as a signal;
codegen handles nil method as a built-in free before the normal dispatch path.

Phase 2 milestone verified: a linked list using TNode (TObject subclass) with
separate method impls, Create, and Free compiles and runs with zero valgrind errors
(3 allocs, 3 frees, 0 bytes in use at exit).

Design doc updated: new rows for is/as, ARC exception cleanup, separate method
impls, and Free built-in; Immediate Next Steps trimmed to macOS ARM64 only.

469 tests, 0 failures.
2026-04-21 17:33:39 +01:00
Graeme Geldenhuys fa536a917a Add ARC cleanup on exception paths in try/finally
On the exception path of a try/finally block, release in-scope string
variables before re-raising so that heap-allocated string buffers are
freed even when exceptions unwind through the frame.

After each StringRelease the stack slot is zeroed so that nested
finally handlers see a nil pointer and take the no-op release path,
preventing double-free on deep exception chains.

Adds FCurrentBlock field and EmitExcPathArcCleanup to TCodeGenQBE.
Two new codegen tests verify position and zeroing behaviour.
2026-04-21 15:34:09 +01:00
Graeme Geldenhuys 62026fb00a Add is/as type-test operators with runtime typeinfo dispatch
- AST: TIsExpr, TAsExpr nodes (obj + type name, ResolvedType set by semantic)
- Lexer: tkIs, tkAs tokens (IS/AS are true keywords, handled in MapKeyword)
- Parser: parse 'Obj is T' and 'Obj as T' at comparison precedence level;
  right-hand side is a type name (identifier), not a sub-expression
- Semantic: AnalyseIsExpr (result Boolean), AnalyseAsExpr (result = target type);
  both require class type on left, known class type on right
- CodeGen: EmitTypeInfoDefs emits $typeinfo_T = { l parent_or_0 } per class;
  vtable slot 0 is now the typeinfo pointer (methods shift to (slot+1)*8);
  EmitIsExpr emits call $_IsInstance; EmitAsExpr emits branch + _Raise_InvalidCast
- RTL: BlaiseTypeInfo struct, _IsInstance (walks parent chain), _Raise_InvalidCast
- 21 new tests (481 total); all existing vtable dispatch tests pass after slot shift
2026-04-21 14:45:21 +01:00
Graeme Geldenhuys c1d53e7be6 Implement setjmp/longjmp-based exception dispatch in QBE IR
- Add blaise_exc.c to RTL: _PushExcFrame, _PopExcFrame, _Raise,
  _CurrentException, _Reraise using thread-local setjmp/longjmp frames
- Update rtl/Makefile to compile and archive blaise_exc.c
- Rewrite EmitTryFinallyStmt: alloc16 frame on stack, push+setjmp at try
  entry, finally body emitted on both normal and exception paths, _Reraise
  propagates to enclosing handler on exception path
- Rewrite EmitTryExceptStmt: same frame setup, except handler entered via
  jnz on setjmp return, _PopExcFrame before handler body
- Add 6 codegen tests for setjmp-based dispatch patterns (460 tests total)
- Update design doc: mark try/except/finally as Done, update next steps
2026-04-21 14:07:04 +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 bca985e8cd Update Phase 2 implementation status in design doc
Reflect completed work: class methods, var parameters, unit
interface/implementation, full ARC (compiler + RTL), and string
concatenation. Mark try/except/finally as partial. Update immediate
next steps to virtual dispatch, real exception unwinding, and macOS target.
2026-04-20 23:44:37 +01:00
Graeme Geldenhuys e555f1dea9 Implement ARC RTL and wire string header layout end-to-end
- blaise_arc.c: implement _StringAddRef, _StringRelease, _StringConcat
  using a 12-byte header (refcnt/length/capacity) at the string pointer;
  refcnt=-1 marks immortal static strings; _StringConcat returns refcnt=0
  so the compiler-inserted _StringAddRef at assignment brings it to 1
- EmitDataSection: string literals now include the 12-byte immortal header
  so _StringAddRef/_StringRelease are safe when called on literal values
- EmitWrite: add 12 to string pointer before calling printf so char data
  is passed correctly (header is now part of every string allocation)
- Update TestWriteLn_StringLit_CallsPrintf to check for the +12 offset
  instruction instead of the no-longer-direct label argument
2026-04-20 23:40:53 +01:00
Graeme Geldenhuys 54bfe6e93a Add compile-time ARC for string value params and string concatenation
- String value parameters now receive _StringAddRef on function entry
  and _StringRelease on exit; var parameters are left untouched (caller owns)
- String + string binary expression lowers to $_StringConcat RTL call
- Semantic analyser accepts string + string, resolving result as string type
- Six new tests covering all ARC scenarios in cp.test.arc
2026-04-20 23:30:10 +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 4c6c3d9b2c Add unit interface/implementation: lexer, AST, parser, semantic, codegen
TUnit AST node with IntfBlock/ImplBlock; ParseUnit and ParseForwardDecl;
AnalyseUnit with signature verification and missing-impl detection;
GenerateUnit with export prefix for interface-declared functions.
2026-04-20 23:10:45 +01:00
Graeme Geldenhuys 90d9ac1ef1 Add try/finally, try/except, and raise statement support
Adds tkTry/tkFinally/tkExcept/tkRaise tokens, TTryFinallyStmt,
TTryExceptStmt, and TRaiseStmt AST nodes, recursive-descent parser
for all three forms (bare raise supported), semantic validation
(raise requires a class expression), and QBE codegen.

try/finally emits both bodies sequentially (simplified; full
stack unwinding requires RTL). try/except emits the try body then
skips to an except_handler label (reachable via raise dispatch in
a future phase). raise emits call $_Raise(l <obj>).

26 new tests in cp.test.exceptions (378 total, all passing).
2026-04-20 22:57:10 +01:00
Graeme Geldenhuys d7b42180f1 Add for-loop support (to and downto forms)
Adds tkFor/tkTo/tkDownto lexer tokens, TForStmt AST node, parser
support, semantic validation (ordinal loop variable, type-matched
bounds), and QBE codegen (init storew, cslew/csgew condition,
add/sub increment, jmp back to condition label).
26 new tests in cp.test.forloop (352 total, all passing).
2026-04-20 22:51:51 +01:00
Graeme Geldenhuys e06c7e3b91 Add CLAUDE.md to .gitignore
CLAUDE.md is a private Claude Code session notes file and should
never appear in the repository history.
2026-04-20 22:45:38 +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 4105a0f03b Add while loops
while/do loops with Boolean condition type-checking and compound
begin/end body support. QBE emits an explicit jmp into the condition
block, jnz to branch into body or exit, and a back-edge jmp at the
end of the body block.

13 new tests; 307 total, 0 failures.
2026-04-20 22:24:51 +01:00
Graeme Geldenhuys cb595cf869 Add if/else, comparison operators, and compound statements
if/else statements with full comparison support (=, <>, <, >, <=, >=).
Comparisons produce Boolean; if condition is type-checked to require
Boolean. Compound begin/end statements work as then/else branches.
QBE emits signed comparison instructions (ceqw, cnew, csltw, etc.)
with jnz/jmp control flow and unique numbered labels.

40 new tests; 294 total, 0 failures.
2026-04-20 22:21:10 +01:00
Graeme Geldenhuys c6683522f7 Add standalone procedures and functions
Standalone procedure/function declarations are now supported at the
program block level (after the var section, standard Pascal order).
Includes TFuncCallExpr for function calls in expression context,
full semantic validation (arg count/types, Result variable), and
QBE IR emission without a Self parameter. Mutual calls between
standalone procs/funcs work because all signatures are registered
before any body is analysed.

28 new tests; 254 total, 0 failures.
2026-04-20 19:58:38 +01:00
Graeme Geldenhuys 0fbeea1f9e Add function methods with Result variable and call expressions
Lexer:
- Added tkFunction keyword token.

AST:
- TMethodDecl gains ReturnTypeName (string) and ResolvedReturnType
  (TTypeDesc, nil = procedure).
- TMethodCallExpr: new expression node for obj.Method(args) in rvalue
  context; carries ObjectName, Name, Args, ResolvedClassType,
  ResolvedMethod — set by uSemantic.

Parser:
- ParseMethodDecl(IsFunction): dispatches on tkFunction vs tkProcedure;
  for functions, consumes ': ReturnType' after the parameter list.
- ParseClassDef: loops on tkProcedure or tkFunction tokens.
- ParseFactor: IDENT.IDENT followed by '(' now produces TMethodCallExpr
  with an inline arg list; without '(' stays TFieldAccessExpr.

Semantic:
- AnalyseTypeDecls: resolves ReturnTypeName → ResolvedReturnType for
  each function method alongside param type resolution.
- AnalyseMethodDecl: for function methods, defines 'Result' as a
  skVariable of the return type in the method scope, making it
  assignable inside the body.
- AnalyseMethodCallExpr: validates object type, looks up method,
  verifies the method has a return type (not a procedure), checks arg
  count and types; sets ResolvedClassType and ResolvedMethod.
- AnalyseExpr: handles TMethodCallExpr before TFieldAccessExpr.

Code generator:
- EmitMethodDef: emits 'function <qtype> $T_M(...)' for function
  methods; allocates and zero-inits %_var_Result; after EmitBlock,
  loads Result and emits 'ret <temp>'.
- EmitExpr: handles TMethodCallExpr — loads Self, builds arg list,
  emits 'call $T_M(...)' into a typed temp, returns the temp.

Tests: 226 unit tests (22 new function-method tests), 0 failures.
2026-04-20 19:48:49 +01:00
Graeme Geldenhuys 313619511c Add class instance methods (procedure, inline body, Self, method calls)
Lexer:
- Added tkProcedure keyword token.

AST:
- TMethodParam: stores parameter name, type string, and resolved type.
- TMethodDecl: method declaration with name, param list, and inline body
  (TBlock). Avoids TObject.MethodName collision by naming the field Name.
- TClassTypeDef.Methods: owned TObjectList of TMethodDecl.
- TMethodCallStmt: statement node for obj.Method(args) calls; ObjectName
  and Name fields; ResolvedClassType and ResolvedMethod set by semantic.
- TBlock forward-declared before TMethodDecl to break circular dependency.

Parser:
- ParseMethodDecl: parses 'procedure Name [(ParamList)] ; Block ;' inside
  a class body.
- ParseParamList: handles 'Name : Type [; Name : Type ...]' param groups.
- ParseClassDef: field parsing followed by method parsing before 'end'.
- ParseStmt: after 'IDENT.IDENT', dispatches to TFieldAssignment (':=')
  or TMethodCallStmt (everything else, optionally with arg list).
- ParseMethodCallArgList: argument list for method call statements.

Semantic:
- FMethodIndex: TStringList mapping 'TypeName.Name' -> TMethodDecl for
  O(log n) dispatch lookup.
- AnalyseTypeDecls: indexes methods and resolves param types after fields.
- AnalyseMethodBodies: analyses each method body after type registration.
- AnalyseMethodDecl: pushes a scope with Self (class type) and explicit
  params, then calls AnalyseBlock for the method body.
- AnalyseMethodCall: resolves object type, looks up method, checks arg
  count and types; sets ResolvedClassType and ResolvedMethod on the node.

Code generator:
- EmitMethodDefs: emits a QBE function for every method in every class
  type before the main function.
- EmitMethodDef: emits 'function $TypeName_Name(l %_par_Self, ...) {'.
- EmitParamAllocs: allocates stack slots for Self and explicit params.
- EmitMethodCall: loads Self from the caller's var slot, then emits
  'call $TypeName_Name(l selfTemp, ...)'.

Tests: 204 unit tests (24 new method tests), 0 failures.
Grammar: docs/grammar.ebnf updated with MethodDecl, ParamList, MethodCall.
2026-04-20 19:42:09 +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 8122b0a299 Wire semantic analysis pass into the compiler pipeline
The semantic analyser now runs between parsing and code generation.
Type mismatches, undeclared identifiers, and duplicate declarations are
reported to stderr with source position and exit code 1 before any IR
is emitted.
2026-04-20 18:01:03 +01:00
Graeme Geldenhuys 4fbfdfdbba Add semantic analyser with type inference and checking
TSemanticAnalyser walks the AST, resolves all identifiers via the symbol
table, annotates every expression node with ResolvedType, and type-checks
assignments and procedure calls. Raises ESemanticError with source position
on undeclared identifiers, type mismatches, duplicate declarations, and
unknown types. TProgram now owns the TSymbolTable after analysis so
ResolvedType pointers remain valid for the lifetime of the AST.

Also adds the 'div' keyword for integer division throughout the pipeline
(lexer, parser, semantic). 26 new FPCUnit tests, 126 total, all passing.
2026-04-20 17:58:27 +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
Graeme Geldenhuys 95e1ea58af Add FPC-compatible CLI mode to Blaise compiler
Blaise can now act as a drop-in FPC replacement for PasBuild when
invoked via `pasbuild compile --fpc /path/to/blaise`.

- Detects FPC-style invocation (single-dash flags vs. native double-dash)
- Handles -iV / -iTP / -iTO probe queries PasBuild uses before compiling
- Maps -FE<dir>, -o<name> to output path; silently ignores -Fu, -FU,
  -Mobjfpc, -O<n>, -g, -gl, -CX and unknown single-dash flags
- Native --source / --output / --emit-ir mode is fully preserved
2026-04-20 17:37:14 +01:00
Graeme Geldenhuys 25de8be212 Fix compilation errors; vendor QBE 1.2; end-to-end Hello World works
Fix TProgram.Uses reserved-word clash (renamed to UsedUnits), nested
brace-comment in uParser.pas, and missing uAST in Blaise.pas uses clause.

Vendor QBE 1.2 source in vendor/qbe/ (built locally with make; binary
excluded via .gitignore). End-to-end pipeline confirmed: Blaise compiles
Hello World and Calc (6*7=42) to native x86_64 Linux binaries.

71/71 FPCUnit tests passing.
2026-04-20 16:38:57 +01:00