New features: `is not` / `not in`, property compound assignment, for-step loopsThree additions landed in main. Each is gated on its own modeswitch (or is unleashed-mode only); details below per feature.
`is not` and `not in` operatorsAvailable in
{$mode unleashed}, no dedicated modeswitch.
Standard Pascal forces an extra pair of parentheses for negated runtime type checks and set membership tests:
if not (Obj is TFoo) then ...
if not (x in [Apple, Orange]) then ...
Unleashed lets you write it the way you say it out loud:
if Obj is not TFoo then ...
if x not in [Apple, Orange] then ...
Both forms are Delphi-compatible - Delphi has supported them for years.Both forms compile to the same node tree as the parenthesised version, so semantics, error messages, and runtime cost are unchanged.
Outside Unleashed the parser rejects both: `obj is not T` reads as `obj is (not T)` and triggers an operator error, `x not in S` is a syntax error at `not`.
Demoprogram is_not_not_in_demo;
{$mode unleashed}
type
tanimal = class
name: string;
constructor create(const aname: string);
end;
tdog = class(tanimal) end;
tcat = class(tanimal) end;
tcolor = (red, green, blue, yellow, purple);
tpalette = set of tcolor;
constructor tanimal.create(const aname: string);
begin
name := aname;
end;
procedure greet(a: tanimal);
begin
if a is not tdog then
writeln(a.name, ' is not a dog')
else
writeln(a.name, ' is a dog - woof!');
end;
var
pets: array of tanimal;
warm: tpalette;
c : tcolor;
i : longint;
begin
pets := [tdog.create('Rex'), tcat.create('Whiskers'), tdog.create('Buddy')];
for i := 0 to high(pets) do
greet(pets[i]);
for i := 0 to high(pets) do
pets[i].free;
writeln;
warm := [red, yellow];
for c := low(tcolor) to high(tcolor) do
if c not in warm then
writeln(c, ' is cold')
else
writeln(c, ' is warm');
readln;
end.
Compound assignment on propertiesAvailable in
{$mode unleashed}, no dedicated modeswitch.
Compound operators
+=,
-=,
*=,
/=,
div=,
mod=,
and=,
or=,
xor=,
shl=,
shr= work directly on a class or record property that has both read and write accessors.
Stock FPC rejects `prop += x` with `Error: Variable identifier expected`, on the grounds that the read accessor and the write accessor can target different storage. So you have to spell it out by hand:
f.Name := f.Name + 'bar';
f.Count := f.Count * 2;
With Unleashed:
f.Name += 'bar'; // -> f.Name := f.Name + 'bar'
f.Count *= 2; // -> f.Count := f.Count * 2
The expansion is the same node tree the user would build manually: one getter call on the read side, the binary operator, one setter call on the write side. Side effects in the accessors fire exactly as in the manual rewrite, no more and no fewer.
The C-style operators (
+=,
-=,
*=,
/=) still need
{$coperators on} or
-Sc as in any FPC mode. The word-based operators (
and=,
or=,
xor=,
mod=,
div=,
shl=,
shr=) work without it.
Limitations- Property must have both read and write accessors.
- Indexed properties (property X index N: ...) and parametrized properties (property Items[idx: integer]: ...) are not supported. Use the explicit rewrite for those.
Demoprogram prop_compound_demo;
{$mode unleashed}
{$coperators on}
type
tbox = class
private
fn: integer;
fs: string;
function getn: integer;
procedure setn(v: integer);
function gets: string;
procedure sets(const v: string);
public
property n: integer read getn write setn;
property s: string read gets write sets;
end;
function tbox.getn: integer; begin result := fn; end;
procedure tbox.setn(v: integer); begin fn := v; end;
function tbox.gets: string; begin result := fs; end;
procedure tbox.sets(const v: string); begin fs := v; end;
var
b : tbox;
begin
b := tbox.create;
b.n := 10;
b.s := 'foo';
writeln('-- arithmetic --');
b.n += 5; writeln('n += 5 -> ', b.n);
b.n -= 3; writeln('n -= 3 -> ', b.n);
b.n *= 2; writeln('n *= 2 -> ', b.n);
b.n div= 4; writeln('n div= 4 -> ', b.n);
b.n mod= 4; writeln('n mod= 4 -> ', b.n);
writeln('-- bitwise --');
b.n := $FF;
b.n and= $0F; writeln('n and= $0F -> $', hexstr(b.n, 2));
b.n or= $30; writeln('n or= $30 -> $', hexstr(b.n, 2));
b.n xor= $05; writeln('n xor= $05 -> $', hexstr(b.n, 2));
b.n shl= 4; writeln('n shl= 4 -> $', hexstr(b.n, 4));
writeln('-- string --');
b.s += 'bar'; writeln('s += ''bar'' -> ', b.s);
b.free;
readln;
end.
For-step loopsModeswitch
forstep, on by default in
{$mode unleashed}.
`step N` clause in for-loops to advance the counter by an arbitrary positive amount on each iteration. Works with both `to` and `downto`, and with inline `var`.
Forward and backwardfor i := 1 to 10 step 2 do
write(i, ' '); // 1 3 5 7 9
for i := 20 downto 1 step 3 do
write(i, ' '); // 20 17 14 11 8 5 2
for var k := 5 to 50 step 5 do
write(k, ' '); // 5 10 15 ... 50
The step expression must be ordinal and positive. Use `downto` for descending loops; the step itself is always positive. A constant zero or negative step is a parse error.
Single evaluationThe step expression is evaluated once before the loop starts, so calls with side effects fire only once:
for i := 0 to 12 step ComputeStep() do
Use(i); // ComputeStep called exactly once
Same for the upper bound, the same way classic for-loops already do.
Preserved counterThe loop-end check restores the last in-range value before leaving the loop, so the counter holds the last value the body actually saw, not the overshoot:
for i := 1 to 10 step 4 do ; // body runs at 1, 5, 9
{ i = 9 }
for i := 20 downto 1 step 4 do ; // body runs at 20, 16, 12, 8, 4
{ i = 4 }
Same guarantee on classic for-loops in Unleashed mode.
`step` is a context-sensitive keywordNot a reserved token. The parser only checks for it in one position - right after the to / downto expression and before do. Anywhere else `step` stays an ordinary identifier:
var step: integer = 5; // OK - variable named step
function step: integer; // OK - function named step
begin Result := 7; end;
type TFoo = record step: integer end; // OK - field named step
for i := 1 to step do Use(i); // OK - upper bound is the variable step
for i := 0 to step step 1 do // OK - first `step` is the upper bound,
Use(i); // second `step` is the keyword
Lazarus' SynEdit highlighter mirrors this: only the keyword position lights up, not identifiers.
Demoprogram forstep_demo;
{$mode unleashed}
function compute_step : integer;
begin
writeln(' [compute_step called]');
result := 5;
end;
var
i, step: integer;
begin
writeln('-- forward, step 2 --');
for i := 1 to 9 step 2 do
write(i, ' ');
writeln;
writeln('-- downto, step 3 --');
for i := 20 downto 1 step 3 do
write(i, ' ');
writeln;
writeln('-- inline var, step 5 --');
for var k := 5 to 50 step 5 do
write(k, ' ');
writeln;
writeln('-- step expression evaluated once --');
for i := 0 to 12 step compute_step do
write(i, ' ');
writeln;
writeln('-- preserved counter (last value the body saw) --');
for i := 1 to 10 step 4 do ;
writeln('after for 1..10 step 4: i = ', i); // 9
for i := 20 downto 1 step 4 do ;
writeln('after for 20 downto 1 step 4: i = ', i); // 4
writeln('-- step is a context-sensitive keyword --');
step := 3;
for i := 0 to step step 1 do // upper = var step (=3), keyword = 1
write(i, ' ');
writeln;
readln;
end.