A var declaration shadowing a same-block const (or a const declared twice in the same block) was silently accepted instead of raising a Duplicate identifier error. Root cause: AnalyseBlock registers consts in the outer scope then pushes a new inner scope before calling AnalyseVarDecls, so FTable.Define (which only checks the current scope) could not detect the clash. Fix: AnalyseVarDecls now scans the block's own ConstDecls list before defining each var name. AnalyseConstDecls now treats a duplicate as an error when the collision originates from an earlier const in the same block; cross-unit const shadowing (e.g. a unit redefining a system.pas constant) continues to be silently accepted. Also removes the redundant LineEnding const from blaise.testing.pas — it is already exported by system.pas. Fixes: https://github.com/graemeg/blaise/issues/30
257 lines
8.4 KiB
Plaintext
257 lines
8.4 KiB
Plaintext
= Blaise Pascal Compiler
|
|
:icons: font
|
|
:source-highlighter: rouge
|
|
|
|
**The Pascal you love, reimagined for the modern era.**
|
|
|
|
Blaise is a next-generation Object Pascal compiler built from the ground up to eliminate decades of legacy baggage. It prioritizes developer productivity, memory safety, and high-performance execution.
|
|
|
|
|
|
== ✨ The Vision
|
|
|
|
The Object Pascal ecosystem has two options: Embarcadero Delphi (proprietary,
|
|
Windows-first) and Free Pascal (open source but carrying 30 years of accumulated
|
|
complexity — five language modes, five string types, and thousands of include files).
|
|
|
|
This compiler takes a different approach:
|
|
|
|
* *One language mode.* No `{$mode}` switches; no legacy dialect support.
|
|
* *One string type.* UTF-8 reference-counted string and 0-based indexing. `RawBytes` for binary data.
|
|
* *One memory model.* Automatic reference counting applies uniformly to
|
|
strings, classes, and interfaces. No manual/auto split between `TObject`
|
|
and `TInterfacedObject`; `[Weak]` breaks cycles. `Free` is retained as a
|
|
synonym for immediate release.
|
|
* *Clean interfaces.* No COM GUIDs; interface dispatch via compile-time vtable mapping.
|
|
* *Reified generics.* Monomorphization at compile time — no type erasure.
|
|
* *Modern build system.* PasBuild with `project.xml`; no makefiles.
|
|
* *First-class debugger.* OPDF is the default debug format; DWARF is not required.
|
|
|
|
See link:docs/design.adoc[docs/design.adoc] for the full architecture and
|
|
implementation plan.
|
|
|
|
The result — A modern, cross-platform Object Pascal compiler targeting native code via
|
|
https://c9x.me/compile/[QBE] (and eventually LLVM). Single language mode,
|
|
single string type, zero-GUID interfaces, reified generics, and first-class
|
|
https://github.com/graemeg/opdebugger[OPDF] debug format support.
|
|
|
|
|
|
== 🚀 Project Status
|
|
|
|
* **Self-Hosting:** Yes. Blaise bootstraps and recompiles itself with byte-for-byte fixpoint. FPC is no longer required — the entire toolchain runs on Blaise alone.
|
|
* **Testing:** 1934 tests and growing (Test-Driven Development from day one). The test suite itself compiles under Blaise.
|
|
* **Backends:** Currently utilising a QBE backend, with an LLVM backend in active development.
|
|
|
|
|
|
[cols="1,3,1", options="header"]
|
|
|===
|
|
| Phase | Goal | Status
|
|
|
|
| 1
|
|
| Bootstrap pipeline — Hello World on Linux x86_64 via PasBuild
|
|
| Complete ✅
|
|
|
|
| 2
|
|
| Type system — classes, records, ARC, exceptions
|
|
| Complete ✅
|
|
|
|
| 3
|
|
| Generics + zero-GUID interfaces
|
|
| Complete ✅
|
|
|
|
| 4
|
|
| OPDF debug info emission
|
|
| Complete ✅
|
|
|
|
| 5
|
|
| Self-hosting + LLVM + Windows + macOS ARM64
|
|
| In-Progress
|
|
|
|
| 6
|
|
| LSP + VS Code extension
|
|
| Planned
|
|
|
|
| 7
|
|
| Migration analyser for FPC/Delphi codebases
|
|
| Planned
|
|
|===
|
|
|
|
== What Is Dropped From Classic Pascal
|
|
|
|
[cols="1,3", options="header"]
|
|
|===
|
|
| Feature | Reason for removal
|
|
|
|
| `ShortString`, `AnsiString`, `WideString`, `UnicodeString`
|
|
| Replaced by a single UTF-8 reference-counted `string` type
|
|
|
|
| `with` statement
|
|
| Source of hard-to-diagnose symbol resolution bugs; breaks static analysis
|
|
|
|
| Old-style `object` types
|
|
| Use `record` (stack/value) or `class` (heap/reference) instead
|
|
|
|
| COM-style interface GUIDs
|
|
| Interface dispatch via compile-time vtable; GUIDs are unnecessary complexity
|
|
|
|
| Multiple language modes
|
|
| One dialect, maintained well, beats five dialects maintained poorly
|
|
|
|
| `assign`, `reset`, `rewrite`, `blockread`
|
|
| Replaced by a stream-based I/O RTL
|
|
|
|
| `TObject` vs `TInterfacedObject` split
|
|
| One unified class model under automatic reference counting; `[Weak]`
|
|
breaks cycles
|
|
|===
|
|
|
|
== 📢 Community
|
|
|
|
The core architecture is still being finalised, so the project is not yet
|
|
accepting code contributions. Feedback on language design, syntax choices, and
|
|
the future direction of Blaise is very welcome — please use the
|
|
https://github.com/graemeg/blaise/discussions[Discussions] tab on GitHub.
|
|
|
|
|
|
== Repository Layout
|
|
|
|
This project uses PasBuild's multi-module layout. Each subdirectory with a
|
|
`project.xml` is an independent module; the root `project.xml` is the aggregator.
|
|
|
|
....
|
|
project.xml Root aggregator (packaging=pom)
|
|
│
|
|
├── compiler/ The compiler binary (packaging=application)
|
|
│ ├── project.xml
|
|
│ └── src/
|
|
│ ├── main/pascal/ uLexer, uParser, uAST, uCodeGenQBE, ...
|
|
│ └── test/pascal/ Test suite (blaise.testing, compiled by Blaise)
|
|
│
|
|
├── runtime/ Always-linked runtime (packaging=library)
|
|
│ ├── project.xml
|
|
│ ├── Makefile
|
|
│ └── src/
|
|
│ ├── main/c/ C shims (blaise_exc.c, blaise_io.c, ...)
|
|
│ ├── main/pascal/ system.pas, blaise_str.pas, blaise_arc.pas, ...
|
|
│ └── test/pascal/ Runtime tests (punit, compiled by Blaise)
|
|
│
|
|
├── stdlib/ Standard library — opt-in via uses clause
|
|
│ ├── project.xml
|
|
│ └── src/
|
|
│ └── main/pascal/ sysutils.pas, classes.pas, math.pas, ...
|
|
│
|
|
├── tools/
|
|
│ └── migration-analyser/ FPC/Delphi migration report tool (packaging=application)
|
|
│ ├── project.xml depends on compiler module
|
|
│ └── src/
|
|
│ ├── main/pascal/
|
|
│ └── test/pascal/
|
|
│
|
|
├── vendor/qbe/ Vendored QBE backend source (pinned, built from source)
|
|
└── docs/ Design documents and specifications
|
|
....
|
|
|
|
PasBuild compiles each module to its own `target/` subdirectory. Build output is
|
|
never committed to the repository.
|
|
|
|
== Building
|
|
|
|
=== Prerequisites
|
|
|
|
* A previously released Blaise binary (see `releases/`)
|
|
* https://github.com/graemeg/pasbuild[PasBuild]
|
|
* A C compiler (`gcc` or `clang`) for building the vendored QBE backend and linking
|
|
* GNU `make` for the runtime build
|
|
|
|
NOTE: FPC is *not* required. Blaise is fully self-hosting — each release binary
|
|
compiles the next version. The bootstrap chain starts from the binary in
|
|
`releases/`.
|
|
|
|
=== Bootstrap from a release
|
|
|
|
The runtime build compiles its Pascal units (`blaise_str.pas`, `blaise_arc.pas`,
|
|
`blaise_sys.pas`) using the Blaise binary at `compiler/target/blaise`. On a
|
|
clean checkout that binary does not exist yet, so the release binary must be
|
|
passed explicitly via the `BLAISE` make variable.
|
|
|
|
[source,shell]
|
|
----
|
|
# 0. Build the vendored QBE backend (once-off — not needed again unless
|
|
# you update vendor/qbe/)
|
|
cd vendor/qbe && make && cd ../..
|
|
|
|
# 1. Build the runtime using the release binary (BLAISE= avoids chicken-and-egg)
|
|
cd runtime && make BLAISE=../releases/v0.7.0/blaise && make install && cd ..
|
|
|
|
# 2. Compile the compiler using the latest release binary
|
|
releases/v0.7.0/blaise \
|
|
--source compiler/src/main/pascal/Blaise.pas \
|
|
--unit-path compiler/src/main/pascal \
|
|
--unit-path runtime/src/main/pascal \
|
|
--unit-path stdlib/src/main/pascal \
|
|
--emit-ir > /tmp/blaise.ssa
|
|
|
|
# 3. Assemble and link
|
|
vendor/qbe/qbe -o /tmp/blaise.s /tmp/blaise.ssa
|
|
gcc -o compiler/target/blaise /tmp/blaise.s compiler/target/blaise_rtl.a
|
|
----
|
|
|
|
Once `compiler/target/blaise` exists, subsequent RTL rebuilds (`make && make install`)
|
|
work without the override. The QBE build in step 0 is a one-off and does not
|
|
need to be repeated when rebuilding the compiler.
|
|
|
|
=== Build via PasBuild
|
|
|
|
PasBuild can drive the full compile and test cycle using a Blaise binary:
|
|
|
|
[source,shell]
|
|
----
|
|
pasbuild compile -m blaise-compiler --compiler compiler/target/blaise
|
|
pasbuild test -m blaise-compiler --compiler compiler/target/blaise
|
|
----
|
|
|
|
=== Run tests
|
|
|
|
[source,shell]
|
|
----
|
|
pasbuild test -m blaise-compiler --compiler compiler/target/blaise
|
|
----
|
|
|
|
=== Verify self-hosting fixpoint
|
|
|
|
After any compiler change, verify that the compiler reproduces itself:
|
|
|
|
[source,shell]
|
|
----
|
|
./scripts/fixpoint.sh
|
|
----
|
|
|
|
This generates stage-2 and stage-3 IR and confirms they are identical.
|
|
|
|
=== Running the compiler
|
|
|
|
Once built, the compiler binary is at `compiler/target/blaise`.
|
|
|
|
[source,shell]
|
|
----
|
|
# Compile a single-file program
|
|
compiler/target/blaise --source Hello.pas --emit-ir > Hello.ssa
|
|
vendor/qbe/qbe -o Hello.s Hello.ssa
|
|
gcc -o Hello Hello.s compiler/target/blaise_rtl.a
|
|
|
|
# Compile with unit search paths
|
|
compiler/target/blaise --source MyApp.pas \
|
|
--unit-path src/units \
|
|
--emit-ir > MyApp.ssa
|
|
|
|
# Emit QBE IR only (useful for debugging the compiler itself)
|
|
compiler/target/blaise --source Hello.pas --emit-ir
|
|
----
|
|
|
|
== Licence
|
|
|
|
Apache License v2.0 with Runtime Library Exception. See link:LICENSE[LICENSE].
|
|
|
|
|
|
---
|
|
*Built with ❤️ for the Pascal community by Graeme.*
|