Tour
A first program, how to build and run it on Windows and Linux, and a short walk through the language. Each stop links to the page that covers it in full.
A first program
Every source file is a module; the smallest program is an exe whose begin body prints one line. Save this as hello.pcpp: the module name must equal the file name.
module exe hello;
begin
println("Hello from PaskalCPP!");
end.
Build and run it:
pcpp hello.pcpp -r
Hello from PaskalCPP!
[Heap] Allocs: 0, Frees: 0, Leaked: 0
pcpp writes C++23 for the module, builds it with the bundled Zig toolchain, and with -r runs the program. The executable is in output\bin under the folder you ran pcpp in. The last line is the heap report of a debug build, the default optimize level.
Release builds and Linux
The same source builds for x86_64-linux-gnu from Windows; with -r the Linux program runs through WSL.
pcpp hello.pcpp -r -t x86_64-linux-gnu
pcpp hello.pcpp -r -opt fast
A release level prints no heap report. The source can say all of this itself, with @target and @optimize: see Directives, Building and The pcpp command.
Declarations
Constants always name their type, variables start at their type's zero, routines take parameters by value, var or const, and every name is declared before it is used.
module exe declarations;
// A const always names its type; a const can bound an array
const
CCount: int32 = 3;
var
GSlots: array[0..CCount - 1] of int32;
GTotal: int32 = 10;
GZero: int32;
GName: string = "PaskalCPP";
// IsOdd is called before its full declaration: forward makes it visible
forward routine IsOdd(AN: int32): bool;
routine IsEven(AN: int32): bool;
begin
if AN = 0 then
return true;
end;
return IsOdd(AN - 1);
end;
routine IsOdd(AN: int32): bool;
begin
if AN = 0 then
return false;
end;
return IsEven(AN - 1);
end;
// var: the caller's variable itself; const: read only
routine Bump(var AN: int32);
begin
AN := AN + 1;
end;
routine Twice(const AN: int32): int32;
begin
return AN * 2;
end;
// Overloads differ in their parameter types
routine Kind(AN: int32): string;
begin
return "int32";
end;
routine Kind(AS: string): string;
begin
return "string";
end;
routine Kind(AB: bool): string;
begin
return "bool";
end;
// A trailing ... makes a routine variadic; varargs reads the extra arguments
routine Total(ABase: int32; ...): int64;
var
LT: int64;
LI: int32;
begin
LT := ABase;
LI := 0;
while LI < varargs.count do
LT := LT + varargs.get(LI, int32);
LI := LI + 1;
end;
return LT;
end;
begin
println("CCount = {}, len(GSlots) = {}", CCount, len(GSlots));
println("GTotal = {}, GZero = {}, GName = {}", GTotal, GZero, GName);
println("IsEven(10) = {}, IsOdd(7) = {}", IsEven(10), IsOdd(7));
Bump(GTotal);
println("Bump: {}", GTotal);
println("Twice(21) = {}", Twice(21));
println("Kind(5) = {}, Kind(\"x\") = {}, Kind(true) = {}", Kind(5), Kind("x"), Kind(true));
println("Total(100, 1, 2, 3) = {}", Total(100, 1, 2, 3));
// A body var is visible from its own line on
var LInner: int32 = GTotal * 2;
println("LInner = {}", LInner);
end.
More on Declarations.
Types
Records are plain data, extended with record(TBase); choices, sets, static and dynamic arrays, pointers, overlays and routine types complete the set, and any C or C++ type is a type too.
module exe types_records;
type
TBase = record
A: int32;
end;
// An extension holds every field of its chain
TExt = record(TBase)
B: int32;
end;
TPk = record packed
X: int8;
Y: int32;
end;
TBits = record
S: int32 : 3;
F: bool : 1;
end;
routine ShowBase(const AB: TBase);
begin
println("base {}", AB.A);
end;
var
LE: TExt;
LZ: TExt;
LP: ptr to TBase;
LB: TBits;
begin
println("zero {} {}", LZ.A, LZ.B);
// A record literal names the inherited fields too
LE := TExt(A: 1, B: 2);
println("literal {} {} size {}", LE.A, LE.B, size(TExt));
ShowBase(LE);
LP := address of LE;
println("through base {}", LP^.A);
println("packed {}", size(TPk));
LB.S := -4;
LB.F := true;
println("bits {} {}", LB.S, LB.F);
end.
zero 0 0
literal 1 2 size 8
base 1
through base 1
packed 5
bits -4 true
[Heap] Allocs: 0, Frees: 0, Leaked: 0
More on Types.
Objects
An object is a value with methods, reached through self. A method is virtual only when it says so, and override is required.
module exe objects_virtual;
type
TShape = object
virtual method Area(): int32; abstract;
virtual method Name(): string;
begin
return "shape";
end;
method Kind(): string;
begin
return "plain";
end;
end;
TSquare = object(TShape)
S: int32;
override method Area(): int32;
begin
return self.S * self.S;
end;
override method Name(): string;
begin
return "square";
end;
end;
// A final object: no type may extend TCube
TCube = final object(TSquare)
override final method Name(): string;
begin
return "cube";
end;
override method Area(): int32;
begin
// parent.Area() is TSquare's own, not dispatched
return 6 * parent.Area();
end;
end;
routine Show(var AShape: TShape);
begin
println("{} {} {}", AShape.Name(), AShape.Area(), AShape.Kind());
end;
var
LSq: TSquare;
LCube: TCube;
LP: ptr to TShape;
begin
LSq.S := 3;
LCube.S := 2;
Show(LSq);
Show(LCube);
LP := address of LCube;
println("ptr {} {}", LP^.Name(), LP^.Area());
end.
square 9 plain
cube 24 plain
ptr cube 24
[Heap] Allocs: 0, Frees: 0, Leaked: 0
Objects also have init and done, owners on the heap (own, ref, weak), interfaces, embeds, properties and operators: see Objects.
C and C++
#include a header and call it. Here the C++ standard library is used straight from PaskalCPP:
module exe cpp_mixing_std;
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
template<typename T>
T Max2(T a, T b) {
return a > b ? a : b;
}
var
LS: std::string;
LV: std::vector<int>;
LD: float64;
LX: int32;
begin
LS := "hello world";
if LS.find("world") <> std::string::npos then
println("found at {}", LS.find("world"));
end;
LV.push_back(3);
LV.push_back(1);
LV.push_back(2);
std::sort(LV.begin(), LV.end());
println("sorted {} {} {}", LV[0], LV[1], LV[2]);
println("size {}", LV.size());
LX := 3;
LD := std::pow(float64(LX), 2.0);
LX := Max2(LX, int32(LD));
println("max {}", LX);
end.
found at 6
sorted 1 2 3
size 3
max 9
[Heap] Allocs: 0, Frees: 0, Leaked: 0
More on C++ mixing.
Errors
A mistake in PaskalCPP code is reported by PaskalCPP, at the .pcpp file, line and column, before anything is built. Here an int32 is stored into an int8 with no cast:
More on Diagnostics and No hidden conversions.
Exceptions
guard catches a raise, a C++ exception or a hardware fault; finally runs on every way out.
module exe statements_guard_flow;
routine Find(): int32;
begin
guard
return 7;
finally
println("return finally");
end;
end;
routine Handle(): int32;
begin
guard
raisecode(9, "nine");
except
print("except {}", exccode());
return -1;
finally
println(" finally");
end;
return 0;
end;
var
LI: int32;
begin
for LI := 1 to 5 do
guard
if LI = 2 then
print("skip {}", LI);
skip;
end;
if LI = 3 then
print("leave {}", LI);
leave;
end;
print("pass {}", LI);
finally
println(" finally");
end;
end;
println("loop done");
println("found {}", Find());
println("handled {}", Handle());
end.
pass 1 finally
skip 2 finally
leave 3 finally
loop done
return finally
found 7
except 9 finally
handled -1
[Heap] Allocs: 0, Frees: 0, Leaked: 0
More on guard and The runtime.
Tests
Test blocks follow a module's end.; with @unitTestMode on; the exe runs them and reports.
module exe testing_runner;
@unitTestMode on;
import testing_unit;
var
GHits: int32 = 0;
GText: string = "a";
initialize
begin
println("main init");
end;
finalize
begin
println("main final");
end;
begin
println("the body does not run in test mode");
end.
test "asserts that hold"
var
LP: ptr to int32;
LW: uint32;
LI: int32;
begin
LP := address of GHits;
LW := 7;
LI := 2;
testAssertNotNil(LP);
testAssertEqualInt(16, testing_unit.Square(4));
testAssertEqualInt(7, LW);
testAssertEqualFloat(2.0, LI);
testAssertEqualFloat(0.3, 0.1 + 0.2);
testAssertEqualStr("ab", GText + "b");
end;
test "asserts that fail"
begin
testAssertEqualInt(10, testing_unit.Square(3));
testFail("still runs after a failure");
end;
test "a raise ends the test"
begin
raisecode(7, "stop");
testFail("never reached");
end;
test "a fault ends the test"
var
LQ: ptr to int32;
begin
LQ := nil;
LQ^ := 1;
testFail("never reached");
end;
test "returns early"
begin
GHits := GHits + 1;
return;
testFail("never reached");
end;
More on Unit testing.
Where next
- The language, construct by construct: start at Modules.
- Working with C and C++: C++ mixing.
- The editor, the language server and the debugger: The editor, The language server, The debugger.
- Everything in one place: Grammar and Reference.