pascal-rs — A Modern Pascal Compiler & Interpreter in Rust
pascal-rs is a Pascal compiler, interpreter, and package manager written in Rust. It supports standard Pascal and a growing subset of Object Pascal (classes, exceptions, inheritance, virtual dispatch), with a modern cargo/npm-style build system (pascal.toml), backed by 228 automated tests and 10 runnable example programs.
Status
| Metric | Value |
|---|---|
| Tests | 228 passing (127 unit + 101 integration) |
| Language | Standard Pascal + Object Pascal subset |
| Interpreter | Full-featured tree-walking execution |
| Compiler | x86-64 assembly generation with optimizations |
| Build System | pascal.toml manifest, dependency management, lock file |
| Edition | Rust 2024 |
What's New
Object Pascal in the Interpreter
The interpreter now supports core Object Pascal features at the AST/runtime level:
- Classes —
Value::Objectwith field access, constructors, destructors, method dispatch - Inheritance — single inheritance with field/method merging
- Virtual/Override — vtable-style dispatch via
find_method_in_hierarchy is/as— runtime type checks walking the inheritance chaininherited— call parent class methods- Properties — read/write resolution infrastructure
- Exceptions —
try/except/finally,raise,on E: Type domatching, re-raise
Interpreter Enhancements
- Arrays —
SetLength, indexing with bounds checking,high(),low(),length() - Records — field access and assignment via dot notation
- String indexing —
s[i](1-indexed, Pascal style) exit— early return from functions/procedures, with optional return value (exit(42))break— loop terminationwithstatement — pushes object/record fields into scopeusesclause — loads and imports.pasunit files- Nested functions — proper scoping for functions declared inside functions
Build System & Package Manager
A modern cargo/npm-style project management system:
pascal init <name>— scaffold a new project withpascal.toml,src/,tests/,examples/pascal build— compile all units in dependency order (topological sort)pascal run— run the project's main program (orpascal run file.pasfor single files)pascal add <dep>— add a dependency (version,--path, or--git)pascal remove <dep>— remove a dependencypascal.toml— project manifest (package metadata, dependencies, build config)pascal.lock— reproducible builds with SHA-256 checksums
Parser Improvements
- Source location tracking — error messages now include
at line N, column M - Error recovery —
consume_or_skipandsynchronizefor resilient parsing exit/breaktokens — properly parsed as statements (not silently skipped)
Bug Fixes
- Recursive function scoping —
set_local_variableprevents recursive calls from clobbering parent scope return variables exitinsideif/begin..end—EarlyReturnnow propagates correctly through all control flow
10 Example Programs
Validated end-to-end (source → lexer → parser → interpreter):
| # | File | Features |
|---|---|---|
| 01 | 01_basics.pas |
Variables, arithmetic, if/else, while, for, repeat/until, case |
| 02 | 02_functions.pas |
Factorial, Fibonacci, IsPrime, GCD, procedures |
| 03 | 03_strings.pas |
Reverse, palindrome, char counting, copy, pos, upcase |
| 04 | 04_arrays.pas |
SetLength, indexing, high/low/length |
| 05 | 05_classes.pas |
Shape area calculations with functions |
| 06 | 06_exceptions.pas |
try/except, try/finally, nested exceptions, raise |
| 07 | 07_nested_functions.pas |
exit() with return values, early return patterns |
| 08 | 08_math_algorithms.pas |
GCD, Collatz, digit sum, fast exponentiation |
| 09 | 09_class_hierarchy.pas |
Polymorphic dispatch, string comparisons |
| 10 | 10_comprehensive.pas |
Recursion, primes, strings, case, exceptions combined |
Why pascal-rs?
Compared to Alternatives
| pascal-rs | Free Pascal (FPC) | Delphi | GNU Pascal | |
|---|---|---|---|---|
| Implementation language | Rust (memory-safe) | Object Pascal/C | C++ | C |
| Interpreter mode | Built-in tree-walker | No | No | No |
| Package manager | Built-in (pascal.toml) |
No | No | No |
| Test suite | 228 automated tests | Large but external | Proprietary | Minimal |
| Object Pascal | Subset (classes, exceptions, virtual) | Full | Full | Partial |
| Trait-based design | Yes — testable, extensible | No | No | No |
| Error messages | Line/column, colored | Basic | Good | Basic |
| x86-64 codegen | Yes, with optimizations | Mature, multi-target | Mature | Basic |
| Active | Yes | Yes | Yes | No (2006) |
| Cross-platform | Yes (Rust targets) | Yes | Windows-focused | Yes |
| License | Apache-2.0 | LGPL | Commercial | GPL |
When to Use pascal-rs
- Learning compiler construction — clean Rust codebase, trait-based architecture, well-tested
- Running Pascal programs quickly —
pascal run program.paswith no assembly/linking step - Research & experimentation — easy to extend with new optimizations or language features
- Small projects & algorithms — full standard Pascal with functions, recursion, strings, arrays
When to Use Something Else
- Large production codebases — FPC or Delphi have decades of maturity
- Full Delphi compatibility — pascal-rs covers a subset of Object Pascal
- GUI applications — FPC/Lazarus or Delphi have mature widget libraries
- Multi-target compilation — FPC supports ARM, MIPS, PowerPC, WebAssembly, etc.
Quick Start
Installation
Create a Project
# Scaffold a new project
# Build all units in dependency order
# Run the project
This creates:
myapp/
├── pascal.toml # Project manifest
├── src/
│ └── myapp.pas # Main program
├── tests/
├── examples/
├── .gitignore
└── README.md
Run a Single File
# Interpret directly (no project needed)
# Compile to x86-64 assembly
Run All Tests
test result: ok. 228 passed; 0 failed; 0 ignored
Usage
Commands
| Command | Description | Example |
|---|---|---|
init |
Create a new project | pascal init myapp |
build |
Build the current project | pascal build |
run |
Run a program or project | pascal run / pascal run file.pas |
add |
Add a dependency | pascal add mathlib |
remove |
Remove a dependency | pascal remove mathlib |
compile |
Compile a single file | pascal compile prog.pas -S |
info |
Inspect a PPU file | pascal info module.ppu |
clean |
Remove build artifacts | pascal clean |
Project Management
# Create a new project
# Add dependencies
# Remove a dependency
# Build (compiles all units in dependency order)
# Run the project's main program
pascal.toml
[]
= "calculator"
= "0.2.0"
= "A calculator app"
= ["Alice"]
= "MIT"
= "src"
= "calculator.pas"
[]
= "1.0"
= { = "../shared/utils" }
= { = "https://github.com/example/network.git", = "main" }
[]
= 2
= "build"
Single-File Mode
Compile Options
| Option | Description |
|---|---|
-o, --output <DIR> |
Output directory |
-O, --optimize <LEVEL> |
Optimization level (0–3) |
-v, --verbose |
Verbose output |
-S, --assembly |
Generate assembly output |
-d, --debug |
Debug information |
-I, --include <DIR> |
Add unit search path |
--no-cache |
Disable PPU caching |
Examples
Recursive Fibonacci with Exit
program Fibonacci;
var
i: integer;
(n: integer): integer;
begin
if n <= 1 then
exit(n);
Fib := Fib(n - 1) + Fib(n - 2);
end;
begin
for i := 0 to 10 do
writeln('Fib(', i, ') = ', Fib(i));
end.
Prime Sieve with Early Return
program Primes;
var
i, count: integer;
(n: integer): boolean;
var
i: integer;
begin
if n < 2 then
begin
IsPrime := false;
exit;
end;
i := 2;
while i * i <= n do
begin
if n mod i = 0 then
begin
IsPrime := false;
exit;
end;
inc(i);
end;
IsPrime := true;
end;
begin
count := 0;
for i := 2 to 100 do
if IsPrime(i) then
inc(count);
writeln('Primes under 100: ', count); { Output: 25 }
end.
String Operations
program Strings;
var
s, rev: string;
i, len: integer;
(s: string): string;
var
i, n: integer;
r: string;
begin
n := length(s);
r := '';
for i := n downto 1 do
r := concat(r, copy(s, i, 1));
ReverseStr := r;
end;
begin
s := 'Hello, Pascal!';
writeln('Original: ', s);
writeln('Length: ', length(s));
writeln('Upper: ', upcase(s));
writeln('Reversed: ', ReverseStr(s));
writeln('Substring: ', copy(s, 8, 6)); { Pascal }
writeln('Char 1: ', s[1]); { H }
end.
Exception Handling
program Exceptions;
begin
try
writeln('Before raise');
raise Exception.Create('something went wrong');
writeln('SHOULD NOT PRINT');
except
on E: Exception do
writeln('Caught: ', E.Message);
end;
try
raise Exception.Create('error');
finally
writeln('Finally always runs');
end;
end.
Math Algorithms
program Math;
var
a, b: integer;
(a, b: integer): integer;
begin
while b <> 0 do
begin
a := a mod b;
if a = 0 then
begin
GCD := b;
exit;
end;
b := b mod a;
end;
GCD := a;
end;
(base, exp: integer): integer;
var
result: integer;
begin
result := 1;
while exp > 0 do
begin
if exp mod 2 = 1 then
result := result * base;
base := base * base;
exp := exp div 2;
end;
Power := result;
end;
begin
writeln('GCD(48, 18) = ', GCD(48, 18)); { 6 }
writeln('2^10 = ', Power(2, 10)); { 1024 }
end.
Unit System
MathUtils.pas:
unit MathUtils;
interface
(a, b: integer): integer;
(n: integer): boolean;
implementation
(a, b: integer): integer;
begin
Add := a + b;
end;
(n: integer): boolean;
begin
IsEven := (n mod 2) = 0;
end;
end.
Main.pas:
program Main;
uses MathUtils;
var
x: integer;
begin
x := Add(10, 5);
writeln('10 + 5 = ', x);
writeln('Is even: ', IsEven(x));
end.
See the examples/ directory for all 10 validated example programs.
Architecture
Source (.pas) → Lexer (logos) → Parser (recursive descent) → AST
│
┌─────────────────────────┼──────────────────┐
▼ ▼ ▼
Interpreter Optimizer Type Checker
(tree-walking) (const fold, DCE) (basic validation)
│ │
▼ ▼
Direct Output Code Generator → Assembly (.s)
Project Structure
pascal-rs/
├── src/
│ ├── lexer.rs # Lexical analysis (logos-based, 100+ tokens)
│ ├── parser/ # Recursive descent parser
│ │ ├── mod.rs # Parser core with source location tracking
│ │ ├── expression.rs # Expression parsing (precedence climbing)
│ │ ├── statement.rs # Statement parsing (if, while, for, try, exit)
│ │ └── decl.rs # Declaration parsing (var, type, class, function)
│ ├── ast.rs # AST node definitions
│ ├── interpreter.rs # Tree-walking interpreter (Object Pascal support)
│ ├── build_system.rs # Package manager & build system (pascal.toml)
│ ├── type_checker.rs # Type validation
│ ├── optimizer.rs # Optimization passes
│ ├── unit_codegen.rs # x86-64 code generation
│ ├── resolver.rs # Symbol resolution
│ └── traits/ # Trait abstractions for testability
├── tests/ # 101 integration tests across 8 test files
├── examples/ # 10 validated example programs
├── ARCHITECTURE.md
├── SPEC.md
└── TODO.md
Built-in Functions & Procedures
Math: abs, sqr, sqrt, sin, cos, ln, exp, round, trunc
String: length, concat, copy, pos, upcase, lowercase, inttostr, strtoint
Ordinal: ord, chr, odd, succ, pred, inc, dec
Array: length, high, low, setlength
I/O: write, writeln, readln
Control: exit, break, halt, random
Testing
Test Breakdown
Library (unit tests) 127 (includes 12 build system tests)
Example pipeline tests 19
Compiler codegen tests 10
Complex validation 9
Integration tests 10
Interpreter tests 11
Simple compiler (parser) 18
Simple interpreter 13
Type checker 10
Basic 1
─────────────────────────────────
Total 228
Documentation
- ARCHITECTURE.md — module design and data flow
- SPEC.md — language feature matrix (parser/interpreter/codegen status)
- TODO.md — development roadmap and completed phases
Current Limitations
- Inline class method bodies — parser doesn't yet support method bodies inside
typeclass declarations (interpreter supports classes via AST) - Array element assignment —
arr[i] := valnot yet supported - Multi-dimensional arrays — single dimension only in interpreter
- File I/O — not implemented
- Generics/templates — not implemented
- GUI framework — not included (use FPC/Lazarus for GUI apps)
License
Apache-2.0
Made with Rust — 228 tests passing | Standard Pascal + Object Pascal subset | Compiler + Interpreter + Package Manager