Commit graph

120 commits

Author SHA1 Message Date
Graeme Geldenhuys 73cfddeedd docs: document Step 10/11 deferred items and fixpoint rationale
- language-rationale.adoc: update MaxInt entry — now Integer = 2147483647
  (32-bit) with full rationale explaining the Int64 truncation chain;
  update IntToStr table to note auto-routing to _Int64ToStr for Int64 args
- future-improvements.adoc: add two new entries
  * "Int64 literal range detection in self-hosted binary" — root cause of
    MaxInt workaround; ConstValueInt64 path to restore full Int64 support
  * "const sections in unit interface and implementation blocks" — deferred
    feature blocking dupAccept/dupIgnore/dupError in classes.pas public API
2026-05-01 09:38:07 +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 648d580fbf feat(compiler): remove SysUtils/Classes/Contnrs/Process/StrUtils from FpcRtlUnits skip list — Step 9
These units are now provided by the Blaise RTL, so the unit loader
should resolve and compile them rather than silently treating them
as built-in FPC units.
2026-04-30 11:29:46 +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 62a803bb88 docs: clean up multi-line string literals section in future-improvements
Add Options D (triple single-quote) and E (keyword heredoc) with
before/after visual examples matching the format of Options A–C.
Remove conversational draft text and duplicate implementation notes
that were left from an earlier session.
Each option now shows the current concatenation form alongside the
proposed syntax for direct readability comparison.
2026-04-29 16:54:58 +01:00
Graeme Geldenhuys aa5ff0495b feat(compiler): replace CreateFmt with Create(Format(...)) — Step 7
Replace all 83 .CreateFmt(...) calls across 5 compiler unit files with
.Create(Format(...)) to eliminate dependency on Exception.CreateFmt
(which requires array-of-const / TVarRec, unsupported in Blaise):

  uLexer.pas:       2 replacements
  uParser.pas:      61 replacements
  uSemantic.pas:    8 replacements
  uCodeGenQBE.pas:  10 replacements
  uUnitLoader.pas:  2 replacements

The Format call retains FPC-compatible [args] array notation so the
multi-file source continues to compile under FPC.  The hand source
(blaise-compiler.pas) already uses Format without array brackets and
is unaffected.

All 975 tests pass.  Fixpoint verified.

Also: docs/future-improvements.adoc — multi-line string literals section
expanded with visual before/after examples for each candidate syntax
(heredoc, backtick, triple-brace).
2026-04-29 16:12:52 +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 81920c545a feat(rtl): TStringList — Text property, LoadFromFile, SaveToFile, Strings/Objects indexed properties
- Text: string read GetText write SetText
- SetText: Clear + SplitIntoList(AText, Ord(#10), Self)
- LoadFromFile: SetText(ReadFile(APath))
- SaveToFile: WriteFile(APath, GetText + #10)
- property Strings[Index: Integer]: string read Get write Put
- property Objects[Index: Integer]: Pointer read GetObject write SetObject
- 16 new compiler tests in cp.test.collections covering semantic and
  codegen for all new APIs
2026-04-29 13:07:44 +01:00
Graeme Geldenhuys f5587f18f4 feat(rtl): split TObjectList into contnrs.pas; add Items indexed property
- New rtl/src/main/pascal/contnrs.pas — TObjectList with Items[Index: Integer]
  indexed property (read Get write Put), matching the FPC contnrs layout that
  the compiler units use (uses Contnrs)
- rtl/src/main/pascal/classes.pas — TObjectList removed; Classes now provides
  only TStringList and SplitIntoList
2026-04-29 12:49:06 +01:00
Graeme Geldenhuys 36529f384d docs: document unit file name casing convention and resolution strategy 2026-04-29 12:46:23 +01:00
Graeme Geldenhuys ef4eb10cb8 fix: unit loader — case-insensitive file lookup on case-sensitive filesystems
TUnitLoader.Locate now tries LowerCase(AName) + '.pas' first, then falls
back to the exact case as written in the uses clause.  This means Pascal's
case-insensitive unit names work correctly on Linux and FreeBSD where the
filesystem distinguishes 'Classes.pas' from 'classes.pas'.

Established convention: all Blaise RTL unit files use lowercase names.
Renamed existing RTL files to match (Classes.pas, System.pas,
Generics.Collections.pas, Generics.Defaults.pas).
2026-04-29 12:44:31 +01:00
Graeme Geldenhuys 2f922fb57f docs: integer types, indexed properties — grammar and rationale
- language-rationale.adoc: add Integer Types section (Integer = 32-bit
  fixed, Int64 = 64-bit, Byte = 8-bit; MaxInt constant; IntToStr/Int64ToStr/
  StrToInt/StrToInt64 conversion built-ins; alternatives rejected); add
  Indexed Properties section (decision, rationale, alternatives rejected);
  resolve the open Integer Width question pointing to the new section
- grammar.ebnf: update PropertyDecl to include optional [Param: Type]
  index parameter; update FieldAssignment and Factor to include optional
  [Expr] index on IDENT DOT IDENT; add disambiguation comment
2026-04-29 12:32:37 +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 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 f6856e366a feat: @expr address-of operator for array elements and local variables
Adds the @ prefix operator so callers can take the address of a static-array
element (@Arr[I]), an open-array element, or any local scalar variable and
store or pass the resulting typed pointer (^T).  No load is emitted — the
element address is returned directly from the pointer arithmetic that was
previously discarded after the subscript load.
2026-04-28 13:53:12 +01:00
Graeme Geldenhuys 9d4b2837a1 docs: add future-improvements.adoc with single-precision trig dispatch entry 2026-04-28 13:34:48 +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 e4f3b9e38b feat: Low() / High() for static arrays
Low(A) and High(A) on a static array variable now emit compile-time
constants (copy LowBound / copy HighBound) rather than a runtime slot
load. The semantic pass accepts tyStaticArray alongside tyOpenArray.
4 new tests; all 892 tests pass.
2026-04-28 12:35:05 +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 a644803516 docs: add platform interoperability section to language-rationale
Documents how Blaise's UTF-8 string type interacts with OS APIs:
POSIX/Xlib work directly via PChar; Windows W APIs require UTF-8 to
UTF-16LE conversion at the RTL boundary. Explains why this does not
motivate adding a UTF-16 string type to the language. Phase 6 item.
2026-04-27 14:29:27 +01:00
Graeme Geldenhuys 674d3edc8e docs: add language-rationale.adoc; confirm #N literals work
language-rationale.adoc records design decisions for the Blaise dialect:
single string type, no Char type, Byte subscript semantics, char literal
coercion, PChar (planned), ARC, out parameters, build-tool-drives-compiler,
and three open questions (integer width, enum storage, Result convention).

CLAUDE.md updated to require all future language design decisions to be
documented in language-rationale.adoc.

#N numeric char literals (e.g. #45 = Ord('-')) work without additional
implementation: UnescapeString converts them to single-byte strings,
CoerceToCharOrd handles the rest. Test added to lock in the behaviour.
2026-04-27 14:00:03 +01:00
Graeme Geldenhuys 5dbdc35c1e feat: string subscript S[N] — byte access
S[N] returns the Nth byte (1-based) of a string as Integer.
Single-quoted ASCII literals coerce to their Ord value when compared
with a subscript result; multi-byte literals (e.g. emoji) are a
compile-time error.

Character data lives at string_ptr + 12 (after the 12-byte ARC header),
so S[N] emits: extuw index; add index, 11; add str_ptr, offset; loadub.

4 new tests in cp.test.stringops cover codegen (loadub, copy 104),
non-string subscript error, and multi-byte char literal error.
2026-04-27 13:34:41 +01:00
Graeme Geldenhuys 1ea878000d feat: multi-file compilation (v0.2.0)
Implement whole-programme multi-file compilation. The compiler now
resolves `uses` clauses, locates unit source files via `--unit-path`
search directories, compiles them, and merges exported symbols into
the programme scope. Combined QBE IR is emitted in dependency order.

New components:
- uUnitLoader: post-order DFS unit loader with cycle detection
  (EUnitNotFound, ECircularDependency); skips FPC RTL builtins
- TSemanticAnalyser.AnalyseUnitForExport: promotes unit interface
  symbols to global scope; implementation symbols stay scoped
- TCodeGenQBE.AppendUnit / AppendProgram: accumulate combined IR;
  FStrLitsEmitted tracks emitted string literals to avoid duplicates
- 8 new tests in cp.test.multifile covering loader, semantic export,
  and combined codegen

Language additions:
- `out` parameter modifier (treated as var — pass by reference)
- implementation-section `uses` clause in units

Blaise.pas wired to use loader pipeline; --unit-path flag is
repeatable; -Fu<path> FPC-style flags are honoured.

Design doc updated: Phase 4 (multi-file), phases 5–8 renumbered;
--unit-path added to CLI table; build-tool-drives-compiler principle
documented in constraints.

End-to-end verified: two-unit programme compiles and executes correctly.
2026-04-27 11:35:26 +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 50c746996b codegen: fix var-param forwarding to use loaded pointer
When a var-param actual argument is itself a var parameter, the local
alloc slot holds the caller's address rather than being the address.
Passing %_var_Name directly gave the callee a pointer-to-slot instead
of the original caller's address, causing reads/writes to land in the
wrong location.

Add EmitVarArgAddr() which detects this case and emits loadl %_var_Name
to obtain the stored pointer before passing it. Applied at all seven
call-emission sites in uCodeGenQBE.

Root cause of TStringList_FindSorted writing Mid into Find's local slot
(overwriting the Idx pointer) instead of into the caller's Idx variable,
so LookupLocal always read Idx=0 after a sorted search.

Two regression tests added to cp.test.varparams.
2026-04-24 23:26:40 +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 4797ce71a9 fix: drain child process output to avoid pipe deadlock
RunProcess used poWaitOnExit combined with poUsePipes, which
deadlocks when the child fills the stderr pipe buffer before we
read — the child blocks waiting for the reader, we block waiting for
exit.  Drain the pipe in a loop while the child runs, then wait on
exit separately.  Also fold stderr into stdout so qbe/cc diagnostics
round-trip through a single handle.

rtl/Makefile: install blaise_rtl.a into compiler/target (not
compiler/target/bin) so it lives next to the blaise binary produced
by pasbuild.
2026-04-24 17:42:54 +01:00
Graeme Geldenhuys 979943d274 codegen: short-circuit boolean and/or, fix class-expr field assign
Two codegen bugs surfaced while debugging self-hosting. Both were
previously invisible in unit tests because they only bite when the
compiled programme exercises the affected shapes.

EmitFieldAssignment emitted a spurious `loadl` for the receiver when
ObjExpr was set and IsClassAccess was True, dereferencing the class
pointer a second time. That read the object's vtable, and the ARC
write-barrier then released a method pointer and stored nil into the
vtable slot. Broke statements like `TFoo(X).Field := nil`.

boAnd and boOr were emitted as bitwise `and`/`or`, evaluating both
sides unconditionally. FPC and Delphi use short-circuit by default,
and guarded nil-checks like `(P <> nil) and P.IsX` rely on it. Now
emit a stack-slot-backed branch that skips the RHS when the result is
already determined.

The two boolean-op codegen tests updated to look for the new sc_rhs
and sc_end labels.
2026-04-24 17:42:34 +01:00
Graeme Geldenhuys 2cc192cd57 Stub program unit for the migration analyser. 2026-04-24 08:12:30 +01:00
Graeme Geldenhuys 916bf12af7 build: disable blaise-rtl from default reactor build
RTL Pascal sources (System.pas, Classes.pas, etc.) are compiled by
the Blaise compiler, not FPC. C sources are compiled separately via
GCC. Marking blaise-rtl as activeByDefault=false excludes it from
'pasbuild compile' and 'pasbuild test' reactor runs; it can still be
built explicitly with 'pasbuild compile -m blaise-rtl'.

Also adds a bootstrapExclude entry for System.pas so that if
blaise-rtl is compiled explicitly, the FPC bootstrap program does not
include System (FPC provides it implicitly).
2026-04-24 08:06:01 +01:00
Graeme Geldenhuys 9c4df99aca chore: clean-up .gitkeep files we don't need any more 2026-04-24 07:43: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 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