Skip to content
PaskalCPP
Language

Expressions

Operators and how tightly they bind, arithmetic with no hidden conversions, comparisons, the conditional, assignment, casts, calls and members, literals, and C++ written inside an expression.

Comparisons of records, arrays, sets and pointers are on Types; is, as and declared operators on Objects.

expressions_power.pcpp
module exe expressions_power;

var
  LT: bool = true;
  LF: bool = false;
  LA: int32 = 6;
  LU: uint32 = 4;
  LC: char = 'a';
  LNeg: float64 = -3.75;
  LN8: int8 = 126;

begin
  println("shl before + {}", 1 + 2 shl 1);
  println("* before shl {}", 2 * 3 shl 1);
  println("& before = {}", LA & 2 = 2);
  println("not before = {}", not LT = LF);
  LC++;
  println("char step {}", LC);
  println("truncates {}", int32(LNeg));
  println("mixed sign {}", uint32(LA) + LU);
  println("test a bit {}", (LA & 1) <> 0);
  LN8 += 1;
  println("compound {}", LN8);
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
shl before + 5
* before shl 12
& before = true
not before = true
char step b
truncates -3
mixed sign 10
test a bit false
compound 127
[Heap] Allocs: 0, Frees: 0, Leaked: 0

Binding power

From loosest to tightest:

LevelOperators
assignment (right associative):= and the compound forms
conditional (right associative)? :
oror ||
andand &&
comparison= <> < > <= >= <=> in is
adding+ - | xor
shiftshl shr << >>
multiplying* / div mod % & as
prefixthe prefix operators and the C-style cast
call( )
postfixindex, member, ++ --
derefthe postfix ^
  • | and & sit with + and * (Pascal placement), not below the comparisons as in C++: LA | LB = 3 is (LA | LB) = 3, and LA & 2 = 2 is (LA & 2) = 2.
  • The shifts bind tighter than + and looser than *: 1 + 2 shl 1 is 5 and 2 * 3 shl 1 is 12.
  • not binds tighter than a comparison: not LT = LF is (not LT) = LF.
  • Comparisons do not chain: LN < LM < 3 is PR0038.
  • One spelling per operator: C++'s == and != are PR0039, naming PaskalCPP's = and <>, and the C++ word operators (bitand, compl and the others) are PR0040 with the PaskalCPP form.
  • = is always equality: an expression statement whose operator is = (LN = 5;) is PR0048, since it compares and assigns nothing.
  • ^ after an operand is the deref, never C++'s xor; bitwise xor is the word xor.
C++ side

Two levels sit where C++ does not put them: | and & bind tighter than a comparison, and the shifts bind tighter than + and -. When in doubt, parenthesize.

Arithmetic

+ - * work on numbers; / is always real division (7 / 2 is 3.5); div and mod are integer division and remainder, and % is the same as mod. div truncates toward zero, so -7 div 2 is -3 and -7 mod 2 is -1.

A result has the type C++ gives it (the usual arithmetic conversions): int8 + int8 is int32, / of two integers float64, - or ~ of an int8 int32, a shift the promoted left operand's type, ++ / -- the operand's own type.

No hidden conversions

Each operand must convert to the result's type with no hidden conversion: LA + LU (int32 with uint32) is SM0200 ('int32' does not convert implicitly to 'uint32'); write the cast on one side, uint32(LA) + LU.

  • / converts both integer operands to float64 under the same rule, so an int64 or uint64 operand needs float64(...).
  • The result then converts where it goes: LN := LA + LB with int8 operands and an int8 target is SM0200; int8(LN + 5) stores it.
  • bool, char and wchar are no arithmetic operands: LC + 1 is SM0250; int32(LC) + 1 is the number. Comparisons, in and ++ / -- of a char stay legal: LC++ steps 'a' to 'b'.
  • A string / wstring takes + (concatenation), comparisons with the string family of its width, and in; any other operator is SM0250.
  • An opaque C/C++ operand keeps the result opaque, judged by clang.
errorSM0200…/samples/diagnostics_narrowing.pcpp(33,13)
'int32' does not convert implicitly to 'int8'; write int8(LBig)

Constant folding

A constant expression is folded and judged when the program is compiled: an overflow is SM0256 (constant overflows 'int32': 2147483647 + 1), a zero divisor SM0257 (5 div 0), the minimum div -1 SM0258, and a constant shift count outside 0 .. bits - 1 SM0259 (shift count 32 outside 0..31).

Bitwise and logical

shl shr << >> | & xor ~ take integers: 1 shl 4 is 16, 6 & 3 is 2, 5 xor 3 is 6, ~LN of 5 is -6. A float operand is SM0250, and so is & on two bools.

xor also takes two bools: then it is logical and its result a bool (LT xor LF is true, LT xor LT false).

The result of a bitwise operator on integers is a number: LB := LN & 1 is SM0200 ('int32' does not convert implicitly to 'bool'). A bool from it is a comparison: (LA & 1) <> 0.

and or not (and && || !) take bool and give bool; and / or evaluate the right side only when it decides the value. An int32 operand is SM0250.

Comparisons

= <> < > <= >= give bool; <=> gives C++'s ordering, compared with 0: (LA <=> LB) < 0. Integers, floats, chars and strings compare.

  • Two numbers compared meet at one type, each converting to it: LA < LU (int32 with uint32) is SM0200, since C++ compares that pair unsigned.
  • x in S tests membership of a set variable or of a set literal written in place: 3 in [1, 2, 3], LN in [2..6].

The conditional

C ? A : B is A or B and evaluates only that branch; it is right associative (C1 ? A : C2 ? B : C). C is a bool: LN ? 1 : 2 is SM0263.

Assignment

Assignment is an expression: := and the compound forms += -= *= /= %= &= |= <<= >>=, right associative, so LA := LB := 4 sets both.

  • A compound assignment's value converts to the target's type: LN8 += 1 is fine, LN += LA (int32 into int8) is SM0200; /= on an integer target is SM0250 (write div).
  • A constant cannot be assigned (SM0255).

Conversions and casts

No hidden conversions: a value converts implicitly only when every value it can hold is exactly representable in the target (integer widening, float32 to float64, a constant that fits). An int32 variable into an int8 is SM0200, an int32 argument for an int8 parameter SM0404. An opaque C/C++ value is accepted; clang judges it.

  • A type cast T(x) (a builtin type or an alias of one) converts as C++ does: a float truncates toward zero (int32(3.75) is 3, int32(-3.75) is -3) and a narrower integer wraps (int16(70000) is 4464).
  • A cast takes one value: int32(1, 2) is PR0041. A type named as a value (LN := TSmall;) is SM0269.
  • A C-style cast (T)x is a cast when the parenthesized text is plainly a type ((int)LF, (int32_t)-1); (a name) is a cast only when an operand follows, so (LN) - 1 is a subtraction.

Prefix and postfix operators

Prefix not - + address of and the C++ forms ! ~ * & ++ --; postfix ^ ++ --. Prefix operators chain: not not LB, **LPP, LPP^^.

address of (and &) takes a variable, a field, an element or a deref; a value that is not stored has none: address of Five() is SM0254.

Calls and members

A call ( ), an index [ ] and a member . / -> chain on any value: a call result (Make().Y), a nested record (LQ.P.X), a record through a pointer (LQP->P.X), C++ members (LV.size()). After . and -> any keyword is a name, so a C++ member named type or end is reached as written.

Only a routine or a routine value is called: LM(1) with an int32 LM is SM0400. A member a record lacks is SM0009 ('TQ' has no member 'Z').

Primaries

true, false, nil, integer, float, string and char literals, ( ), self (the object a method runs on) and parent.<member>.

Set literals

[e, a..b, ...] holds elements and ranges of one ordinal family (integers of any width are one); [] is the empty set. A float element is SM0266, an int32 and a char in one literal SM0266, a range whose low bound exceeds its high bound SM0267.

Record literals

T(F: v, G: w) builds a record value: the fields it names, in declaration order, each once; a field it leaves out starts at zero. Literals nest and pass as arguments. A field the record lacks is SM0414, a field given twice SM0415, fields out of order SM0416 ('X' comes before 'Y' in 'TP').

C++ in expressions

C++ is written in an expression as in C++: qualified names with template arguments (std::max<int>(3, 9)), new / delete, nullptr, this, sizeof, braced init ({1, 2}) and lambdas.

  • Template arguments after a qualified name are taken unless an operand follows the > (std::numbers::pi_v<float64> > 3.0); the one token <> is an empty list only before ( { :: > , ) ; ] (std::less<>()); an unqualified name<...> is a comparison.
  • [ ... ] is a lambda when ( or { follows its ], else a set literal.
  • A group that never closes is PR0063 at its opener ('(' is never closed). A C++ mistake is clang's, reported at its .pcpp line: see Diagnostics.