TMoney is a thin value-type wrapper layering an ISO-4217 currency tag onto
an exact TDecimal amount. The numeric core (TDecimal) stays currency- and
locale-agnostic; currency policy lives entirely in this wrapper, matching
Moneta / money-gem / rusty-money.
Design:
- Currency is an upper-cased ISO-4217 string code; the set is open (unknown
codes are accepted at the fallback minor-unit scale of 2).
- A built-in registry gives each currency its default scale (JPY 0, USD 2,
KWD 3, fallback 2); every TMoney is normalised to its currency's scale on
construction and after every operation, using banker's rounding.
- Cross-currency Add/Subtract/Compare raise EMoneyMismatch (no implicit
conversion); Equals is total (False, not raise, across currencies).
- Immutable value semantics, mirroring TDecimal.
API: free-function constructors (MoneyFromStr / MoneyFromDecimal /
MoneyFromInt / MoneyZero), record methods (Amount, CurrencyCode, IsZero,
Sign, Add, Subtract, Negate, Multiply, MultiplyInt, Compare, Equals,
AmountString, ToString), and the CurrencyScale registry function.
Tests:
- cp.test.numerics.money.pas: 22 IR/semantic tests (resolution, IR shape,
type errors) via TUnitLoader.
- cp.test.e2e.numerics.money.pas: 24 dual-backend e2e tests
(CompileAndRunWithRTL) covering construction + per-currency normalisation,
banker's rounding, case-folding, arithmetic, mismatch raising,
Compare/Equals/IsZero/Sign, the registry, and a realistic invoice flow.
Docs: language-rationale.adoc gains "Currency Amounts — TMoney Wraps
TDecimal, Currency Is a String Tag" (decision + alternatives);
future-improvements.adoc marks Numerics.Money as implemented.
Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK; full suite
green on both the QBE-built and native-built runners (3671 tests).
A single exact base-10 decimal type for financial / exact-decimal use,
replacing the historical Currency/Comp/Extended proliferation with one type.
- Construction: DecFromInt/Int64/Str, plus a safe/exact float split
(DecFromFloat takes the shortest decimal — 0.1 stays 0.1 — while the
dangerous binary-exact path is the explicitly-named DecFromFloatExact).
- Value semantics + value-based equality: 2.0 = 2.00 with a consistent hash,
so it is safe as a dictionary key (unlike Java BigDecimal).
- Arithmetic: Add/Subtract (scale = max), Multiply (scale = sum), Negate, Abs,
arbitrary precision via a decimal-digit magnitude (compact Int64 fast path
inflating on overflow).
- Division + rounding: a layered design — a TRoundingMode enum (8 modes,
banker's default) over an IRoundingStrategy interface users can implement
for custom rounding. Division always carries an explicit scale + mode.
- Formatting: ToString/ToPlainString never use scientific notation;
StripTrailingZeros keeps integer zeros (600 stays 600, never 6E+2).
- Conversions out: ToDouble (lossy), ToInt64 (truncating).
IR tests (32) + e2e tests (46). Add/Subtract/Multiply and the value-semantics
run dual-backend; Divide/RoundTo and float conversion run QBE-only pending
native codegen fixes (logic verified on QBE; see bugs.txt).
Accept an anonymous integer subrange as a set base type, e.g.
'set of 0..255' or 'set of 1..10', in both the inline (var/param/field)
and 'type T = set of L..H' declaration positions. The subrange lowers to
the same bitmap machinery as 'set of Byte': the bitmap is sized to H+1
bits and member ordinals are the integer values themselves. Bounds may be
integer literals, named constants, or constant expressions; the lower
bound must be >= 0, the upper <= 255, and the range ascending.
Also fix 'set of Boolean' so a Boolean operand is accepted directly on
either side of 'in' and as the second argument of Include/Exclude, without
an intervening Ord().
Parser handles both 'set of' paths (ParseTypeName and ParseSetDef);
semantic resolution adds ResolveSubrangeSetType for on-demand and
type-decl paths. Adds dual-backend e2e tests and semantic unit tests.
Updates grammar.ebnf and language-rationale.adoc per the language-decision
rule, and removes the now-implemented item from future-improvements.adoc.
`set of byte` and `set of Boolean` are implemented (issue #105) — named ordinal
base types lower to a bit set (byte uses the 256-bit jumbo path), with working
membership/Include/Exclude and [lo..hi] range literals on both backends. The
section is rewritten to record that, leaving only the genuinely-remaining work:
* the anonymous integer-subrange base form `set of 0..255` / `set of 1..10`,
still rejected at parse time ("Expected enum type or '(' after 'set of'");
* a minor `set of Boolean` follow-on where `True in s` is rejected by the `in`
type-check (Ord(b) in s works).
Replace the FPC/Delphi replace surface (ReplaceStr, ReplaceText, and the
proposed StringReplace + TReplaceFlags set) with two self-explanatory
functions:
Replace(S, Old, New) — replaces the FIRST occurrence
ReplaceAll(S, Old, New) — replaces EVERY occurrence
Both are case-sensitive and literal (non-pattern). This sheds the legacy
redundancy: ReplaceStr/ReplaceText were two names for one operation split by
a 'String vs Text' distinction that is not obvious, and Delphi's StringReplace
encodes the same two booleans (all-vs-first, sensitive-vs-insensitive) as an
awkward flag-set. Modern languages (Go, Python, Java, Rust) use a plain
first/all split with no case flag.
Case-insensitive replace is expressed by lower-casing the inputs with the
built-in LowerCase/UpperCase before calling Replace/ReplaceAll; a
case-*preserving* insensitive replace and pattern/regex matching are deferred
and recorded in docs/future-improvements.adoc.
The internal worker keeps the existing TStringBuilder-based loop; it gains a
FirstOnly flag and drops the now-unused case-fold path.
No callers of the removed functions existed anywhere in the tree. IR/semantic
and e2e tests updated to the new names; both fixpoints green (FIXPOINT_OK,
NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK); full suite OK (3346 tests).
A set literal element may now be a constant range:
e := [Red..Blue]; { {Red, Green, Blue} }
e := [m0, m2..m4, m7]; { ranges mix with single members }
The parser builds a transient TSetRangeExpr for each lo..hi element; the
semantic pass (AnalyseSetLiteralExpr) expands it into the individual member
idents before any other consumer runs, so overload resolution, the bitmask
folder, the jumbo-set path, and both code generators see an ordinary member
list and need no range-specific logic.
Both bounds must be compile-time constants of the set's base type. A
reversed constant range [Blue..Red] is a compile-time error rather than a
silently empty set — matching Blaise's preference for rejecting
confusing-but-legal constructs over FPC's empty-set-with-warning behaviour.
Variable bounds [lo..hi] are rejected ("set range bound must be a
constant"); runtime-variable ranges are deferred.
TSetRangeExpr is wired into the .bif serialiser and the AST cloner so a
generic template body containing an unexpanded range round-trips through
separate compilation (verified by hand: a generic returning [m1..m3]
compiled to .o, then instantiated from the .bif with source hidden).
Set base types remain enumerations; the issue's set-of-byte example needs
ordinal-base set types, tracked separately in docs/future-improvements.adoc.
Range expansion is base-type-agnostic, so ranges will work for those
automatically once they land.
Docs: grammar.ebnf SetElement rule; language-rationale.adoc decision +
alternatives. bif-coverage.status regenerated for the new node.
FIXPOINT_OK + NATIVE_FIXPOINT_OK; full suite OK (3201 tests, 11 new).
pdr now resolves the ASLR slide correctly (load base from the
binary's offset-0 mapping), so the -no-pie guard in both native link
paths is no longer needed. Debug binaries are position-independent
again, matching the platform default.
Verified under live ASLR: breakpoints, var-param drilldown, captured
vars, dynamic arrays, TList<T> inspection, callstack and stepping all
work against PIE binaries.
Remove the 'PIE (ASLR) support in PDR' section from
future-improvements.adoc — implemented.
Make parentheses mandatory on every function, procedure, method, and
constructor call — even those with zero arguments. A bare identifier
or field access is now unambiguously a variable/field/property read;
appending () makes it a call.
Mechanically migrated all 144 source files (compiler, runtime, stdlib,
tests, kanban tool). Fixed several latent bugs exposed by the AST node
transition from TFieldAccessExpr.IsMethodCall to TMethodCallExpr:
- IsBuiltinToString applied to record methods (added tyClass guard)
- IsVarParam not set for value record/static-array parameters in
AnalyseMethodCallExpr (extended to check skParameter + aggregate type)
- Native backend used movq (pointer load) for record receivers instead
of leaq (address-of) in EmitMethodCallExpr
- ResolveDiamond now handles TMethodCallExpr for diamond-operator
constructor calls
Updated grammar.ebnf (MethodCall, ProcCall, Factor rules) and
language-rationale.adoc with the design decision. Marked the
future-improvements.adoc entry as implemented.
All 2627 tests pass. Fixpoint verified (FIXPOINT_OK).
Reapply the const-parameter ARC elision that was reverted in 03b59e8,
now paired with the caller-side transient retain that makes it sound.
The four entry/exit ARC loops skip IsConstParam again (a const parameter's
object is kept alive by the caller for the whole call, so the callee needs no
_StringAddRef/_StringRelease or _ClassAddRef/_ClassRelease pair). The earlier
revert was because that premise fails for a TEMPORARY bound to a const param
(e.g. `Use(A + ' ' + B)`): the concat result is +0, its only reference is the
argument slot, and with the callee retain elided it was freed mid-call.
The fix (Andrew Haines, cherry-picked from the llvm branch — commits 00fcf41 +
e94e70544) adds the missing reference at the CALL SITE: EnsureConstStringRef
emits _StringAddRef before the call and ReleaseConstStringArgs emits
_StringRelease after, for each value-mode argument to a const-string parameter.
The pair is a no-op on immortal literals and nets to zero on owned strings, so
the overhead falls only on the transients that actually need it.
Tests: restored the elision IR tests (string + interface const params),
Andrew's caller-retains-transient IR tests and valgrind e2e
(TestRun_ConstStringParam_TransientRetained_Valgrind), alongside the existing
TestRun_ConstStringTemp_StaysAlive_Valgrind. docs/future-improvements.adoc marks
the optimisation shipped. Full suite 0 failures; FIXPOINT_OK; the original
metaclass-ref crash (`C := TFoo`) compiles cleanly.
This reverts commit 5a5b5d4. The optimisation elided the callee-side
ARC retain/release for const string/class/interface value params on the
premise that the caller keeps the argument alive for the whole call. That
premise fails for a TEMPORARY bound to a const param (e.g. `Use(A + ' ' + B)`):
the concatenation result's only reference is the argument slot, so without the
callee-side retain its refcount hits zero at the call boundary and it is freed
before the callee reads it — a use-after-free.
This bit the RTL hardest: `_StringCopy` / `StrHead` take `const string` params
and are called with built-at-runtime temporaries, so the emitted RTL was
miscompiled. Under self-hosting the defect is self-reproducing and only
manifests at the SECOND generation (the compiler that emits the broken RTL is
itself fine), which is why a one-step fixpoint did not expose it and the
compiler's own sources did not reliably trigger it. The symptom was a
deterministic crash compiling any program that uses a metaclass reference
(`C := TFoo`) or HasClassAttribute — which is why TestRunner (via
blaise.testing.runner.text) could not be built, blocking the whole suite.
The original change's valgrind e2e test passed only because it bound a string
LITERAL (immortal) to the const param, not a temporary.
Removed the now-invalid IR/e2e tests that asserted the elided behaviour
(string const params, and the interface-const variant from 088d12f which
relied on this commit's IsConstParam guard). Added
TestRun_ConstStringTemp_StaysAlive_Valgrind, which passes a concatenation
result as a const string param and reads it in the callee — the exact case the
optimisation broke. docs/future-improvements.adoc records how to re-attempt the
elision safely (condition on the argument, not the parameter; retain temporaries
either caller- or callee-side).
Verified: stage-2 build clean, TestRunner builds, full suite 0 failures,
FIXPOINT_OK.
Implement the sret calling convention for functions/procedures that
return a record type, matching the QBE backend's hidden-first-pointer
approach:
Callee side (EmitFunctionDef):
- FSretFunc flag set in BuildFrame when ResolvedReturnType.Kind = tyRecord
- Result slot becomes an 8-byte pointer slot (nil type = pointer-size)
- Prologue spills the hidden sret %rdi into the Result slot (IntIdx starts
at 1 so normal params continue at %rsi, %rdx, ...)
- No Result initialisation (caller's buffer is already zeroed by caller)
- Epilogue emits plain ret (no return value in %rax/%xmm0)
Field writes through Result (TFieldAssignment with RecordName='Result'):
- Load the sret pointer from the Result slot into %rcx
- Write through %rcx + field offset
Caller side (EmitSretCall):
- leaq dest → %r10 before arg evaluation (survives clobbers)
- Call memset(%r10, 0, TotalSize) to zero the destination buffer
- Reload %r10 after memset (caller-saves may be clobbered)
- Evaluate normal args and push; pop into %rsi/%rdx/... (index 1+)
- movq %r10, %rdi to place sret pointer as hidden first arg
- callq function
TAssignment detection: when LHS is a record and RHS is a record-returning
TFuncCallExpr, dispatch to EmitSretCall instead of EmitExprToEax.
TestRun_Native_RecordReturnFunction promoted from Ignore to AssertRunsOnBoth.
2383 tests pass; FIXPOINT_OK.
Replace the raw conversation fragment with a structured improvement entry
covering the motivation (TIdentExpr.IsNoArgFuncCall ambiguity), the
interaction with the Result-variable convention, migration impact, and
effort estimate.
Two related bugs found while testing procedural types as open-array elements:
1. Static-array element stores for tyProcedural used 'storew' (32-bit) instead
of 'storel' (64-bit), truncating the function pointer and causing a segfault
at the indirect call site. Fixed by adding tyProcedural to the storel branch
in the static-array element-store path in EmitArraySubscriptStmt.
2. Calling through an array subscript expression (Fns[I]()) was rejected by the
parser with "Expected 'end' but got '('". The postfix chaining loop only
handled '.', '[', and '^'; it did not recognise '(' as a postfix call on a
non-identifier expression.
Fixed by introducing TIndirectFuncCallExpr (callee is an arbitrary TASTExpr),
extending the postfix loop in ParseFactor to emit it when '(' follows any
expression, and adding the corresponding semantic analysis and codegen paths.
The codegen uses the callee value directly as the call target — no extra loadl,
since the subscript load already yields the function pointer value.
Adds E2E test TestRun_OpenArray_ProcType_CallEach covering a static array of
TIntFn passed as an open-array parameter and called element-by-element via the
direct Fns[I]() syntax.
Registers five new trig intrinsics in the symbol table, semantic analyser,
and codegen. Single arguments emit the *f libc variants (asinf, acosf,
sinhf, coshf, tanhf); Double arguments emit the unprefixed variants.
Removes the now-implemented section from docs/future-improvements.adoc.
Adds a "Stream I/O" section to docs/language-rationale.adoc covering:
the decision (Go/Okio-inspired shape, one-direction abstract bases plus
capability interfaces), rationale (cross-language survey lessons from
Java/Go/Rust/Okio/.NET/Python), why capability interfaces sit alongside
abstract classes (TBuffer as both source and sink), UTF-8 only in v1,
alternatives rejected (single-TStream root, TFilterStream decorator,
async-from-day-one), and TODOs flagged in code (segment-pool thread
safety, CopyStream fast paths, non-identifier interface arguments).
Removes the "Formal TStream decorator hierarchy" section from
docs/future-improvements.adoc — the streams subsystem is now
implemented across phases 1-5 (releases of the past few commits).
The implementation departs from the sketched TStream/TFilterStream
shape in favour of one-direction abstract bases + capability
interfaces, for the reasons documented in the rationale.
./scripts/fixpoint.sh clean.
Documents the design space for richer enum models with reference examples
from Java, Swift, Go, C#, and Oxygene (RemObjects), each showing both the
type definition and realistic application-code usage.
Options documented:
- Option B: built-in string name lookup (compiler-generated table)
- Option C: enum class with per-variant fields and methods (Java-style)
Recommended progression from done (explicit ordinals) through near-term
(Ord/Succ/Pred intrinsics), medium-term (Option B), and long-term (Option C).
Low(S) always returns 0; High(S) returns Length(S)-1, consistent with
0-based string indexing. The semantic pass previously rejected both with
"must be an array"; codegen for High(S) reads the ARC header length field
directly (data_ptr-8, loadsw) and subtracts 1.
for B in S (B: Byte or Integer) was already implemented; this commit
documents the decision in language-rationale.adoc and grammar.ebnf.
language-rationale.adoc:
- Fix stale "1-based" description in String Subscript section
- Add "Low and High on Strings" section (decision, rationale, alternatives)
- Add "for B in S — String Byte Iteration" section
grammar.ebnf:
- Extend Low/High signatures to show Array | String
- Add ForStmt element-type annotation block
future-improvements.adoc:
- Add migration analyser suggestion to recommend for..in as replacement
for pure character-walk index loops
7 new tests in cp.test.stringops.pas, all passing (1378 total, 0 failures).
Fixpoint verified.
Blaise strings are now 0-based: S[0] is the first character, Pos returns
a 0-based index (-1 = not found), Copy takes a 0-based From argument.
Compiler changes:
- Add uStrCompat.pas bootstrap shim with StrAt, StrHead, StrCopyFrom,
StrCopyTail, StrPos — thin wrappers that translate between FPC's
1-based and Blaise's 0-based conventions
- Convert all string operations in Blaise.pas, uLexer.pas, uCodeGenQBE.pas,
and uSemantic.pas to use the 0-based shims
- Add PosOrd/PosSubstr shims in uPasTokeniser.pas to keep its internal
1-based FPos convention while translating at the boundary
- Fix UpCase codegen to extract ordinal via OrdAt when argument is a string
- Fix vtable emission in AppendUnit to use StrAt/StrCopyTail instead of
1-based E.ImplName[1] and Copy
ARC fixes uncovered during self-hosting:
- Add string param AddRef/Release to EmitMethodDef (was only on EmitFuncDef)
- Add string and class ARC to var/out parameter assignment path
RTL changes:
- _StringPos, _StringPosEx: return 0-based index, -1 for not found
- _StringCopy, _StringDelete: accept 0-based From/Idx
- _OrdAt: accept 0-based index
- SplitIntoList in classes.pas: convert to 0-based loop
Tests: add coverage for method string param ARC, var-param string ARC,
constructor prefix matching (CreateFmt), pointer type aliases, metaclass
aliases. Update E2E tests for 0-based Copy/Pos semantics.
Docs: add 0-based string rationale to language-rationale.adoc and
migration analyser checklist to future-improvements.adoc.
Self-hosting fixpoint verified (114758 lines, stage-2 == stage-3).
Add detailed design considerations for TThread under Blaise's automatic
reference counting: the fire-and-forget safety problem and the recommended
self-referential threading solution. Include practical code examples,
async/await vs TThread comparison table, state-machine explanations for
async/await transformation, and effort estimates for implementation.
Add '''...''' multi-line string literal syntax to the lexer and
tokeniser. Opening ''' followed by a newline starts a text block;
closing ''' on its own line sets the indentation baseline for margin
stripping. Single quotes inside the block require no escaping.
Disambiguation: ''' followed by a newline opens a text block; followed
by any other character falls back to the classic '' escape parse.
11 new tests in cp.test.textblock.pas cover basic blocks, margin
stripping, embedded quotes, empty blocks, relative indentation
preservation, disambiguation from '''', trailing content, tabs,
and blank line preservation. All 1264 tests pass; fixpoint verified.
Add _InheritsFrom RTL helper in blaise_arc.pas that walks the typeinfo
parent chain to check class identity. Wire IsBuiltinInheritsFrom through
the semantic analyser (tyPointer, tyMetaClass, tyClass receivers) and
codegen (emits call $_InheritsFrom with correct typeinfo loading).
Update punit with AssertInheritsFrom, AssertInheritsFromClass, and class
identity checks in AssertException and RunTestHandler. Update testpunit2
so DoTest21 correctly fails on a class mismatch (EError expected, EFail
raised). Add 5 unit tests and 6 e2e tests; all 1253 tests pass, fixpoint
confirmed.
Removes const sections, Abs(), ClassName/ClassType (all confirmed
implemented). Updates Str() workaround note now that DoubleToStr/
SingleToStr exist. Updates InheritsFrom effort note now that ClassType
vmt slots are in place.
Six missing language features added:
1. type PFoo = ^TFoo — pointer and simple type aliases in type sections.
Parser dispatches tkCaret/tkIdent to new TTypeAliasDef AST node;
semantic pass resolves to TPointerTypeDesc or the aliased type.
2. Double / Single float types — lexer emits tkFloatLit; TFloatLiteral
AST node; tyDouble/tySingle in the type system; QBE 'd'/'s' emit;
arithmetic, comparison, and integer promotion in codegen;
DoubleToStr, SingleToStr, StrToDouble, Abs(Double) built-ins;
_DoubleToStr/_SingleToStr/_StrToDouble/_AbsInt/_AbsInt64 in RTL
(new blaise_float.c). QBE generates SSE2 instructions automatically.
Float const declarations supported.
3. Abs() — built-in for Integer, Int64, Double, Single.
4. TObject.ClassName — typeinfo gains a third slot (offset 16) holding
a pointer to an immortal class-name string ($__cn_TFoo + 12).
obj.ClassName loads vtable[0] (typeinfo), then typeinfo[16] (nameptr).
EmitClassNameRef() emits the data-section label+offset relocation.
5. Global record field bug fix — FieldPtr() now accepts AIsGlobal and
uses VarRef() so $RecordVar is used for global records instead of
%_var_RecordVar. Both assignment and read paths fixed.
6. future-improvements.adoc — implemented items marked; Currency and
BigDecimal deferred to BCL packages section added.
Tests: 1155 pass, 5 pre-existing errors (TUnitTests AV), 0 new failures.
Adds support for declaring named procedural types that hold pointers to
standalone functions and procedures:
type
TIntFn = function: Integer;
TStrFn = function(const S: string): Integer;
TLogProc = procedure(Level: Integer; const Msg: string);
var F: TIntFn;
begin F := @MyFn; X := F(); end;
A procedural variable is stored as a single QBE 'l' (8-byte code
pointer). @FuncName produces a value of the matching procedural type.
Indirect calls F(args) load the pointer and emit a QBE indirect call.
Compatibility requires return types to match (both nil or both same
TTypeDesc) and parameter lists to match pairwise on type and parameter
mode (var/const/value); names do not participate.
Compiler additions:
* tyProcedural TTypeKind + TProcParamInfo + TProceduralTypeDesc
(with IsCompatibleWith)
* TProceduralTypeDef AST node
* Parser: type T = function/procedure ... ; reuses ParseParamList
* Semantic: ResolveProceduralTypeDef in pass 2; AnalyseAddrOfExpr
short-circuits @FuncName to a procedural-typed value;
AnalyseFuncCallExpr accepts procedural-typed variables as
indirect-call targets; CheckTypesMatch allows compatible
procedural assignment
* Codegen: QbeTypeOf(tyProcedural) -> 'l'; EmitVarAllocs emits an
8-byte slot; EmitAddrOfExpr emits $FuncName for @FuncName;
EmitFuncCallExpr emits 'call %tmp(...)' for indirect calls,
placed before the ResolvedDecl=nil type-cast branch
Out of scope (deferred until a use case requires them):
* function ... of object (method pointers — fat pointer ABI)
* reference to function/procedure (anonymous methods / closures)
* cdecl/stdcall calling-convention markers on procedural types
Tests: cp.test.proctypes.pas — 14 tests covering parser (kinds,
return types, params, var/const flags), semantic (compat accept/
reject on return type and arg count), and codegen (var allocation,
@FuncName emission, indirect-call emission).
Grammar and rationale: docs/grammar.ebnf adds the ProceduralType
production; docs/language-rationale.adoc captures the decision and
deferred items.
Motivation: prerequisite for porting Michael Van Canneyt's punit test
framework into rtl/src/test/pascal/, where every test, every
Setup/TearDown, and every hook handler is stored as a function
pointer.
1155 tests pass (1141 pre-existing + 14 new), no regressions.
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).
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.
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
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).
- 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