Skip to content
PaskalCPP
Language

Types

Builtin types, aliases, choices, arrays, pointers, sets, routine types, C++ types, records and overlays. Objects, interfaces and owners have their own page: Objects.

Builtin types

TypeSize
int8 int16 int32 int641, 2, 4, 8 bytes
uint8 uint16 uint32 uint641, 2, 4, 8 bytes
float32 float644, 8 bytes
bool char1 byte
wcharthe target's wchar_t: 2 bytes on x86_64-windows-gnu, 4 on x86_64-linux-gnu
string wstringtext
ptrthe untyped pointer, 8 bytes; ptr to T points to a T
varargsonly the type of a variadic routine's local holding varargs.copy()

bool and char are also C++ fundamentals, and both spellings are one C++ type, so plain C++ functions take them as they are. pointer and boolean are not keywords: a C typedef such as Windows' boolean is an ordinary name.

A builtin takes no C++ suffix or qualifier: int32*, int32&, string&&, int32 const, const int32 and volatile float64 are PR0027 (for int32* the message says write ptr to int32). ptr to T, a var / const parameter or the C++ type (int32_t) says it instead.

Aliases

TName = <type expr>; gives a type a second name, and it IS that type: values move between the two spellings freely, across var parameters too. Every type form may be aliased, and an alias works as a cast (TByte(300) with TByte = uint8 gives 44), a parameter type and a return type.

  • A type that names itself (TA = TA, ptr to TList with no forward declaration, array of TGrid) is SM0003.
  • An alias of a variable, a constant, a routine, a module or a choice value is SM0013 ('X' is not a type).
  • A name PaskalCPP does not know, in a type position, is taken as a C/C++ type, so clang reports it.

Choices

TColor = choices(cRed, cGreen, cBlue); names a list of values. Each is reached through its type, TColor.cRed; a bare value is SM0015 (write TColor.cRed), a name the list lacks SM0010.

types_choices.pcpp
module exe types_choices;

type
  TColor = choices(cRed, cGreen, cBlue);
  // An explicit value; the implicit ones after it count on
  TLevel = choices(lLow = -1, lMid, lHigh = 10, lTop);

var
  LC: TColor = TColor.cGreen;

begin
  println("{} {} {}", TColor.cRed, LC, TColor.cBlue);
  println("numbers {} {} {}", int32(TColor.cRed), int32(LC), int32(TColor.cBlue));
  println("lLow = {}, lHigh = {}, lTop = {}", int32(TLevel.lLow), int32(TLevel.lHigh), int32(TLevel.lTop));
  // (T)N makes a value from a number; one outside the list prints as TColor(N)
  println("cast {} {}", (TColor)2, (TColor)7);
  println("cGreen is after cRed: {}", LC > TColor.cRed);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
cRed cGreen cBlue
numbers 0 1 2
lLow = -1, lHigh = 10, lTop = 11
cast cBlue TColor(7)
cGreen is after cRed: true
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • Values are numbered from 0. An explicit = value sets one and the implicit ones after it count on; inside its own list an earlier value is named bare and is its number (fB = fA + 1). Two values may share a number (the first one prints); a value past int32 makes the type 8 bytes.
  • An explicit value is a PaskalCPP integer constant: a variable or C++ (cpp("5"), a C macro) is SM0202, a float SM0200. An empty list is PR0035, a name used twice SM0004.
  • print shows a value by its name, and a number outside the list as TColor(7).
  • A value is no number and a number no value: both directions are SM0200. int32(V) / int64(V) give the number and (TColor)N makes a value, both constants when N is.
  • A value has no arithmetic: only a comparison and in take one; +, +=, ++ and the like are SM0250.
  • A choices bitfield must hold every number of the type as a signed value (SM0316), and holds its values exactly on every target.

Arrays

array[lo..hi] of T is a static array; array of T (or array[] of T) is dynamic, sized by setlength, and new elements start zeroed.

types_arrays.pcpp
module exe types_arrays;

type
  TQuad = array[0..3] of int32;

var
  LA: TQuad;
  LB: TQuad;
  LL: array['A'..'C'] of int32;
  LO: array[-2..2] of int32;
  LD: array of int32;

begin
  LA[0] := 1;
  LA[1] := 2;
  LA[2] := 3;
  LA[3] := 4;
  // A static array is a value: := copies it
  LB := LA;
  LB[1] := 9;
  println("LA {} LB {}", LA, LB);
  println("equal {} len {} size {}", LA = LB, len(LA), size(TQuad));
  // A char-bounded array is indexed by a char
  LL['B'] := 5;
  println("letters {} {}", LL['B'], LL);
  LO[-2] := 7;
  println("offset {} len {}", LO[-2], len(LO));
  // New elements of a dynamic array start zeroed
  setlength(LD, 3);
  LD[2] := 8;
  println("dynamic {} len {}", LD, len(LD));
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
LA [1, 2, 3, 4] LB [1, 9, 3, 4]
equal false len 4 size 16
letters 5 [0, 5, 0]
offset 7 len 5
dynamic [0, 0, 8] len 3
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • Static bounds are constants (a variable is SM0202) of one family, integer, char or one choices type (otherwise SM0322), low not above high (SM0328). A static array names both bounds: array[5] is PR0001.
  • A low bound may be negative or other than 0. An index takes the bounds' family: an integer, a char for a char-bounded array, a value of the type for a choices-bounded one (else SM0261). A constant index outside the bounds is SM0276.
  • len gives the element count, size the bytes, and print shows every element ([1, 2, 3]).
  • A static array is a value: := copies it, a value parameter and a return value are copies, and = compares element by element. A var parameter writes the caller's array, a const one reads it.
  • Two static arrays of other bounds or other element types are other types, and a dynamic array is no static one (SM0200).
  • Arrays nest (array[0..1] of array[0..2] of int32, indexed LG[1][2]) and may be record fields.
  • No type holds more than 1152921504606846975 bytes on any target: a static array past it is SM0277.

Pointers

ptr to T, ptr to const T (what it points to is read only through it: a write is SM0255) and the untyped ptr.

types_pointers.pcpp
module exe types_pointers;

type
  TPoint = record
    X: int32;
    Y: int32;
  end;

var
  GN: int32 = 10;
  GPoint: TPoint;
  GList: array[0..2] of int32;
  LP: ptr to int32;
  LC: ptr to const int32;
  LU: ptr;
  LR: ptr to TPoint;
  LQ: ptr to int32;

begin
  LP := address of GN;
  print("deref {}", LP^);
  LP^ := LP^ + 1;
  println(", after write {}", GN);
  LR := address of GPoint;
  LR^.X := 5;
  LR->Y := 6;
  println("record {} {}", GPoint.X, GPoint.Y);
  // A ptr to T converts to a ptr to const T and to the untyped ptr
  LC := LP;
  println("const read {}", LC^);
  LU := LP;
  LQ := ptr to int32(LU);
  println("round trip {} same {}", LQ^, LQ = LP);
  GList[0] := 10;
  GList[1] := 20;
  GList[2] := 30;
  LQ := address of GList[0];
  print("index {}", LQ[2]);
  LQ := LQ + 1;
  println(", moved {}", LQ^);
  LQ := nil;
  println("nil {}", LQ = nil);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
deref 10, after write 11
record 5 6
const read 11
round trip 11 same true
index 30, moved 20
nil true
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • address of X gives a pointer to X; P^ reads and writes what P points to; P^.F and P->F reach a field; P[i] indexes a typed pointer and P + n moves it; pointers nest (LPP^^).
  • ^ and -> need a pointer (SM0250 on a value), and . on a pointer reaches no field (SM0250: write P^.X or P->X). The untyped ptr points at no type: P^ and P[i] on it are SM0253 (cast it to a typed pointer first).
  • A ptr to T converts to a ptr to const T and to the untyped ptr; nil converts to every pointer. Nothing else converts implicitly (SM0200, with the cast named as the hint): not to a pointer to another type, not const away, not 0 (write nil), not to an integer.
  • The pointer cast is ptr to T(x): it takes a pointer that keeps const, or an integer of pointer width (uint64); uint64(P) goes the other way. A cast that drops const, a float, a narrower integer or nil is SM0265.
  • A ptr to const char holds a string literal, and print shows its text.

Sets

set of lo..hi (a range of constants of one family: integer, char or one choices type) or set of T (an element type: a choices type, char or bool). A set type is declared in a type block and used by its name; a bare set is PR0053, an inline one PR0026.

types_sets.pcpp
module exe types_sets;

type
  TColor = choices(cRed, cGreen, cBlue);
  TSmall = set of 0..15;
  TColors = set of TColor;

var
  LS: TSmall;
  LT: TSmall;
  LC: TColors;
  LN: int32 = 9;

begin
  // A literal lists values and ranges
  LS := [1, 3..5, LN];
  println("literal {}", LS);
  println("in {} {}", 4 in LS, 2 in LS);
  LT := [2, 3..5];
  println("union {} difference {} intersection {}", LS + LT, LS - LT, LS * LT);
  // <= is subset, >= superset
  println("subset {} equal {}", [3, 4] <= LS, LS = LT);
  LC := [TColor.cRed];
  LC += [TColor.cBlue];
  println("colors {}", LC);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
literal [1, 3, 4, 5, 9]
in true false
union [1, 2, 3, 4, 5, 9] difference [1, 9] intersection [3, 4, 5]
subset true equal false
colors [cRed, cBlue]
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • A range's bounds are of one family (SM0320), low not above high (SM0328), with at most 65536 members (SM0321). An integer type as the element names its range instead (SM0318: set of 0..255); set of wchar is SM0319, since its size differs by target (write set of 0..65535).
  • A set literal lists values and ranges ([1, 3..5, LN]); [] is empty. Its elements are of the range's family, and a constant one lies inside the range (SM0200). Two different set types do not convert.
  • in tests a member; + - * and += -= *= are union, difference and intersection; = <> compare, >= <= are superset and subset; / and /= are SM0250.
  • A set prints its members in order; the widest set (set of 0..65535) is 8192 bytes.

Routine types

routine[(params)][: T] is a type expression, written wherever a type goes, named or inline: a field, an array element, a variable, a parameter, a return type, ptr to. routine clink(...) is the C linkage type, a type of its own (a cpplink routine never fills it); no linkage word means cpplink.

types_routines.pcpp
module exe types_routines;

type
  TUnary = routine(AN: int32): int32;
  TOps = record
    Named: TUnary;
    // A routine type written inline, where a type goes
    Square: routine(AN: int32): int32;
  end;

routine Twice(AN: int32): int32;
begin
  return AN * 2;
end;

routine Sqr(AN: int32): int32;
begin
  return AN * AN;
end;

routine Apply(const AF: TUnary; AN: int32): int32;
begin
  return AF(AN);
end;

routine Thrice(AN: int32): int32;
begin
  return AN * 3;
end;

routine Thrice(AS: string): int32;
begin
  return int32(len(AS) * 3);
end;

var
  LOps: TOps;
  LTable: array[0..1] of TUnary;
  LF: TUnary;

begin
  LOps.Named := Twice;
  LOps.Square := Sqr;
  println("field {} inline {}", LOps.Named(5), LOps.Square(5));
  LTable[0] := Twice;
  LTable[1] := Sqr;
  println("table {} {}", LTable[0](4), LTable[1](4));
  println("apply {}", Apply(Twice, 7));
  // An overload set gives its member of the type's signature
  LF := Thrice;
  println("overload {}", LF(10));
  LF := nil;
  println("nil {}", LF = nil);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
field 10 inline 25
table 8 16
apply 14
overload 30
nil true
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • A routine value assigned, passed or returned must match the type's parameters, modes, result and linkage exactly (SM0200, SM0404 for an argument); an overload set gives its member of that signature.
  • A routine value is called like a routine, with exactly its parameters, at least the fixed ones when it ends in ... (SM0401); one with no return type gives no value (SM0406); nil compares.
  • The : after a routine type's parameters is always its return type, so a routine-typed field takes no bitfield width.

C++ types

Any C or C++ type is a type expression, written as C++ spells it: fundamentals and their combinations (unsigned int, long long, auto), cv qualifiers before or after, * with its own cv, one & or && last, qualified names (a leading :: too), template arguments holding a type, a constant or a C++ type id (std::function<int(int)>), an elaborated key (struct tm) and decltype(...).

PaskalCPP values and routines move into C++ types by C++'s rules (a routine into a std::function, nil into a pointer). A C++ mistake inside one is clang's; a mistake in its shape is the parser's: a repeated cv qualifier PR0029, & not last PR0030, template arguments never closed PR0031, struct with no name PR0036, decltype with no parentheses PR0037. See C++ mixing.

Records

A record is a plain aggregate of fields. It may also hold anonymous overlays and operators (see Operators), but no methods (PR0034).

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
  • The prefixes come in the order record(TParent) packed align(n); out of order or twice is PR0032.
  • A record is plain data: a variable, a field, create's object and a setlength element start zeroed.
  • A record literal names its fields, TR(B: 7); on an extension it names the inherited fields too.
  • Records, overlays and arrays of them do not compare: =, <>, < and the like are SM0252, unless the type declares that operator. Arrays of comparable elements compare element by element.
  • create(P) / destroy(P) take a pointer to a record or an overlay (a ptr to int32 is SM0409); destroy frees it and sets the pointer to nil.

Parents and extensions

record(TBase) extends a record and holds every field of its chain; a field reuses no name of the chain (SM0309), and an object, a choices type or an alias of int32 as a parent is SM0308.

  • An extension stands in for its base: a pointer to it converts to a pointer to the base, a value converts to the base (the base part is copied), a variable passes to a var parameter of the base. Never the other way (SM0200; a var parameter SM0405); a downcast is written ptr to TExt(P).
  • A record with the same fields is no extension: records are named types.
  • A base holding a routine-typed field gives dispatch through a record: an extension sets it, and a call through a pointer to the base runs the extension's routine.
  • A C++ struct may be a record's parent, declared as plain C++ in the same file; its members are C++'s, and the record converts to a pointer to that struct.

packed and align

packed packs every field as written, with no padding, nested records, static arrays, sets and the parent included: int8 + int32 is 5 bytes. A string, wstring or dynamic array field, or a record holding one, is SM0331.

A field of a packed record may be misaligned. It is read, copied to a const parameter, printed, indexed, tested with in and assigned whole; but a var argument, address of and a compound assignment (+=) on it are SM0270.

align(n) takes a constant expression: a power of two (SM0310), at most 8192 (SM0312), not below the record's natural alignment (SM0311, lower it with packed). The same limits and codes hold for an align value written in C++ and for a record holding C/C++ fields.

Bitfields

F: T : width; declares a bitfield of an integer, bool, char, wchar or choices type (another type is SM0314); the width is 1 up to the type's bits, wchar 1..16 on every target (else SM0315).

A constant stored into a bitfield (:=, a record literal's field) must fit its width, signed for a signed integer, char and choices, unsigned for an unsigned integer and wchar: 100 does not fit in 'int32 : 3' (-4..3) (SM0200).

Overlays

An overlay holds its fields in the same storage: each field is a view of the same bytes, and its size is its largest field's. An anonymous record inside it groups fields that are the overlay's own (LW.Lo).

types_overlays.pcpp
module exe types_overlays;

type
  TWord = overlay
    Whole: uint32;
    record
      Lo: uint16;
      Hi: uint16;
    end;
  end;

var
  LW: TWord;

begin
  // Every byte starts zeroed
  println("fresh {} {} size {}", LW.Whole, LW.Hi, size(TWord));
  // 0x12345678: both official targets are little-endian
  LW.Whole := 305419896;
  println("lo {} hi {}", LW.Lo, LW.Hi);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
fresh 0 0 size 4
lo 22136 hi 4660
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • An overlay starts zeroed in every byte wherever it is created (a variable, a field, an array element, a setlength element, create's object): a field read before any write reads 0.
  • Its fields may be bitfields, choices, named records, C structs (plain data), an anonymous packed record, or an anonymous record holding an anonymous overlay. An overlay is a field, an array element, a pointer target and a parameter like any type.
  • Each field is plain data: a string, wstring or dynamic array, or a record holding one at any depth, is SM0313, as is a C++ type with a constructor or destructor. An object or an own / ref / weak is SM0357.
  • An overlay holds no methods, hidden members, embeds, init, properties or operators (PR0034); two fields of one name are SM0004.