Skip to content
PaskalCPP
Start

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.

hello.pcpp
module exe hello;

begin
  println("Hello from PaskalCPP!");
end.

Build and run it:

text
pcpp hello.pcpp -r
output on x86_64-windows-gnu and x86_64-linux-gnu
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.

text
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.

declarations.pcpp
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.

types_records.pcpp
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.
output on x86_64-windows-gnu and x86_64-linux-gnu
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.

objects_virtual.pcpp
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.
output on x86_64-windows-gnu and x86_64-linux-gnu
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:

cpp_mixing_std.pcpp
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.
output on x86_64-windows-gnu and x86_64-linux-gnu
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:

errorSM0200…/samples/diagnostics_narrowing.pcpp(33,13)
'int32' does not convert implicitly to 'int8'; write int8(LBig)

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.

statements_guard_flow.pcpp
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.
output on x86_64-windows-gnu and x86_64-linux-gnu
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.

testing_runner.pcpp
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