Implements `for X in Collection do` iteration using the GetEnumerator
protocol, matching FPC/Delphi semantics. This first commit covers
class-based enumerators; static array and byte-string iteration are
deferred to follow-on commits.
Compiler changes:
- uAST: new TForInStmt node (VarName, CollExpr, Body; semantic
annotations: EnumVarName, ResolvedVarType, GetEnumDecl, MoveNextDecl,
CurrentDecl)
- uParser: ParseForStmt returns TASTStmt; detects 'for X in' vs
'for X :=' and produces TForInStmt or TForStmt respectively
- uSemantic: AnalyseStmt handles TForInStmt — verifies GetEnumerator/
MoveNext/Current protocol, checks type compatibility, injects a
synthetic TVarDecl (__forin_N) into the enclosing block so
EmitVarAllocs allocates the slot and EmitArcCleanup releases it
- uCodeGenQBE: EmitForInStmt — calls GetEnumerator, ARC-stores result
in synthetic slot, loops via MoveNext, reads Current, assigns to
loop variable; handles both static and virtual dispatch
- uDebugOPDF: CollectStmtLines extended with TForInStmt case
RTL additions:
- classes.pas: TStringListEnumerator + TStringList.GetEnumerator
- contnrs.pas: TObjectListEnumerator + TObjectList.GetEnumerator
Docs:
- grammar.ebnf: ForStmt gains the 'FOR IDENT IN Expr DO Stmt' alternative
- language-rationale.adoc: new Iteration section documenting the protocol
choice and what is deferred
Tests: 17 new tests in cp.test.forin (parser, semantic, codegen);
1089 total, all passing.
Adds compiler/src/it/ (Maven-convention integration test directory) with
a Blaise-specific PDR driver. Four initial tests ported from the OPDF
integration suite: breakpoint+next, local variables, locals command,
and step-over. Test programs are adapted for Blaise (no FPC directives),
line numbers preserved to match the original commands files.
The pasbuild-integration-test plugin (phase: none) can be invoked as
'pasbuild integration-test'; it verifies pdr and the Blaise binary are
present, then delegates to compiler/src/it/run_tests.sh.
All four tests currently fail — line info in the OPDF section maps every
statement to the function-start address (per-stmt addresses require QBE
changes, tracked in future-improvements.adoc). The aspirational expected
files show exactly what each test should produce once OPDF is complete,
making failures a clear roadmap rather than noise.
Also adds PIE/ASLR support entry to future-improvements.adoc (Linux,
FreeBSD, macOS load-base strategies for the PDR debugger).
- uDebugOPDF: add EmitFunctionScope_Main to emit recFunctionScope keyed on
label 'main' for the program body, including per-line recLineInfo records
- uDebugOPDF: refactor EmitLineInfoForScope into EmitLineInfoForBlock so
both procedure scopes and the main body share one implementation
- Blaise: pass -no-pie to gcc when linking a debug-opdf build so OPDF
.quad addresses are absolute runtime addresses; PIE relocation produces
relative offsets that the debugger cannot use without the load base
- Tests: three new assertions covering recFunctionScope, LowPC, and line
info for the main program body (28 OPDF tests total, all passing)
Standalone procedures/functions now emit 'export function' in QBE IR
instead of bare 'function'. Global data items now emit 'export data'.
This makes these symbols globally visible (.globl in assembly output),
which is required for the OPDF companion file to reference them via
.quad directives at link time.
Without export, QBE emits local-only labels that the linker cannot
resolve from a separate object file (.opdf.s).
New unit uDebugOPDF.pas with TOPDFEmitter:
- EmitHeader, EmitSection: writes .opdf ELF section with OPDF magic header
- EmitPrimitive, EmitEnum, EmitRecord, EmitClass, EmitAnsiStr: type records
- EmitGlobalVar: recGlobalVar with linker-resolved .quad address
- EmitFunctionScope: recFunctionScope with LowPC/HighPC labels
- EmitParameters: recParameter for each proc/func parameter
- EmitLocalVars: recLocalVar with RBP-relative stack offsets
- EmitLineInfoForScope: recLineInfo records from AST line numbers
(uses function-start label as address; per-stmt addr needs QBE changes)
- CollectStmtLines: recursive AST walker for unique (line, col) pairs
- FNV-1a 32-bit TypeID allocation; ScopeID counter for scope linking
- DoEmit/PatchTotalRecords: idempotent emission with TotalRecords patching
Blaise.pas pipeline:
- --debug-opdf / -g flag: creates companion .opdf.s alongside output binary
- CompileToNative passes companion file to linker
- RunProcess: add poUsePipes so TProcess.Output is valid (pre-existing crash)
Tests: cp.test.opdf.pas with 22 tests covering all record types, fields,
addresses, and line info. Validated end-to-end with opdf_dump.
{$H+} is required for FPC compilation (string = AnsiString); not needed
when compiled by Blaise itself (Blaise defaults to AnsiString).
TDuplicates = (dupAccept, dupIgnore, dupError) replaces the integer
constant workaround that was needed before enum type support existed.
FDuplicates field and Duplicates property are now typed TDuplicates;
the constructor initialises with dupAccept and Add() compares against
dupIgnore by name rather than the magic literal 1.
All 1035 compiler tests pass.
Write/WriteLn now call _SysWriteStr/_SysWriteInt/_SysWriteInt64/
_SysWriteNewline (blaise_sys.pas + blaise_sys_posix.c) instead of
printf/fprintf. _SysWriteNewline uses POSIX write(2) directly;
no format strings, no libc I/O buffering.
Pure Pascal implementations replace these libc calls in the RTL:
- toupper / tolower (inline ASCII arithmetic in blaise_str.pas)
- strlen (NUL-scan loop in blaise_str.pas)
- memcpy (byte-copy loop in both blaise_str.pas and blaise_arc.pas)
- memcmp (byte-compare loop in blaise_arc.pas)
Remaining libc dependency: malloc/free (needs Pascal allocator — Step 9d).
All 1035 compiler tests pass.
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.
Remove blaise_str.c from the RTL archive and replace it with object code
compiled from blaise_str.pas via the Blaise compiler itself. The variadic
_StringFormat cannot be expressed in Pascal so it moves to blaise_str_fmt.c.
Makefile changes:
- blaise_str.c removed from C_SRCS; blaise_str_fmt.c added in its place
- New Pascal build rules: .pas → .ssa (unit IR via build driver + sed strip)
→ .s (QBE) → .o (gcc -c) → included in blaise_rtl.a
- blaise_str_build_driver.pas: minimal program driver used by the Makefile
to compile the unit; the program section is stripped before assembly
All 1035 compiler tests pass with the new RTL.
- Add `mod` operator: tkMod/boMod in lexer+AST+parser, QBE `rem` in codegen
- Allow assignment to value parameters (skParameter) in semantic checker
- PChar + Integer arithmetic: semantic and codegen now accept PChar on pointer-arithmetic LHS
- PChar nil comparison: nil is now compatible with PChar in all comparison/assignment contexts
- Untyped Pointer ↔ PChar: CheckTypesMatch accepts PChar where Pointer expected and vice-versa
- Type cast widening fix: Int64(IntExpr) emits `extsw` instead of invalid `l copy w`
- CoerceArg helper: sign-extends w arguments to l at all call sites (proc/func/method)
- blaise_str.pas: Pascal port of blaise_str.c — compiles cleanly and passes runtime test
All 1035 tests pass. blaise_str driver produces correct output for IntToStr, StrToInt,
StringLength, StringUpperCase, StringLowerCase, StringTrim, StringPos, StringCopy, Chr.
Add the 'external' directive to proc/function/method declarations,
enabling Pascal code to bind to C symbols without a body:
procedure Foo; external;
function Bar: Integer; external name 'c_bar';
- uLexer: tkExternal token; mapped in the identifier fast-path
- uParser: ParseMethodDecl and ParseForwardDecl consume 'external'
and optional 'name <string>'; body-trigger check is suppressed for
external declarations so the following 'begin' is not consumed as a
method body
- uAST: TMethodDecl.IsExternal, TMethodDecl.ExternalName
- uSemantic: external interface decls are exempt from the
"no implementation" check; AnalyseStandaloneDecl skips AnalyseBlock
when IsExternal is set
- uCodeGenQBE: EmitProcCall and TFuncCallExpr codegen use ExternalName
as the QBE symbol when set; no function body emitted for external decls
- grammar.ebnf: MethodDirective / ExternalDirective rules; MethodDecl
and StandaloneDecl updated to use { MethodDirective SEMICOLON }
11 new tests in cp.test.external — all 1035 tests pass.
- Fix ParseMethodDecl: add tkConst to body-trigger condition so const
blocks are parsed inside method, procedure, and function bodies
- ParseConstBlock now accepts TObjectList directly (was TBlock) so it
can be called uniformly from all scope positions
- Class body loop: handle CONST sections (→ TClassTypeDef.ConstDecls)
and VAR keyword before field declarations
- TClassTypeDef.ConstDecls: new owned TObjectList of TConstDecl
- TFieldAccessExpr: new IsConstant/ConstValue/ConstString fields set by
uSemantic when TypeName.ConstName resolves to a class-level constant
- uSemantic: register class constants as both unqualified and qualified
(TFoo.MaxItems) symbols; resolve TypeName.ConstName in field-access
analysis before raising "Unknown class method" error
- uCodeGenQBE: emit integer copy or _StringRetain for IsConstant paths
- docs/grammar.ebnf: add ConstSection/ConstDecl/ConstExpr rules;
update Block, InterfaceSection, ImplementationSection, ClassDef, and
GenericClassDef to allow ConstSection
All 1024 tests pass including 15 new const-scope regression tests.
10 tests covering integer consts, negative consts, string consts,
multi-const blocks, two const blocks in one scope, unit interface
const block parsing, unit implementation const block parsing, and
cross-unit export visibility (interface const visible in importing
program via AnalyseUnitForExport).
All pre-existing 1009 tests continue to pass (1019 total).
Updates future-improvements.adoc: const support is complete.
Standalone pasbuild plugin (phase: none) that builds the Blaise compiler
using the Blaise bootstrap binary rather than FPC:
pasbuild blaise-compile
Steps: RTL make+install → emit IR via bootstrap binary → qbe assemble
→ gcc link → compiler/target/blaise
Bootstrap binary resolved from BLAISE_BOOTSTRAP env var or the newest
releases/vX.Y.Z/blaise binary found in the project tree.
Blaise is now fully self-hosting on the true multi-file source.
Fixpoint confirmed: releases/v0.3.0/blaise (Blaise-compiled) recompiles
Blaise.pas and produces byte-identical IR to the FPC-built stage-1.
Blaise already handles FPC-style invocations (IsFPCStyleInvocation,
HandleFPCInfoQuery, ParseFPCArgs), so pasbuild --fpc releases/v0.3.0/blaise
works today. The only remaining improvement is renaming --fpc to --compiler
in PasBuild's CLI for clarity.
Two new future-improvement entries covering the remaining FPC dependencies
in the development cycle:
- Native Blaise test framework: port fptest (DUnit2-based) to pure Blaise
so that cp.test.*.pas units compile and run without FPC; eliminates the
last FPC dependency from pasbuild test
- PasBuild Blaise compiler backend: rename --fpc to --compiler, add backend
detection and Blaise-style command-line construction so that
pasbuild compile --compiler releases/v0.3.0/blaise works correctly
- 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
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
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
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)
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.
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.
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).
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).
- 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
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).
- 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
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).
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.
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.
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.
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)
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.
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.
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.
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.
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
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.
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.
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.
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.