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.
282 lines
6.3 KiB
ObjectPascal
282 lines
6.3 KiB
ObjectPascal
{
|
|
Blaise - An Object Pascal Compiler
|
|
Copyright (c) 2026 Graeme Geldenhuys
|
|
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
|
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
|
See LICENSE file in the project root for full license terms.
|
|
}
|
|
|
|
program Phase3Milestone;
|
|
|
|
{ Phase 3 milestone: exercises TList<Integer> and TDictionary<string,Integer>.
|
|
Must produce correct output and zero valgrind leaks. }
|
|
|
|
type
|
|
TIntList = class
|
|
FData: ^Integer;
|
|
FCount: Integer;
|
|
FCapacity: Integer;
|
|
procedure Grow;
|
|
procedure Add(Value: Integer);
|
|
function Get(AIndex: Integer): Integer;
|
|
procedure Delete(AIndex: Integer);
|
|
procedure Destroy;
|
|
property Count: Integer read FCount;
|
|
end;
|
|
|
|
TStrIntDict = class
|
|
FKeys: ^string;
|
|
FValues: ^Integer;
|
|
FCount: Integer;
|
|
FCapacity: Integer;
|
|
procedure Grow;
|
|
function FindKey(Key: string): Integer;
|
|
procedure Add(Key: string; Value: Integer);
|
|
function TryGetValue(Key: string; var Value: Integer): Boolean;
|
|
function ContainsKey(Key: string): Boolean;
|
|
procedure Remove(Key: string);
|
|
procedure Destroy;
|
|
property Count: Integer read FCount;
|
|
end;
|
|
|
|
procedure TIntList.Grow;
|
|
var
|
|
NewCap: Integer;
|
|
begin
|
|
if Self.FCapacity = 0 then
|
|
NewCap := 4
|
|
else
|
|
NewCap := Self.FCapacity * 2;
|
|
Self.FData := ReallocMem(Self.FData, NewCap * SizeOf(Integer));
|
|
Self.FCapacity := NewCap
|
|
end;
|
|
|
|
procedure TIntList.Add(Value: Integer);
|
|
var
|
|
Dest: ^Integer;
|
|
begin
|
|
if Self.FCount = Self.FCapacity then
|
|
Self.Grow();
|
|
Dest := Self.FData + Self.FCount * SizeOf(Integer);
|
|
Dest^ := Value;
|
|
Self.FCount := Self.FCount + 1
|
|
end;
|
|
|
|
function TIntList.Get(AIndex: Integer): Integer;
|
|
var
|
|
Src: ^Integer;
|
|
begin
|
|
Src := Self.FData + AIndex * SizeOf(Integer);
|
|
Result := Src^
|
|
end;
|
|
|
|
procedure TIntList.Delete(AIndex: Integer);
|
|
var
|
|
Src: ^Integer;
|
|
Dst: ^Integer;
|
|
I: Integer;
|
|
begin
|
|
I := AIndex;
|
|
while I < Self.FCount - 1 do
|
|
begin
|
|
Dst := Self.FData + I * SizeOf(Integer);
|
|
Src := Self.FData + (I + 1) * SizeOf(Integer);
|
|
Dst^ := Src^;
|
|
I := I + 1
|
|
end;
|
|
Self.FCount := Self.FCount - 1
|
|
end;
|
|
|
|
procedure TIntList.Destroy;
|
|
begin
|
|
FreeMem(Self.FData);
|
|
Self.FData := nil;
|
|
Self.FCount := 0;
|
|
Self.FCapacity := 0
|
|
end;
|
|
|
|
procedure TStrIntDict.Grow;
|
|
var
|
|
NewCap: Integer;
|
|
OldCap: Integer;
|
|
begin
|
|
OldCap := Self.FCapacity;
|
|
if OldCap = 0 then
|
|
NewCap := 8
|
|
else
|
|
NewCap := OldCap * 2;
|
|
Self.FKeys := ReallocMem(Self.FKeys, NewCap * SizeOf(string));
|
|
{ Zero-init new string slots so ARC Release of "old" value is safe }
|
|
ZeroMem(Self.FKeys + OldCap * SizeOf(string),
|
|
(NewCap - OldCap) * SizeOf(string));
|
|
Self.FValues := ReallocMem(Self.FValues, NewCap * SizeOf(Integer));
|
|
Self.FCapacity := NewCap
|
|
end;
|
|
|
|
function TStrIntDict.FindKey(Key: string): Integer;
|
|
var
|
|
I: Integer;
|
|
Ptr: ^string;
|
|
begin
|
|
Result := -1;
|
|
I := 0;
|
|
while I < Self.FCount do
|
|
begin
|
|
Ptr := Self.FKeys + I * SizeOf(string);
|
|
if Ptr^ = Key then
|
|
begin
|
|
Result := I;
|
|
break
|
|
end;
|
|
I := I + 1
|
|
end
|
|
end;
|
|
|
|
procedure TStrIntDict.Add(Key: string; Value: Integer);
|
|
var
|
|
Idx: Integer;
|
|
KPtr: ^string;
|
|
VPtr: ^Integer;
|
|
begin
|
|
Idx := Self.FindKey(Key);
|
|
if Idx >= 0 then
|
|
begin
|
|
VPtr := Self.FValues + Idx * SizeOf(Integer);
|
|
VPtr^ := Value
|
|
end
|
|
else
|
|
begin
|
|
if Self.FCount = Self.FCapacity then
|
|
Self.Grow();
|
|
KPtr := Self.FKeys + Self.FCount * SizeOf(string);
|
|
VPtr := Self.FValues + Self.FCount * SizeOf(Integer);
|
|
KPtr^ := Key;
|
|
VPtr^ := Value;
|
|
Self.FCount := Self.FCount + 1
|
|
end
|
|
end;
|
|
|
|
function TStrIntDict.TryGetValue(Key: string; var Value: Integer): Boolean;
|
|
var
|
|
Idx: Integer;
|
|
VPtr: ^Integer;
|
|
begin
|
|
Idx := Self.FindKey(Key);
|
|
if Idx >= 0 then
|
|
begin
|
|
VPtr := Self.FValues + Idx * SizeOf(Integer);
|
|
Value := VPtr^;
|
|
Result := True
|
|
end
|
|
else
|
|
Result := False
|
|
end;
|
|
|
|
function TStrIntDict.ContainsKey(Key: string): Boolean;
|
|
begin
|
|
Result := Self.FindKey(Key) >= 0
|
|
end;
|
|
|
|
procedure TStrIntDict.Remove(Key: string);
|
|
var
|
|
Idx: Integer;
|
|
I: Integer;
|
|
KDst: ^string;
|
|
KSrc: ^string;
|
|
VDst: ^Integer;
|
|
VSrc: ^Integer;
|
|
begin
|
|
Idx := Self.FindKey(Key);
|
|
if Idx >= 0 then
|
|
begin
|
|
I := Idx;
|
|
while I < Self.FCount - 1 do
|
|
begin
|
|
KDst := Self.FKeys + I * SizeOf(string);
|
|
KSrc := Self.FKeys + (I + 1) * SizeOf(string);
|
|
VDst := Self.FValues + I * SizeOf(Integer);
|
|
VSrc := Self.FValues + (I + 1) * SizeOf(Integer);
|
|
KDst^ := KSrc^;
|
|
VDst^ := VSrc^;
|
|
I := I + 1
|
|
end;
|
|
{ Release the duplicate last string slot left behind by the shift }
|
|
KDst := Self.FKeys + (Self.FCount - 1) * SizeOf(string);
|
|
KDst^ := nil;
|
|
Self.FCount := Self.FCount - 1
|
|
end
|
|
end;
|
|
|
|
procedure TStrIntDict.Destroy;
|
|
var
|
|
I: Integer;
|
|
Ptr: ^string;
|
|
begin
|
|
{ Release all stored strings before freeing the backing store }
|
|
I := 0;
|
|
while I < Self.FCount do
|
|
begin
|
|
Ptr := Self.FKeys + I * SizeOf(string);
|
|
Ptr^ := nil;
|
|
I := I + 1
|
|
end;
|
|
FreeMem(Self.FKeys);
|
|
FreeMem(Self.FValues);
|
|
Self.FKeys := nil;
|
|
Self.FValues := nil;
|
|
Self.FCount := 0;
|
|
Self.FCapacity := 0
|
|
end;
|
|
|
|
var
|
|
List: TIntList;
|
|
Dict: TStrIntDict;
|
|
V: Integer;
|
|
Found: Boolean;
|
|
|
|
begin
|
|
{ --- TList<Integer> --- }
|
|
List := TIntList.Create;
|
|
List.Add(10);
|
|
List.Add(20);
|
|
List.Add(30);
|
|
List.Add(40);
|
|
List.Add(50);
|
|
|
|
WriteLn('list.count=', List.Count); { 5 }
|
|
WriteLn('list[0]=', List.Get(0)); { 10 }
|
|
WriteLn('list[4]=', List.Get(4)); { 50 }
|
|
|
|
List.Delete(1);
|
|
WriteLn('count_after_delete=', List.Count); { 4 }
|
|
WriteLn('list[1]_after_delete=', List.Get(1)); { 30 }
|
|
|
|
List.Free();
|
|
|
|
{ --- TDictionary<string,Integer> --- }
|
|
Dict := TStrIntDict.Create;
|
|
Dict.Add('alpha', 1);
|
|
Dict.Add('beta', 2);
|
|
Dict.Add('gamma', 3);
|
|
Dict.Add('delta', 4);
|
|
|
|
WriteLn('dict.count=', Dict.Count); { 4 }
|
|
|
|
Found := Dict.TryGetValue('beta', V);
|
|
WriteLn('beta=', V); { 2 }
|
|
|
|
Found := Dict.ContainsKey('gamma');
|
|
WriteLn('has_gamma=', Found); { 1 }
|
|
|
|
Dict.Add('beta', 99);
|
|
Found := Dict.TryGetValue('beta', V);
|
|
WriteLn('beta_after_update=', V); { 99 }
|
|
|
|
Dict.Remove('alpha');
|
|
WriteLn('count_after_remove=', Dict.Count); { 3 }
|
|
Found := Dict.ContainsKey('alpha');
|
|
WriteLn('has_alpha_after_remove=', Found); { 0 }
|
|
|
|
Dict.Free()
|
|
end.
|