= Blaise Compiler — Future Improvements
:toc:
:toc-placement: preamble
:sectnums:

This document records optimisation opportunities, quality-of-life improvements,
and language enhancements that are worth pursuing but are deliberately deferred
from the current implementation phase. Each entry states what the improvement is,
why it matters, and any known implementation notes.

Items here are not committed to any release milestone. They exist so that good
ideas are not lost and so that the rationale is available when the time comes to
prioritise them.

== Generics

- TStringList ← TList<String>: Generic class inheritance. Defer until generics
  inheritance is implemented and well tested.
- Generic class inheritance (class(TList<String>)): Parser reads the
  parent name as a string, but semantic analysis never resolves a generic
  instantiation as a parent — it needs template lookup + monomorphisation.
  Moderate-significant effort.

- TComponent mixes a raw pointer array with ARC, which is inherently delicate. A cleaner long-term design would store children
  in TList<TComponent> (per the project's "use Generics.Collections" policy) so the list itself holds ARC refs and the manual
  _ClassAddRef/_ClassRelease bookkeeping disappears entirely. That's a larger refactor than fixing the red suite, so I kept it
  minimal — but it's the right direction if you want to revisit classes.pas.


== Language

=== Procedural types

- reference to function/procedure (anonymous methods)
- cdecl/stdcall calling-convention markers on procedural types

=== Method level attributes

Blaise only has class-level attribute RTTI today — there's no per-method attribute reflection, and adding it is a layout-changing compiler change (new method-table slot, stage-2 rebuild).

=== If Conditional Operator (Ternary Conditional Operator)

More details here: https://en.wikipedia.org/wiki/Ternary_conditional_operator

Many languages use the ?: syntax as follows:

`result = condition ? value_if_true : value_if_false`

But that's not very pascal-like. Oxygene language has implemented this years
ago with te following syntax:

For instance

[source]
----
var s := if (a<>0) then Sin(1/a) else 1;
----

being equivalent to

[source,pascal]
----
var s : Float;
if (a<>0) then
   s := Sin(1/a)
else s := 1;
----

So for Blaise we could do similar:

X := if Left < 100 then 22 else 45;
// the operator can be used as part of any expression, as follows:
ShowMessage(if Left < 100 then 'Small' else 'Big');

NOTE: If the two types do not match and are incompatible, the compiler should issue an error message

NOTE: It is important to note that the if-then-else expression differs from the if-then-else statement, the expression has a single type. When the type of the then and else expressions differ, the expression's type is the common type of the two expressions' types; also known as the Least Upper Bound (LUB) type.

See also Delphi's documentation: https://docwiki.embarcadero.com/RADStudio/Florence/en/Conditional_Operators_(Delphi)


== Language — Features Required to Complete the punit Port

The following features were identified as missing from Blaise while porting
`/data/devel/fpc-3.3.1/src/packages/fcl-base/tests/punit.pp` to
`rtl/src/test/pascal/punit.pas`.  Each one has a concrete workaround in the
current port, noted below, so none is a blocker; they are collected here so
they can be prioritised alongside other language work.


=== `Str()` intrinsic — numeric to string conversion

*What.* The FPC/Pascal `Str(X, S)` intrinsic writes the string representation
of a numeric value `X` (integer or float) into the `var` string parameter `S`.

*Why it matters.* It is the traditional Pascal way to convert a number to a
string without pulling in `SysUtils`.  Used throughout punit's `SysTimeString`
helper and the float assertion overloads.

*Current workaround in punit.* Integer conversions replaced with `IntToStr()`
and `Int64ToStr()`.  Float→string conversions use `DoubleToStr()` /
`SingleToStr()`.  The FPC-style `Str(X, S)` var-parameter form is still absent.

*Implementation notes.* `Str` is a thin wrapper: for integers, alias
`IntToStr` / `Int64ToStr`; for floats, alias `DoubleToStr` / `SingleToStr`.
The `var`-parameter assignment can be emitted as a string copy into `S`.

*Effort.* Trivial.

=== `New()` and `Dispose()` built-ins for typed pointers

*What.* `New(P)` allocates `SizeOf(P^)` bytes and returns a typed pointer;
`Dispose(P)` frees it.  Both are traditional Pascal alternatives to manual
`GetMem` / `FreeMem` with explicit `SizeOf`.

*Why it matters.* In the original punit every record allocation uses `New` and
every deallocation uses `Dispose`.  Without them the port must call
`GetMem(SizeOf(TFoo))` and `ZeroMem` manually, and the developer must remember
to `FreeMem` at every exit path.

*Current workaround in punit.* All uses replaced with `GetMem(SizeOf(T))` +
`ZeroMem(ptr, SizeOf(T))` + `FreeMem(ptr)`.

*Implementation notes.* `New(P)` is a thin wrapper over `GetMem(SizeOf(P^))`
with the type deduced from the pointer's declared type at the call site.
`Dispose(P)` calls `FreeMem(P)` after invoking the destructor if the pointed-to
type is a class (for record pointers, no destructor call is needed under ARC
provided string fields have been properly finalised first).

*Effort.* Small — semantic pass extracts element type, codegen emits the
appropriate `malloc` / `free` sequence.

=== `Default(T)` expression

*What.* `Default(T)` returns the zero value for any type `T` — zero for
integers, empty string for strings, nil for pointers and classes, and so on.
Commonly used to reset a record variable to its initial state.

*Why it matters.* In the original punit it is used to re-initialise
`TResultRecord` and `TRunSummary` records before each test run.

*Current workaround in punit.* Replaced with `ZeroMem(@rec, SizeOf(rec))`.
This is correct for records whose managed fields (strings, interface refs) are
already nil, but for a record mid-lifetime it bypasses ARC cleanup of any
non-nil string fields.  For punit's specific use (zeroing a freshly allocated
or just-finalised record) this is safe; for general use it is not.

*Implementation notes.* The semantic pass resolves the type argument; the
codegen emits a `ZeroMem` (or a field-by-field assignment for managed types
where ARC release must precede zeroing).

*Effort.* Small for unmanaged types; moderate for records containing managed
fields (requires field-by-field ARC release before memset).

=== `Finalize()` procedure for managed records

*What.* `Finalize(V)` releases all ARC-managed fields inside a record variable
`V` (strings, interface references, object references) without freeing the
record's own memory.  Used before `FreeMem` when the record was heap-allocated.

*Why it matters.* In the original punit it is called on `TResultRecord^` before
`FreeMem` to release the `TestMessage: string` field.  Without it, heap-
allocated records that contain strings leak the string's reference count.

*Current workaround in punit.* The `Finalize` calls were dropped.  In Blaise,
ARC on assfignment should automatically manage string fields inside records, so
the string is released when the record's string field is overwritten or when the
last reference to the containing heap block is released.  The correctness of
this assumption depends on the ARC implementation for record fields — if the
codegen does not insert `_StringRelease` when a record pointer is freed via
`FreeMem`, there will be a string leak.

*Implementation notes.* `Finalize(V)` is a compiler built-in that walks the
fields of the record type and emits the appropriate ARC release calls for each
managed field.  The implementation mirrors what the codegen already does for
local record variables at scope exit.

*Effort.* Small to moderate — shares infrastructure with ARC scope-exit emit.


== StdLib Packages — Numeric Types

The exact-decimal type (`TDecimal` in `Numerics.Decimal`) and the currency-aware
wrapper (`TMoney` in `Numerics.Money`) are both implemented; the design
decisions are recorded in `docs/language-rationale.adoc` ("Decimal Arithmetic —
One `TDecimal`, No `Currency` Primitive" and "Currency Amounts — `TMoney` Wraps
`TDecimal`, Currency Is a String Tag").  The following numeric items remain
genuinely future work.

=== Operator overloading for `TDecimal`

`TDecimal` currently exposes arithmetic as named methods (`A.Add(B)`,
`A.Multiply(B)`, `A.Divide(B, Scale, Mode)`) because operator overloading is not
yet a language feature.  Once it lands, `A + B` / `A * B` / comparisons can layer
on additively (division stays a named method — it requires an explicit scale and
rounding mode).  Operator overloading is a compiler feature in its own right and
warrants a separate design document.

=== Migration-analyser rule for legacy numeric types

The migration analyser should flag FPC/Delphi `Currency` and `Comp` and suggest
`TDecimal`; `Extended` maps to `Double` with a precision-loss warning.

== StdLib — String operations

=== Regular-expression search and replace

`StrUtils` provides literal (non-pattern) replacement only: `Replace` (first
occurrence) and `ReplaceAll` (every occurrence), both case-sensitive.  A
pattern-based API — `regex` search, `ReplaceMatches`/`ReplaceFirstMatch`, and
capture-group substitution — is deliberately deferred because it requires a
regular-expression engine (parser + NFA/DFA matcher) that does not yet exist
in the stdlib.

*Why deferred.*  A correct, reasonably performant regex engine is a
substantial component in its own right.  The literal `Replace`/`ReplaceAll`
functions cover the common cases without it.

*Current workarounds.*

- Case-insensitive replace: lower-case the inputs with the built-in
  `LowerCase` (or `UpperCase`) before calling `Replace`/`ReplaceAll`.  Note
  that this also folds the case of the surrounding text in the result; a
  case-*preserving* insensitive replace is a regex-engine feature.
- Fixed multi-pattern replacement: chain `ReplaceAll` calls.

*Effort.*  Large — a regex engine is the bulk of the work; the replace API on
top of it is small.

== macOS — Debugging and Code-Signing

=== Debugger entitlement requirements

*What.* On macOS, a debugger binary must carry the
`com.apple.security.get-task-allow` entitlement (and, post-SIP, the binary
it attaches to must carry the same entitlement) before the kernel permits
`task_for_pid`.  The Blaise debugger will need a documented signing workflow
for macOS targets.

*Why it matters.* Without the entitlement, `ptrace` and Mach exception
ports are blocked by the kernel even for self-compiled binaries.  Open-source
users who cannot obtain an Apple Developer certificate ($99/year) need a
supported path.

*Recommended approach.*

. *Ad-hoc signing* — the user self-signs with a locally generated certificate.
  Works for development; the compiler should emit a post-build `codesign`
  invocation for debug configurations.

. *Debug-server architecture* — ship a small, separately signed stub
  (`blaisedbgserver`) that handles `task_for_pid` and communicates over a
  local socket.  The main debugger frontend requires no special entitlement.
  This mirrors LLDB’s `debugserver` design and avoids per-binary signing of
  every compiled Pascal program.

. *SIP caveat* — SIP blocks attachment to Apple-signed system processes.
  For user-compiled Pascal binaries, SIP is not an obstacle provided the
  binary carries `get-task-allow`.

*Debug-format note.* The Blaise compiler’s own debug information format
(OPDF or equivalent) avoids dependency on Mach-O `dSYM` bundles and
Apple’s `dsymutil`.  This is the correct design: DWARF/dSYM integration adds
fragile toolchain coupling without benefit for a self-hosted debugger.

*Effort.* Small (documentation + build-system integration for `codesign`);
moderate for the debug-server architecture.

== Concurrency

=== Threading and modern concurrency models

*What.* Blaise needs a concurrency story.  The plan is a layered approach:
a compatibility layer for existing Pascal code, and a modern concurrency
model that makes Blaise worth choosing over FPC/Delphi for concurrent
workloads.

*Layer 1 — TThread (Delphi/FPC compatibility).*

Thin wrapper around `pthread_create` (POSIX) or `CreateThread` (Win32).
Required for porting existing Pascal codebases.  Provides the familiar
`TThread.Create`, `Execute` override, `Synchronize`, `WaitFor`, and
`Terminate` / `Terminated` pattern.  Not inspiring, but necessary for
ecosystem compatibility.

**Important ARC consideration for TThread:**
Blaise's automatic reference counting introduces a subtle but critical
interaction with thread lifetimes.  The naive fire-and-forget pattern:

[source,pascal]
----
var T: TMyThread;
begin
  T := TMyThread.Create;
  T.FreeOnTerminate := True;
  T.Start;
end; // <- T is released here; refcount → 0; object freed while thread runs
----

...is unsafe under ARC.  When the local variable `T` goes out of scope,
ARC immediately decrements the refcount.  If this is the last reference,
the object is freed whilst the thread is still executing, causing a
use-after-free or crash.

The recommended solution is **self-referential threads**: each TThread
increments its own refcount during `Execute` and decrements it during
finalisation, keeping itself alive for the duration of its work.  This
mirrors modern threading APIs (Rust's `Arc<Thread>`, Go's goroutines).

Implementation sketch:

[source,pascal]
----
procedure TThread.Execute;
begin
  _ClassAddRef(Self);  { Keep self alive during execution }
  try
    // User's Execute override (via virtual dispatch)
    Self.UserExecute;
  finally
    _ClassRelease(Self);  { Release self; if FreeOnTerminate, frees object }
  end;
end;
----

This way the caller can write idiomatic fire-and-forget code and the
thread automatically cleans itself up once it finishes:

[source,pascal]
----
var T: TMyThread;
begin
  T := TMyThread.Create;
  T.FreeOnTerminate := True;
  T.Start;  // Thread increments self-refcount in Execute
end;        // T's refcount drops, but thread keeps itself alive
            // Thread finalizer decrements self-refcount, freeing the object
----

*Layer 2 — Modern structured concurrency.*

The industry has converged on a few proven models:

* *Structured concurrency with async/await* (C#, Rust, JavaScript, Swift,
  Kotlin) — the compiler transforms `async` functions into state machines.
  Works well with I/O-bound code.  Requires language-level support in the
  parser and codegen but no complex runtime scheduler.

* *Virtual threads / green threads* (Java 21, Go goroutines, Erlang
  processes) — the runtime multiplexes many lightweight threads onto a
  small pool of OS threads.  User code looks synchronous.  Requires a
  scheduler in the RTL, which is a larger investment.

* *Structured task groups* (Swift `TaskGroup`, Java `StructuredTaskScope`)
  — tasks have parent-child relationships with automatic cancellation
  propagation and scope-bound lifetimes.  Prevents leaked tasks by
  construction.

**Comparison: async/await vs TThread.**

[%header, cols="2,5,5"]
|===
| Aspect | TThread | Async/Await
| Concurrency model | OS threads (preemptive) | Cooperative coroutines (event loop)
| Stack per task | ~1 MB per thread | Minimal; thousands in ~few MB
| Scheduling | OS kernel; true parallelism on multicore | Single event loop; one task at a time
| Blocking I/O | Blocks thread; wasteful | Suspends task; loop handles others
| Data sharing | Requires locks/atomics (races, deadlocks) | Inherently safe; only one coroutine active
| Cancellation | Difficult (cooperative flags) | Natural (`cancel()` the task)
| CPU-bound work | Natural fit | Poor fit (no parallelism)
| Common use | Fire-and-forget background workers | Server handling thousands of clients
|===

The recommended path for Blaise:

. Start with `TThread` for compatibility, using self-referential semantics
  to protect thread lifetime under ARC.
. Add `async`/`await` as the primary modern concurrency primitive for
  I/O-bound workloads.  This is the most compiler-friendly approach —
  it is a source-to-source transform during semantic analysis, turning
  async functions into state machine classes.  It does not require a
  complex runtime scheduler.
. Layer structured task groups on top of async/await for fork-join
  parallelism with automatic cancellation.
. Consider virtual threads as a long-term option if the runtime grows to
  support a user-mode scheduler (significant investment).

*Design considerations.*

* ARC interaction — both TThread (self-referential) and async/await handle
  lifetime automatically.  For async tasks, captured variables are
  referenced; the ARC system must ensure they outlive the task.
  Structured concurrency helps here because task lifetimes are lexically
  scoped.
* Async/await state machine — the compiler transforms `async` function
  bodies into state-machine classes during semantic analysis.  Each
  `await` point becomes a state transition.  The state machine holds
  captured variables and a continuation pointer.  No runtime scheduler or
  coroutine primitives needed — just ordinary function calls and ARC.
* QBE constraints — QBE has no coroutine or `setjmp`-based context
  switching primitives.  `async`/`await` via state-machine transform
  does not require these — the compiler generates ordinary function calls
  between states.  Green threads would require platform-specific context
  switching (`swapcontext` or hand-rolled assembly), which is harder to
  express through QBE.
* Exception propagation — async tasks need a defined exception model.
  Structured concurrency naturally provides this: exceptions propagate
  to the parent scope and cancel sibling tasks.

*Effort.*

* TThread self-referential semantics: Small (2–3 days; modifications to
  RTL's TThread base class and entry point).
* Async/await: Large.  This is a significant compiler feature (parser,
  semantic, codegen transforms) that warrants its own design document and
  phased implementation.  Estimated 4–6 weeks post-v0.6.0.

== Migration Analyser

=== Flag `TObjectList` usage and recommend `TList<TObject>`

*What.*  The migration analyser must detect uses of `TObjectList` from the
FPC/Delphi `contnrs` unit and recommend the generic `TList<TObject>` from
`Generics.Collections` as the idiomatic Blaise replacement.

*Why it matters.*  `TObjectList` is a pre-generics workaround — a pointer
list that stores `TObject` references without type safety.  In the generics
era, `TList<TObject>` provides the same capability with full type checking.
In modern languages (Go, Swift, Kotlin, C#) the untyped container pattern
is considered obsolete; Blaise should steer new code toward the typed form.

*Patterns to detect.*

[cols="1,2", options="header"]
|===
| Pattern | Recommended replacement

| `uses Contnrs; ... TObjectList`
| `uses Generics.Collections; ... TList<TObject>`

| `TObjectList.Create(True)` (owned)
| `TList<TObject>.Create` (ARC handles ownership automatically)

| `TObjectList.Create(False)` (unowned)
| `TList<TObject>.Create` (same — ARC does not double-free)
|===

*Implementation notes.*

. Detect `TObjectList` type references in variable declarations, field
  declarations, and `Create` calls.
. Report each occurrence with the source location and the suggested rewrite.
. Note that the ownership flag (`AOwnsObjects`) has no equivalent in
  `TList<TObject>` — ARC manages object lifetimes automatically, so the
  flag is meaningless in Blaise.  The migration note should explain this.

*Effort.*  Small — one pattern rule in the type-reference checker.

=== Detect 1-based string indexing patterns from FPC/Delphi code

*What.*  The migration analyser (`tools/migration-analyser`) must detect
and flag patterns in FPC/Delphi source that rely on 1-based string
semantics, since Blaise strings are 0-based (see
`docs/language-rationale.adoc`, section "0-Based String Indexing").

*Why it matters.*  Ported code that uses 1-based idioms will compile
without error but produce wrong results silently — off-by-one character
accesses, missed `Pos` not-found checks, and incorrect `Copy` start
positions.  These are among the most insidious categories of migration
bugs because they produce plausible-looking output most of the time and
only misbehave on edge cases.

*Patterns to detect.*

[cols="1,2,2", options="header"]
|===
| Pattern | FPC/Delphi meaning | Blaise equivalent

| `S[1]`
| First character
| `S[0]`

| `S[N]` where `N` is a literal ≥ 1
| Character at 1-based position N
| `S[N-1]`

| `Copy(S, 1, N)`
| First N characters
| `Copy(S, 0, N)`

| `Copy(S, K, N)` where `K` is a literal ≥ 2
| Substring starting at 1-based K
| `Copy(S, K-1, N)`

| `Copy(S, K, MaxInt)` where K ≥ 2
| Tail from 1-based K
| `Copy(S, K-1, MaxInt)`

| `Pos(Sub, S) > 0`
| Substring found
| `Pos(Sub, S) >= 0`

| `Pos(Sub, S) = 0`
| Substring not found
| `Pos(Sub, S) < 0`

| `if Pos(Sub, S) > 0 then ... Pos(Sub, S)` (double call)
| Find and use position
| Assign to variable first; compare `>= 0`; use variable as 0-based index

| `PosEx(Sub, S, K)` where `K` is a literal ≥ 2
| Search from 1-based K
| `PosEx(Sub, S, K-1)`

| `OrdAt(S, K)` where `K` is a literal ≥ 1
| Ordinal at 1-based K (FPC-defined helper)
| `OrdAt(S, K-1)`

| `for I := 1 to Length(S) do ... S[I]`
| Iterate characters 1-based
| `for I := 0 to Length(S) - 1 do ... S[I]`
|===

*Implementation path.*

. Add a string-index analysis pass to the analyser that walks expression
  trees and identifies the patterns above.
. Report each occurrence with source location, the offending expression,
  and the suggested rewrite.
. Special-case `Copy(S, 1, N)` — this is the most common false-positive
  source (copying the whole string) so provide the `Copy(S, 0, N)` rewrite
  without noise.
. The `Pos(...) > 0` → `>= 0` and `Pos(...) = 0` → `< 0` rewrites are
  purely mechanical and could be offered as auto-apply fixes.

*Effort.*  Medium — approximately one week to add the expression-walker and
pattern-matching rules, plus testing against a representative FPC
codebase.

=== Suggest `for B in S` as the idiomatic replacement for index-based string loops

*What.*  When the migration analyser detects the pattern
`for I := 1 to Length(S) do ... S[I]` (or the 0-based equivalent
`for I := 0 to Length(S) - 1 do ... S[I]`), and the loop body only
accesses the character via `S[I]` without using `I` for any other
purpose, offer `for B in S do` as the preferred rewrite rather than a
mechanical index adjustment.

*Why it matters.*  The index-based loop is a common FPC/Delphi idiom
that ports verbatim but reads less clearly than the enhanced for form.
Blaise supports `for B in S do` with `B: Byte` or `B: Integer`, which
eliminates the index entirely and makes the intent — "process every byte
of the string" — explicit.  Recommending this form during migration
encourages idiomatic Blaise code rather than a literal transcription of
the original pattern.

*Suggested output.*

----
test.pas:12: for I := 1 to Length(S) do
  note: consider replacing with: for B in S do
  (only applicable if I is not used other than for S[I])
----

*Implementation notes.*

. The pattern check runs after the index-adjustment rewrite: if `I` is
  used exclusively as `S[I]` within the loop body, upgrade the
  suggestion from "rewrite index" to "use for-in".
. The analyser must confirm that the loop variable does not appear in any
  expression other than `S[VarName]` inside the body.  A conservative
  check (any other use of `I` suppresses the suggestion) is acceptable
  for the initial version.

*Effort.*  Small — a follow-on pattern in the same analysis pass.

== Enhanced Enumerations

Blaise currently supports simple positional enumerations (`type TDir = (dNorth, dSouth, dEast, dWest)`)
and explicit ordinal assignment (`type TStatus = (Idle=10, Running=20, Done=30)`).
This section documents richer enum models drawn from other languages, for
consideration in a future language revision.

The reference examples below are included as design inspiration, not as a
commitment to any particular syntax.  Any enhancement must preserve
`case`/`set` compatibility with simple integer ordinals unless a new,
distinct type form is introduced.

=== Reference: Java enum classes

Java enums are named singleton classes with a fixed set of instances.
Each variant carries typed fields initialised through a constructor.
Methods can be defined on the enum type and called on any variant.

.Java — definition
[source,java]
----
public enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS  (4.869e+24, 6.0518e6),
    EARTH  (5.976e+24, 6.37814e6),
    MARS   (6.421e+23, 3.3895e6);

    private static final double G = 6.67300E-11;
    private final double mass;    // kg
    private final double radius;  // metres

    Planet(double mass, double radius) {
        this.mass   = mass;
        this.radius = radius;
    }

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }

    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}
----

.Java — application usage
[source,java]
----
public class WeightOnPlanets {
    public static void main(String[] args) {
        double earthWeight = 75.0;
        double mass = earthWeight / Planet.EARTH.surfaceGravity();

        for (Planet p : Planet.values()) {
            System.out.printf("Weight on %s is %6.2f%n",
                              p, p.surfaceWeight(mass));
        }
        // Weight on MERCURY is  28.33
        // Weight on VENUS   is  67.89
        // Weight on EARTH   is  75.00
        // Weight on MARS    is  28.46
    }
}
----

*Key traits.* Each variant is a singleton object; the type is a class
hierarchy.  Methods have access to per-variant state.  `values()` returns
an ordered array of all variants.  Pattern matching (Java 21+) allows
exhaustive `switch` over variants.

=== Reference: Swift associated values

Swift enums are value types that support per-variant associated data of
heterogeneous types.  There is no shared constructor — each variant
carries its own payload, extracted via pattern matching.

.Swift — definition
[source,swift]
----
enum HTTPResponse {
    case ok(body: String)
    case redirect(location: String, permanent: Bool)
    case error(code: Int, message: String)
}

enum Direction {
    case north, south, east, west

    var opposite: Direction {
        switch self {
        case .north: return .south
        case .south: return .north
        case .east:  return .west
        case .west:  return .east
        }
    }
}
----

.Swift — application usage
[source,swift]
----
func handle(_ response: HTTPResponse) {
    switch response {
    case .ok(let body):
        print("Success: \(body)")
    case .redirect(let url, let permanent):
        let kind = permanent ? "301" : "302"
        print("\(kind) → \(url)")
    case .error(let code, let msg):
        print("Error \(code): \(msg)")
    }
}

let r = HTTPResponse.error(code: 404, message: "Not Found")
handle(r)   // Error 404: Not Found
----

*Key traits.* Associated values are extracted only at the call site via
`switch`/`if case`.  Different variants can carry different types and
arities.  The enum itself remains a value type (stack-allocated).

=== Reference: Go typed constants (iota)

Go has no dedicated `enum` keyword.  Enumerations are typed integer
constants, often using `iota` for automatic sequencing.  Methods are
attached via receiver functions outside the type declaration.

.Go — definition
[source,go]
----
type Weekday int

const (
    Sunday Weekday = iota  // 0
    Monday                  // 1
    Tuesday                 // 2
    Wednesday               // 3
    Thursday                // 4
    Friday                  // 5
    Saturday                // 6
)

func (d Weekday) String() string {
    names := [...]string{
        "Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday",
    }
    if d < Sunday || d > Saturday {
        return fmt.Sprintf("Weekday(%d)", int(d))
    }
    return names[d]
}

type Permission uint

const (
    Read    Permission = 1 << iota  // 1
    Write                            // 2
    Execute                          // 4
)
----

.Go — application usage
[source,go]
----
func describeDay(d Weekday) string {
    switch d {
    case Saturday, Sunday:
        return "weekend"
    default:
        return "weekday"
    }
}

func checkAccess(p Permission) {
    if p&Read != 0 {
        fmt.Println("can read")
    }
    if p&Write != 0 {
        fmt.Println("can write")
    }
}

func main() {
    fmt.Println(describeDay(Friday))           // weekday
    fmt.Println(Wednesday)                     // Wednesday (via String())
    checkAccess(Read | Write)                  // can read / can write
}
----

*Key traits.* Minimal syntax — enums are just typed constants.
`iota` auto-increments within a `const` block; bitfield flags use
`1 << iota`.  No associated data per variant; methods are ordinary
receiver functions.

=== Reference: C# enums with extension methods

C# enums are integer-backed value types with optional explicit underlying
type.  Methods cannot be declared inside the enum; extension method classes
provide the equivalent.

.C# — definition
[source,csharp]
----
public enum HttpStatus : ushort {
    OK          = 200,
    Created     = 201,
    NoContent   = 204,
    BadRequest  = 400,
    NotFound    = 404,
    ServerError = 500,
}

public static class HttpStatusExtensions {
    public static bool IsSuccess(this HttpStatus s) =>
        (int)s >= 200 && (int)s < 300;

    public static string Describe(this HttpStatus s) =>
        s switch {
            HttpStatus.OK          => "OK",
            HttpStatus.Created     => "Created",
            HttpStatus.NotFound    => "Not Found",
            HttpStatus.ServerError => "Internal Server Error",
            _                      => $"HTTP {(int)s}",
        };
}
----

.C# — application usage
[source,csharp]
----
void ProcessResponse(HttpStatus status) {
    Console.WriteLine($"{(int)status} {status.Describe()}");

    if (status.IsSuccess())
        Console.WriteLine("Request succeeded.");
    else if (status == HttpStatus.NotFound)
        Console.WriteLine("Resource not found.");
    else
        Console.WriteLine("Unexpected status.");
}

ProcessResponse(HttpStatus.NotFound);
// 404 Not Found
// Resource not found.
----

*Key traits.* Explicit integral values on any member; underlying storage
type can be `byte`, `short`, `ushort`, `int`, `long`, etc.
`Enum.GetValues()` and `Enum.TryParse()` provide runtime reflection.
No per-variant associated data; extension methods approximate attached
behaviour.

=== Reference: Oxygene (RemObjects) enum methods

Oxygene extends the Object Pascal enum with inline method declarations,
keeping Pascal syntax while adding behaviour directly on the type.

.Oxygene — definition
[source,pascal]
----
type
  Status = public enum
    Pending,
    Running,
    Complete,
    Failed;

    public method IsTerminal: Boolean;
    public method Description: String;
  end;

method Status.IsTerminal: Boolean;
begin
  Result := (Self = Complete) or (Self = Failed);
end;

method Status.Description: String;
begin
  case Self of
    Pending:  Result := 'Not started';
    Running:  Result := 'In progress';
    Complete: Result := 'Finished';
    Failed:   Result := 'Error occurred';
  end;
end;
----

.Oxygene — application usage
[source,pascal]
----
procedure ProcessJob(AStatus: Status);
begin
  WriteLn(AStatus.Description);
  if AStatus.IsTerminal then
    WriteLn('Job has reached a terminal state.');
  case AStatus of
    Pending:  WriteLn('Queued');
    Running:  WriteLn('Executing');
    Complete: WriteLn('Done');
    Failed:   WriteLn('Error');
  end;
end;

begin
  ProcessJob(Running);
  // In progress
  // Executing
end.
----

*Key traits.* Methods are declared inside the `enum` block and implemented
as top-level procedures with a `Status.MethodName` prefix — familiar Pascal
style.  No associated data per variant; `case` dispatch remains ordinal.
Oxygene targets .NET and JVM, so enum method calls compile to interface
dispatch under the hood.

=== Option B — Enum with built-in string names

A lightweight extension: the compiler bakes a read-only name lookup table
into the binary so each variant can produce its display name without user
code writing a manual `case` statement.

.Proposed Blaise syntax — definition
[source,pascal]
----
type
  TDirection = enum
    dNorth = ('North'),
    dSouth = ('South'),
    dEast  = ('East'),
    dWest  = ('West');
  end;
----

.Proposed Blaise syntax — application usage
[source,pascal]
----
var
  D: TDirection;
  S: string;
begin
  D := dEast;
  S := TDirection.NameOf(D);   // 'East'
  WriteLn('Heading: ' + S);    // Heading: East

  // Reverse lookup (may raise exception if value not found)
  D := TDirection.FromName('West');
  WriteLn(Integer(D));         // 3
end.
----

*Implementation notes.*  The compiler emits a `$typeinfo_TDirection_names`
data section containing NUL-terminated strings indexed by ordinal.
`NameOf` and `FromName` are compiler-generated RTL wrappers.  This keeps
enum values as plain integers and does not require object allocation.
Works naturally with `case` and `set of`.

*Constraints.*  If ordinals are non-contiguous (e.g. `Idle=10, Done=30`),
the lookup table must be sparse or indexed via a generated search function.
The simpler contiguous case (0-based or gapless explicit values) produces
a flat array.

=== Option C — Enum class with per-variant fields

A richer extension: enum variants are singleton instances of a
compiler-generated class.  Each variant carries typed fields.  Methods
are declared on the class and dispatched virtually.

.Proposed Blaise syntax — definition
[source,pascal]
----
type
  THTTPStatus = enum class
    OK         (Code: Integer = 200, Phrase: string = 'OK'),
    Created    (Code: Integer = 201, Phrase: string = 'Created'),
    NotFound   (Code: Integer = 404, Phrase: string = 'Not Found'),
    ServerError(Code: Integer = 500, Phrase: string = 'Internal Server Error');

    function IsSuccess: Boolean;
    function ToString: string; override;
  end;

function THTTPStatus.IsSuccess: Boolean;
begin
  Result := (Code >= 200) and (Code < 300);
end;

function THTTPStatus.ToString: string;
begin
  Result := IntToStr(Code) + ' ' + Phrase;
end;
----

.Proposed Blaise syntax — application usage
[source,pascal]
----
procedure HandleResponse(S: THTTPStatus);
begin
  WriteLn(S.ToString);
  if S.IsSuccess then
    WriteLn('Request succeeded.')
  else
    WriteLn('Request failed with code ' + IntToStr(S.Code));
end;

begin
  HandleResponse(THTTPStatus.NotFound);
  // 404 Not Found
  // Request failed with code 404
end.
----

*Implementation notes.*  Each variant becomes a global singleton of the
compiler-generated class.  The variable type is an object pointer (8 bytes).
`case` on an enum class requires identity comparison (`ceql` on pointer
values), not ordinal comparison; this is a breaking change from simple enums.
`set of THTTPStatus` is not supported — sets require small-integer ordinals.
The compiler must generate a vtable for the class and emit singleton
initialisers in a dedicated data section.

*Trade-off.*  The expressiveness is high (Java-equivalent), but the
implementation cost is significant and it breaks the simplicity guarantee
that enums are integers.  A new keyword or syntax (`enum class`) is needed
to distinguish this from simple enums so existing code is unaffected.

=== Recommended progression

. *Done* — Explicit ordinal values (`(A=0, B=10, C=100)`) with
  auto-continuation for unlabelled members.
. *Near-term* — Enum intrinsics: `Ord(E)`, `Succ(E)`, `Pred(E)`,
  `Low(TEnum)`, `High(TEnum)`.
. *Medium-term* — Option B (built-in string names) — small compiler
  change, large quality-of-life gain.  Compatible with existing `case`/`set`.
. *Long-term* — Option C (enum class with fields and methods) as a
  distinct `enum class` type form.  Breaking from simple enum semantics
  is acceptable provided it is a new keyword.


