Commit graph

39 commits

Author SHA1 Message Date
Graeme Geldenhuys 36cad37423 feat(stdlib): add Numerics.Money — currency-aware TMoney
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).
2026-06-20 10:33:35 +01:00
Graeme Geldenhuys 718ee45e20 feat(stdlib): add Numerics.Decimal — exact decimal (TDecimal)
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).
2026-06-20 02:10:12 +01:00
Graeme Geldenhuys fa106ceeee feat(sets): support integer-subrange set base (set of 0..255) and Boolean operands
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.
2026-06-18 13:25:22 +01:00
Graeme Geldenhuys f107b0dabc docs: narrow the ordinal-set future-improvements entry to the unimplemented part
`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).
2026-06-18 13:00:04 +01:00
Graeme Geldenhuys 2f54707a63 feat(strutils): clean literal replace API — Replace (first) + ReplaceAll
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).
2026-06-16 23:49:30 +01:00
Graeme Geldenhuys 9362114ee1 feat(sets): range syntax in set literals — [lo..hi] (#105)
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).
2026-06-15 17:14:05 +01:00
Graeme Geldenhuys ca21b884bc feat(debug): link --debug-opdf binaries as PIE
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.
2026-06-11 15:43:02 +01:00
Graeme Geldenhuys 51ebf2350a feat(lang): require mandatory () on all zero-argument calls
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).
2026-06-06 19:14:47 +01:00
Graeme Geldenhuys a5f01c1cd1 perf(arc): elide const-param retain/release, retain transient args
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.
2026-06-05 08:08:19 +01:00
Graeme Geldenhuys 03b59e8ff0 Revert "perf(arc): elide retain/release for const string and class params"
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.
2026-06-04 23:21:25 +01:00
Graeme Geldenhuys bb1cfb7676 feat(codegen): native backend M7a — record-returning functions (sret)
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.
2026-06-03 13:57:14 +01:00
Graeme Geldenhuys 0ce9507775 docs: rewrite 'Function calls requiring ()' section as proper design entry
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.
2026-06-02 09:54:14 +01:00
Graeme Geldenhuys 8b524340ce fix(codegen): storel for proc-pointer static-array slots; add Expr()() postfix call
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.
2026-05-22 17:10:02 +01:00
Graeme Geldenhuys a208906601 docs: update future-improvements.adoc removing completed items. 2026-05-22 16:57:19 +01:00
Graeme Geldenhuys 8f692cb0f8 feat(math): add ArcSin, ArcCos, Sinh, Cosh, Tanh builtins with Single dispatch
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.
2026-05-17 01:56:35 +01:00
Graeme Geldenhuys a33d5aa879 docs(streams): record Stream I/O design rationale; remove from roadmap
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.
2026-05-15 01:10:29 +01:00
Graeme Geldenhuys b359ed4da7 docs(future): add Enhanced Enumerations section to future-improvements.adoc
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).
2026-05-14 01:10:58 +01:00
Graeme Geldenhuys 47e96c3872 docs(migration): add TObjectList → TList<TObject> detection rule 2026-05-13 13:59:34 +01:00
Graeme Geldenhuys f8abb16791 feat(lang): Low/High on strings + document for..in string iteration
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.
2026-05-12 17:51:22 +01:00
Graeme Geldenhuys 079a06ea82 feat(lang): switch to 0-based string indexing throughout compiler and RTL
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).
2026-05-11 17:50:35 +01:00
Graeme Geldenhuys e3732807fd docs: expand concurrency section with ARC + threading design
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.
2026-05-10 23:36:05 +01:00
Graeme Geldenhuys 3e4d591c1d chore(doc): PasBuild rename --fpc to --compiler [already done] 2026-05-07 23:25:19 +01:00
Graeme Geldenhuys 9300fe8a46 chore(doc): new future improvement - If Conditional Operator (ternary operator) 2026-05-07 23:19:03 +01:00
Graeme Geldenhuys db7660a95e chore(docs): Remove Multi-Line string literal from future-improvements
It already landed in the compiler.
2026-05-07 23:18:24 +01:00
Graeme Geldenhuys 11f657caef feat(lexer): implement triple single-quote text blocks
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.
2026-05-07 19:52:23 +01:00
Graeme Geldenhuys 8f0a844d07 feat(compiler,rtl): implement TObject.InheritsFrom
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.
2026-05-07 09:53:47 +01:00
Graeme Geldenhuys f3f0036381 docs: remove implemented entries from future-improvements.adoc
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.
2026-05-07 08:36:28 +01:00
Graeme Geldenhuys faee6e6bef feat(lang): type aliases, floats, Abs, ClassName + global record fix
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.
2026-05-04 02:02:06 +01:00
Graeme Geldenhuys f256052a2b feat(types): bare procedural types (function/procedure pointers)
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.
2026-05-03 23:15:42 +01:00
Graeme Geldenhuys 95f71e1acc feat(test): PDR integration test suite and pasbuild-integration-test plugin
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).
2026-05-02 00:35:00 +01:00
Graeme Geldenhuys f34cf31c17 test(constants): add regression tests for const in all scopes
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.
2026-05-01 12:47:04 +01:00
Graeme Geldenhuys a070727348 docs: correct PasBuild Blaise backend entry — --fpc rename only
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.
2026-05-01 10:42:06 +01:00
Graeme Geldenhuys 2985ff23ef docs: add Tooling section — fptest port and PasBuild Blaise backend
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
2026-05-01 10:40:08 +01:00
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 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 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 9d4b2837a1 docs: add future-improvements.adoc with single-precision trig dispatch entry 2026-04-28 13:34:48 +01:00