fix(parser): enforce mandatory parentheses on statement-position calls (#148)

The "mandatory parentheses on zero-argument calls" rule (language-
rationale.adoc) was enforced in expression position but NOT in statement
position: a bare `Foo;`, `Obj.Method;`, or `Obj.Free;` used as a statement
compiled silently, building a paren-less TProcCall / TMethodCallStmt. Issue
#148 reported `tester.print;` (a unit's global object method call) being
accepted without its mandatory ().

The statement parser now raises the same "requires () for a call" diagnostic
the inherited-call and expression-position paths already use, at the two
fall-through sites:

  * bare unqualified call `Foo` with no '(' (the final ProcCall else-branch);
  * bare `Obj.Method` with no '(' and no further '.' chain.

Field reads, field assignments, indexed writes, and '.'-chains are unaffected
(only a terminating bare reference is rejected). Expression-position calls
were already enforced.

Enforcing the rule required the compiler, RTL, and stdlib to comply first
(self-hosting): swept bare calls in uSemantic (Flush/RepairGenericInstances),
runtime.arc/runtime.exc (_libc_abort), blaise.codegen.native.backend
(FAsm.AppendLine), and a json.writer doc example. The test suite embedded
many bare calls in inline program strings — all updated to carry (); adding
() never changes behaviour since these were always calls.

cp.test.parser.pas gains two parse-error tests (proc and method bare calls);
the old TestProcCall_NoParens, which asserted the bug, is inverted. grammar.ebnf
SubscriptMethodCall made parens mandatory and an example corrected; rationale
notes statement-position enforcement.
This commit is contained in:
Graeme Geldenhuys 2026-06-28 10:15:39 +01:00
parent a205c22136
commit 50b8d75e6d
40 changed files with 169 additions and 125 deletions

View file

@ -160,7 +160,7 @@ end;
procedure TNativeBackend.EmitBlank;
begin
FAsm.AppendLine;
FAsm.AppendLine();
end;
{ The record-return ABI classifier and its leaf predicates now live as shared

View file

@ -119,7 +119,7 @@ begin
_libc_write(STDERR_FD, Msg, Int64(Len));
NL := 10;
_libc_write(STDERR_FD, @NL, 1);
_libc_abort;
_libc_abort();
end;
procedure _AbstractMethodError;

View file

@ -112,7 +112,7 @@ var
ExcSlot: PPointer;
begin
if g_exc_top = nil then
_libc_abort;
_libc_abort();
g_current_exception := Obj;
ExcSlot := g_exc_top + OFS_EXCEPTION;
ExcSlot^ := Obj;
@ -245,7 +245,7 @@ begin
_libc_write(2, Msg, Int64(Len));
NL := 10;
_libc_write(2, @NL, 1);
_libc_abort;
_libc_abort();
end;
procedure _Raise_InvalidCast;

View file

@ -3421,6 +3421,20 @@ begin
if not Check(tkRParen) then
ParseMethodCallArgList(MCall);
Expect(tkRParen);
end
else if not Check(tkDot) then
begin
{ Bare 'Obj.Method' with no '(' and no further '.' chain a
parameterless method call written without its mandatory parentheses.
(A following '.' is a post-call/field chain handled just below; a
terminating bare reference is the error case.) Every call carries ()
even with no arguments see language-rationale.adoc, "Mandatory
parentheses on zero-argument calls". }
MCall.Free();
MCall := nil;
raise EParseError.Create(Format(
'Bare reference to ''%s.%s'' requires () for a call at line %d col %d in %s',
[Name, SecondIdent, Line, Col, FLexer.Filename]));
end;
{ Post-method chain: Name.Method(args).Field := value or .Method2(args) }
if Check(tkDot) then
@ -3595,11 +3609,13 @@ begin
FCallNode.Free();
Exit(Call);
end;
Call := TProcCall.Create();
Call.Line := Line;
Call.Col := Col;
Call.Name := Name;
Result := Call;
{ Bare identifier in statement position with no '(' a parameterless
procedure/function call written without its mandatory parentheses.
Every call carries () even with no arguments (see language-rationale.adoc,
"Mandatory parentheses on zero-argument calls"). }
raise EParseError.Create(Format(
'Bare reference to ''%s'' requires () for a call at line %d col %d in %s',
[Name, Line, Col, FLexer.Filename]));
end;
end;

View file

@ -1168,7 +1168,7 @@ var
I: Integer;
begin
FProg := AProg;
FlushPendingGenericInstances;
FlushPendingGenericInstances();
FCurrentUnitName := AProg.Name;
BuildUsesChain(AProg.UsedUnits);
FTable.UsesChainProvider := Self;
@ -1202,7 +1202,7 @@ var
begin
FCurrentUnitName := AUnit.Name;
FCurrentUnit := AUnit;
FlushPendingGenericInstances;
FlushPendingGenericInstances();
FTable.PushScope();
try
{ The unit's own name and the names of directly used units are
@ -1223,7 +1223,7 @@ begin
*before* any FindTypeOrInstantiate call can clone an instance, or the
instance is born with nil bodies and codegen emits no function for it. }
LinkGenericClassMethodImpls(AUnit.ImplBlock);
RepairEarlyGenericInstances;
RepairEarlyGenericInstances();
{ Register interface-section global variables — visible to impl bodies. }
for I := 0 to AUnit.IntfBlock.Decls.Count - 1 do
@ -1534,7 +1534,7 @@ var
begin
FCurrentUnitName := AUnit.Name;
FCurrentUnit := AUnit;
FlushPendingGenericInstances;
FlushPendingGenericInstances();
BuildUsesChain(AUnit.UsedUnits);
FTable.UsesChainProvider := Self;
{ Auto-tag every global Define within this unit's analysis with the
@ -1557,7 +1557,7 @@ begin
the cloned instance method is born without a body and codegen emits
no function leaving call sites referencing an undefined symbol. }
LinkGenericClassMethodImpls(AUnit.ImplBlock);
RepairEarlyGenericInstances;
RepairEarlyGenericInstances();
{ Register interface-section global variables. Marked IsGlobal so
codegen emits them as data-segment slots rather than stack allocs;
@ -3971,7 +3971,7 @@ begin
declarations, transferring the body so AnalyseMethodBodies can process it. }
LinkClassMethodImpls(ABlock);
LinkGenericClassMethodImpls(ABlock);
RepairEarlyGenericInstances;
RepairEarlyGenericInstances();
{ Register standalone proc/func signatures before class method bodies so that
methods can call free functions declared in the same block. }
AnalyseStandaloneDecls(ABlock);

View file

@ -687,7 +687,7 @@ begin
f := TFoo.Create()
end;
begin
DoIt
DoIt()
end.
''');
FnPos := Pos('function $DoIt', IR);
@ -725,7 +725,7 @@ begin
f := TFoo.Create()
end;
begin
DoIt
DoIt()
end.
''');
FnPos := Pos('function $DoIt', IR);
@ -759,7 +759,7 @@ begin
f := TFoo.Create()
end;
begin
DoIt
DoIt()
end.
''');
FnPos := Pos('function $DoIt', IR);
@ -800,7 +800,7 @@ begin
F := TFoo(P)
end;
begin
DoIt
DoIt()
end.
''');
FnPos := Pos('function $DoIt', IR);
@ -832,7 +832,7 @@ const
r := Make()
end;
begin
Run
Run()
end.
''';
@ -848,7 +848,7 @@ const
b := a
end;
begin
Run
Run()
end.
''';
@ -868,7 +868,7 @@ const
r := Make()
end;
begin
Run
Run()
end.
''';
@ -883,7 +883,7 @@ const
b := a
end;
begin
Run
Run()
end.
''';

View file

@ -964,7 +964,7 @@ begin
'begin' + LineEnding +
' for I := 1 to 5 do' + LineEnding +
' Write(I);' + LineEnding +
' WriteLn' + LineEnding +
' WriteLn()' + LineEnding +
'end.',
Out_, EC) then
begin
@ -1037,7 +1037,7 @@ begin
' Write(N);' + LineEnding +
' N := N + 1' + LineEnding +
' end;' + LineEnding +
' WriteLn' + LineEnding +
' WriteLn()' + LineEnding +
'end.',
Out_, EC) then
begin
@ -1163,7 +1163,7 @@ begin
'begin' + LineEnding +
' G := TGreeter.Create;' + LineEnding +
' WriteLn(G.Greet());' + LineEnding +
' G.Free' + LineEnding +
' G.Free()' + LineEnding +
'end.',
Out_, EC) then
begin

View file

@ -102,7 +102,7 @@ begin
Proc.WaitOnExit();
Code := Proc.ExitCode
finally
Proc.Free
Proc.Free()
end;
if Code <> 0 then

View file

@ -480,7 +480,7 @@ begin
x := Threshold
end;
begin
DoWork
DoWork()
end.
'''
);

View file

@ -517,7 +517,7 @@ const
Freed := 0;
L := TObjectList.Create(True);
L.Add(TThing.Create());
Borrow;
Borrow();
WriteLn(Freed);
L.Free();
WriteLn(Freed)

View file

@ -389,8 +389,8 @@ const
OL := TObjectList.Create(False);
OL.Add(SL);
WriteLn(TStringList(OL[0])[1]); // chained: cast(OL default) then default
SL.Free;
OL.Free
SL.Free();
OL.Free()
end.
''';
var Output: string; RCode: Integer;

View file

@ -192,11 +192,11 @@ const
end;
begin
Counter := 0;
Inner;
Inner();
WriteLn(Counter);
end;
begin
Outer;
Outer();
end.
''';
@ -311,7 +311,7 @@ const
end;
Write(r); Write(' ');
end;
WriteLn
WriteLn()
end.
''';
@ -326,7 +326,7 @@ const
5, 7..9: Write('b');
else Write('.');
end;
WriteLn
WriteLn()
end.
''';

View file

@ -85,7 +85,7 @@ const
program Prg;
var a: array of string; i: Integer;
begin SetLength(a, 3); a[0] := 'x'; a[1] := 'y'; a[2] := 'z';
for i := 0 to 2 do Write(a[i]); WriteLn end.
for i := 0 to 2 do Write(a[i]); WriteLn() end.
''';
SrcRefSem = '''
@ -125,7 +125,7 @@ const
SetLength(m, 2); SetLength(m[0], 2); SetLength(m[1], 2);
m[0][0] := 1; m[0][1] := 2; m[1][0] := 3; m[1][1] := 4;
for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]);
WriteLn
WriteLn()
end.
''';
@ -137,7 +137,7 @@ const
m[0][0] := 'a'; m[0][1] := 'b'; m[1][0] := 'c'; m[1][1] := 'd';
m[0][1] := 'B';
for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]);
WriteLn
WriteLn()
end.
''';
@ -154,7 +154,7 @@ const
c[i][j][k] := i*4 + j*2 + k;
for i := 0 to 1 do for j := 0 to 1 do for k := 0 to 1 do
Write(c[i][j][k]);
WriteLn
WriteLn()
end.
''';
@ -212,7 +212,7 @@ const
SetLength(G, Length(H));
for I := 0 to High(G) do G[I] := 9;
for I := 0 to High(G) do Write(G[I]);
WriteLn
WriteLn()
end.
''';
@ -226,7 +226,7 @@ const
SetLength(A, Length(B));
for I := 0 to High(A) do A[I] := 8;
for I := 0 to High(A) do Write(A[I]);
WriteLn
WriteLn()
end.
''';
@ -241,7 +241,7 @@ const
SetLength(A, Pick());
for I := 0 to High(A) do A[I] := 6;
for I := 0 to High(A) do Write(A[I]);
WriteLn
WriteLn()
end.
''';

View file

@ -372,9 +372,9 @@ const
begin WriteLn(g.Greet(), ' / ', g.Who()) end;
var per: TPerson; lou: TLoud; vl: TVeryLoud;
begin
per := TPerson.Create; Use(per); per.Free;
lou := TLoud.Create; Use(lou); lou.Free;
vl := TVeryLoud.Create; Use(vl); vl.Free
per := TPerson.Create; Use(per); per.Free();
lou := TLoud.Create; Use(lou); lou.Free();
vl := TVeryLoud.Create; Use(vl); vl.Free()
end.
''';
begin
@ -406,7 +406,7 @@ const
i := d; // assignment to interface
i.Hello();
Use(d); // parameter passing
d.Free
d.Free()
end.
''';
begin

View file

@ -123,7 +123,7 @@ begin
' for e in s do n := n + 1;' + LE +
' WriteLn(n);' + LE + { 4 }
' for e in s do Write(Ord(e), '' '');' + LE +
' WriteLn' + LE +
' WriteLn()' + LE +
'end.';
AssertRunsOnAll(Src, '4' + LE + '5 40 70 79 ' + LE, 0);
end;

View file

@ -126,7 +126,7 @@ const
end
end;
begin
DoIt;
DoIt();
WriteLn('done')
end.
''';

View file

@ -347,7 +347,7 @@ const
Obj := TFoo.Create();
Obj.FVal := 55;
M := @Obj.Print;
M;
M();
Obj.Free()
end.
''';
@ -991,11 +991,11 @@ const
begin
x := 5;
WriteLn(IntToStr(x));
Inner;
Inner();
WriteLn(IntToStr(x))
end;
begin
Outer
Outer()
end.
''';
var Output: string; RCode: Integer;
@ -1022,7 +1022,7 @@ const
R.B := Sum * 2
end;
begin
Inner
Inner()
end;
var Rec: TRec;
begin
@ -1050,11 +1050,11 @@ const
end;
begin
R.A := 1; R.B := 2;
Inner;
Inner();
WriteLn(IntToStr(R.A), ' ', IntToStr(R.B))
end;
begin
Outer
Outer()
end.
''';
begin
@ -1073,7 +1073,7 @@ const
A[0] := 10; A[1] := 20; A[2] := A[0] + A[1]
end;
begin
Inner
Inner()
end;
var Ar: TArr;
begin

View file

@ -1226,7 +1226,7 @@ const
WriteLn(D)
end;
begin
ShowDouble
ShowDouble()
end.
''';
@ -1487,7 +1487,7 @@ const
WriteLn(B.Data[4]);
WriteLn(B.Data[I + 2]);
WriteLn(B.At(1));
B.Free;
B.Free();
end.
''';
@ -2843,13 +2843,13 @@ const
end;
procedure Tmi.use;
begin
im.print;
im.print();
end;
var
im: Tmi;
begin
im := Tmi.Create(Toutput.Create());
im.use;
im.use();
end.
''';
@ -4966,7 +4966,7 @@ begin
begin
B := TBox.Create(99);
WriteLn(B.Val);
B.Free
B.Free()
end.
''',
'99' + LE, 0);
@ -4992,7 +4992,7 @@ begin
A := TArr.Create;
WriteLn(A.Items[0]);
WriteLn(A.Items[1]);
A.Free
A.Free()
end.
''',
'10' + LE + '30' + LE, 0);
@ -5018,7 +5018,7 @@ begin
B := TBox.Create;
B.Val := 42;
WriteLn(B.Val);
B.Free
B.Free()
end.
''',
'42' + LE, 0);
@ -5046,7 +5046,7 @@ begin
A.Items[1] := 30;
WriteLn(A.Items[0]);
WriteLn(A.Items[1]);
A.Free
A.Free()
end.
''',
'10' + LE + '30' + LE, 0);
@ -5062,7 +5062,7 @@ begin
begin
F := TFoo.Create;
WriteLn(F.ClassName);
F.Free
F.Free()
end.
''',
'TFoo' + LE, 0);
@ -5084,7 +5084,7 @@ begin
begin
Obj := TCalc.Create;
WriteLn(Obj.Sum6(1, 2, 3, 4, 5, 6));
Obj.Free
Obj.Free()
end.
''',
'21' + LE, 0);
@ -5109,8 +5109,8 @@ begin
O := TOuter.Create;
O.FInner := I;
WriteLn(O.GetVal());
O.Free;
I.Free
O.Free();
I.Free()
end.
''',
'42' + LE, 0);
@ -5137,8 +5137,8 @@ begin
begin
O := TMyObj.Create;
O.FVal := 99;
O.Show;
O.Free
O.Show();
O.Free()
end.
''',
'99' + LE, 0);
@ -5176,7 +5176,7 @@ begin
begin
B := TChild.Create;
WriteLn(B.GetVal());
B.Free
B.Free()
end.
''',
'42' + LE, 0);
@ -5322,7 +5322,7 @@ begin
procedure TCalc.Run;
begin
FVal := 5;
Step;
Step();
WriteLn(FVal);
WriteLn(Double())
end;
@ -5360,8 +5360,8 @@ begin
var P: TParser;
begin
P := TParser.Create();
P.Setup;
P.Advance;
P.Setup();
P.Advance();
WriteLn(P.FA.Kind, ':', P.FA.Value, ':', P.FA.Line, ':', P.FA.Col);
WriteLn(P.FB.Kind, ':', P.FB.Value, ':', P.FB.Line, ':', P.FB.Col);
P.Free()

View file

@ -671,7 +671,7 @@ const
K := TMaker.Create;
R := K.Make(0.25);
WriteLn(R.X);
K.Free
K.Free()
end.
''';
begin

View file

@ -967,7 +967,7 @@ const
var c: TC;
begin
c := TC.Create();
c.Fill;
c.Fill();
writeln(c.A[0]);
writeln(c.A[1]);
writeln(c.A[2]);

View file

@ -198,7 +198,7 @@ const
G[2] := L[2]
end;
begin
Fill;
Fill();
WriteLn(G[0]);
WriteLn(G[2])
end.

View file

@ -254,7 +254,7 @@ const
end
end;
begin
Run
Run()
end.
''';

View file

@ -171,7 +171,7 @@ const
Exit(5)
end;
begin
DoIt
DoIt()
end.
''';

View file

@ -793,7 +793,7 @@ const
program Prg;
uses UPair;
begin
InitPair;
InitPair();
end.
''';

View file

@ -477,7 +477,7 @@ begin
var C: TChild;
begin
C := TChild.Create();
C.NoSuchMethod
C.NoSuchMethod()
end.
''');
end;

View file

@ -525,7 +525,7 @@ const
end;
procedure Tmi.use;
begin
im.print;
im.print();
end;
var
im: Tmi;

View file

@ -376,7 +376,7 @@ begin
var F: TFoo;
begin
F := TFoo.Create();
F.NoSuchMethod
F.NoSuchMethod()
end.
''');
end;

View file

@ -452,7 +452,7 @@ const
procedure ext_abort; external name 'abort';
procedure DoA;
begin
ext_abort
ext_abort()
end;
end.
''';
@ -465,7 +465,7 @@ const
procedure ext_abort; external name 'abort';
procedure DoB;
begin
ext_abort
ext_abort()
end;
end.
''';
@ -474,7 +474,7 @@ const
program TestP;
uses AltA, AltB;
begin
DoA; DoB
DoA(); DoB()
end.
''';
var

View file

@ -155,7 +155,7 @@ begin
'''
program P;
begin
WriteLn
WriteLn()
end.
''');
AssertTrue('empty WriteLn still emits newline',

View file

@ -175,7 +175,7 @@ const
WriteLn(N)
end;
begin
Greet;
Greet();
Greet(42)
end.
''';

View file

@ -47,7 +47,8 @@ type
procedure TestAssignment_IntLit;
procedure TestAssignment_StringLit;
procedure TestProcCall_NoArgs;
procedure TestProcCall_NoParens;
procedure TestProcCall_BareNoParens_RequiresParens;
procedure TestMethodCall_BareNoParens_RequiresParens;
procedure TestProcCall_OneStringArg;
procedure TestProcCall_OneIntArg;
@ -406,18 +407,37 @@ begin
end;
end;
procedure TParserTests.TestProcCall_NoParens;
var
Prog: TProgram;
Call: TProcCall;
procedure TParserTests.TestProcCall_BareNoParens_RequiresParens;
begin
Prog := ParseSource('program P; begin WriteLn end.');
{ Issue #148: a parameterless call in statement position requires its
mandatory () (see language-rationale.adoc, "Mandatory parentheses on
zero-argument calls"). `WriteLn` without () is a parse error. }
try
Call := TProcCall(Prog.Block.Stmts.Items[0]);
AssertEquals('Name', 'WriteLn', Call.Name);
AssertEquals('0 args', 0, Call.Args.Count);
finally
Prog.Free();
ParseSource('program P; begin WriteLn end.').Free();
Fail('Expected EParseError for bare parameterless call');
except
on E: EParseError do
AssertTrue('mentions mandatory parens',
Pos('requires () for a call', E.Message) >= 0);
end;
end;
procedure TParserTests.TestMethodCall_BareNoParens_RequiresParens;
begin
{ Issue #148: a parameterless METHOD call in statement position likewise
requires (). `tester.print` (no parens) is a parse error. }
try
ParseSource(
'program P;' +
' type TC = class procedure P; end;' +
' procedure TC.P; begin end;' +
' var c: TC;' +
' begin c := TC.Create(); c.P end.').Free();
Fail('Expected EParseError for bare parameterless method call');
except
on E: EParseError do
AssertTrue('mentions mandatory parens',
Pos('requires () for a call', E.Message) >= 0);
end;
end;

View file

@ -588,10 +588,10 @@ const
begin
end;
begin
Inner;
Inner();
end;
begin
Outer;
Outer();
end.
''';
var IR: string;
@ -619,10 +619,10 @@ const
end;
begin
x := 0;
Inner;
Inner();
end;
begin
Outer;
Outer();
end.
''';
var IR: string;
@ -650,7 +650,7 @@ const
WriteLn(1);
end;
begin
Inner;
Inner();
end;
procedure OuterB;
procedure Inner;
@ -658,11 +658,11 @@ const
WriteLn(2);
end;
begin
Inner;
Inner();
end;
begin
OuterA;
OuterB;
OuterA();
OuterB();
end.
''';
var

View file

@ -494,7 +494,7 @@ begin
begin
end;
begin
UseFn;
UseFn();
end.
'''
);

View file

@ -413,7 +413,7 @@ const
program P;
type TM = procedure of object;
var G: TM;
begin G end.
begin G() end.
''';
var IR: string;
begin
@ -448,7 +448,7 @@ const
M.Code := MethodAddress(F, 'SayHi');
M.Data := F;
G := TGreet(M);
G;
G();
F.Free()
end.
''';
@ -509,7 +509,7 @@ const
M.Code := MethodAddress(C, 'Print');
M.Data := C;
G := TPrintMethod(M);
G;
G();
C.Free()
end.
''';

View file

@ -473,7 +473,7 @@ end;
procedure TSemanticTests.TestProcCall_WriteLn_NoArgs_OK;
begin
Analyse('program P; begin WriteLn end.').Free();
Analyse('program P; begin WriteLn() end.').Free();
end;
procedure TSemanticTests.TestProcCall_WriteLn_StringArg_OK;
@ -520,7 +520,7 @@ end;
procedure TSemanticTests.TestProcCall_UndeclaredProc_RaisesError;
begin
AnalyseExpectError('program P; begin NoSuchProc end.');
AnalyseExpectError('program P; begin NoSuchProc() end.');
end;
{ ------------------------------------------------------------------ }

View file

@ -854,7 +854,7 @@ StaticSubscriptAssign
* assignment (SubscriptFieldAssign) or a method call on the element
* (SubscriptMethodCall). Examples:
* a[0].Name := 'x'; a[i].Inner.N := 5;
* a[0].Bump; a[i].Items.Add(x);
* a[0].Bump(); a[i].Items.Add(x);
* a[i].Arr[j] := v; element write into an array-typed field
* A terminating subscript directly over another subscript (a[i][j] := v, or
* the comma form a[i, j] := v) is a multi-dimensional element write see
@ -866,7 +866,7 @@ SubscriptFieldAssign
SubscriptMethodCall
= IDENT LBRACKET Expr RBRACKET { SubscriptSuffix }
DOT IDENT [ LPAREN [ ArgList ] RPAREN ]
DOT IDENT LPAREN [ ArgList ] RPAREN
;
(* A subscript suffix indexes one or more dimensions. The comma form

View file

@ -3954,6 +3954,14 @@ the lexical level: `Foo()` is always a call, `Foo` is always a read. The
compiler AST becomes simpler — `TMethodCallExpr` for calls with explicit
parentheses, `TFieldAccessExpr` only for field/property reads.
The rule applies in *statement* position as well as expression position: a
bare `Foo;`, `Obj.Method;`, or `Obj.Free;` used as a statement is a parse
error (issue #148). Earlier the statement parser silently accepted the
bare form — expression position was already enforced, but statement-position
parameterless calls slipped through and built a paren-less `TProcCall` /
`TMethodCallStmt`. Both now raise the same "requires () for a call"
diagnostic the `inherited` and expression-position paths use.
=== Alternatives considered
* *Deprecation warning period* — rejected. Blaise is in alpha; there is no

View file

@ -20,10 +20,10 @@
W.WriteBool('stable', True);
2. Array elements - value only, no name (the '...Value' forms):
W.BeginArray;
W.BeginArray();
W.WriteStringValue('a');
W.WriteIntValue(1);
W.EndArray;
W.EndArray();
3. Structure / escape hatch - when an object member's value is itself a
nested object or array, write the key, then open the container:

View file

@ -92,7 +92,7 @@ begin
Result := N.FValue;
Self.FHead := N.FNext;
Self.FCount := Self.FCount - 1;
N.Free
N.Free()
end;
procedure TLinkedList.Walk;
@ -120,7 +120,7 @@ begin
while N <> nil do
begin
Next := N.FNext;
N.Free;
N.Free();
N := Next
end;
Self.FHead := nil;
@ -145,7 +145,7 @@ begin
WriteLn('count=', List.Count); { 4 }
WriteLn('--- walk ---');
List.Walk; { 40/tag0, 30/tag1/marked, 20/tag0, 10/tag0 }
List.Walk(); { 40/tag0, 30/tag1/marked, 20/tag0, 10/tag0 }
{ Pop two values off the front }
V := List.Pop();
@ -155,7 +155,7 @@ begin
WriteLn('count_after_pops=', List.Count); { 2 }
finally
List.Clear;
List.Free
List.Clear();
List.Free()
end
end.

View file

@ -56,7 +56,7 @@ var
Dest: ^Integer;
begin
if Self.FCount = Self.FCapacity then
Self.Grow;
Self.Grow();
Dest := Self.FData + Self.FCount * SizeOf(Integer);
Dest^ := Value;
Self.FCount := Self.FCount + 1
@ -147,7 +147,7 @@ begin
else
begin
if Self.FCount = Self.FCapacity then
Self.Grow;
Self.Grow();
KPtr := Self.FKeys + Self.FCount * SizeOf(string);
VPtr := Self.FValues + Self.FCount * SizeOf(Integer);
KPtr^ := Key;
@ -251,7 +251,7 @@ begin
WriteLn('count_after_delete=', List.Count); { 4 }
WriteLn('list[1]_after_delete=', List.Get(1)); { 30 }
List.Free;
List.Free();
{ --- TDictionary<string,Integer> --- }
Dict := TStrIntDict.Create;
@ -277,5 +277,5 @@ begin
Found := Dict.ContainsKey('alpha');
WriteLn('has_alpha_after_remove=', Found); { 0 }
Dict.Free
Dict.Free()
end.