Skip to content

Commit 48b7a8b

Browse files
committed
For-in-loop with var and ref declaration
1 parent 42e2bf0 commit 48b7a8b

File tree

3 files changed

+314
-22
lines changed

3 files changed

+314
-22
lines changed

compiler/xpr.parser.pas

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ TParser = class(TObject)
7171
function ParseWhile(): XTree_While;
7272
function ParseRepeat(): XTree_Repeat;
7373
function ParseFor(): XTree_For;
74+
function ParseForIn(): XTree_Node;
7475
function ParseTry(): XTree_Try;
7576

7677
function ParseContinue(): XTree_Continue;
@@ -782,6 +783,57 @@ function TParser.ParseFor(): XTree_For;
782783
Dec(FLooping);
783784
end;
784785

786+
function TParser.ParseForIn(): XTree_Node;
787+
var
788+
items: XNodeList;
789+
collection: XTree_Node;
790+
body: XTree_ExprList;
791+
_pos: TDocPos;
792+
OldFPos: Int32;
793+
DeclareVar: Byte;
794+
begin
795+
_pos := DocPos;
796+
OldFPos := FPos;
797+
798+
Consume(tkKW_FOR);
799+
Consume(tkLPARENTHESES, PostInc);
800+
801+
declareVar := 0;
802+
if NextIf(tkKW_VAR) then
803+
DeclareVar := 1
804+
else if NextIf(tkKW_REF) then
805+
DeclareVar := 2;
806+
807+
items.Init([]);
808+
repeat
809+
items.Add(ParseExpression(False,False));
810+
until(not NextIf(tkCOMMA)) or (DeclareVar = 2);
811+
812+
if not NextIf(tkKW_IN) then
813+
begin
814+
FPos := OldFPos;
815+
Exit(ParseFor()); // reroute to regular for-loop.
816+
end;
817+
818+
collection := ParseExpression(False);
819+
Consume(tkRPARENTHESES, PostInc);
820+
821+
Inc(FLooping);
822+
try
823+
if NextIf(tkKW_DO) then
824+
body := XTree_ExprList.Create(ParseStatements([tkKW_END], True), FContext, DocPos)
825+
else
826+
body := XTree_ExprList.Create(self.ParseStatement(), FContext, DocPos);
827+
finally
828+
Dec(FLooping);
829+
end;
830+
831+
if items.Size = 1 then
832+
Result := XTree_ForIn.Create(items.Data[0], collection, declareVar, body, FContext, _pos)
833+
else
834+
Result := XTree_ForIn.Create(XTree_Destructure.Create(items.Raw(), FContext, _pos), collection, declareVar, body, FContext, _pos)
835+
end;
836+
785837

786838
// ----------------------------------------------------------------------------
787839
// Try-Except
@@ -1551,7 +1603,7 @@ function TParser.ParseStatement: XTree_Node;
15511603
tkKW_SWITCH: Result := ParseSwitch();
15521604
tkKW_WHILE: Result := ParseWhile();
15531605
tkKW_REPEAT: Result := ParseRepeat();
1554-
tkKW_FOR: Result := ParseFor();
1606+
tkKW_FOR: Result := ParseForIn();
15551607
tkKW_RETURN: Result := ParseReturn();
15561608
tkKW_TRY: Result := ParseTry();
15571609
tkKW_BREAK: Result := ParseBreak();

compiler/xpr.tree.pas

Lines changed: 239 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -483,16 +483,16 @@ XTree_Raise = class(XTree_Node)
483483
end;
484484

485485
XTree_Try = class(XTree_Node)
486-
TryBody: XTree_ExprList;
487-
Handlers: TExceptionHandlerArray;
488-
ElseBody: XTree_Node; // For the final optional 'except' (catch-all)
489-
490-
constructor Create(ATryBody: XTree_ExprList; AHandlers: TExceptionHandlerArray; AElseBody: XTree_Node; ACTX: TCompilerContext; DocPos: TDocPos); virtual; reintroduce;
491-
function ToString(offset: string = ''): string; override;
492-
function Compile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
493-
function DelayedCompile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
494-
function Copy(): XTree_Node; override;
495-
end;
486+
TryBody: XTree_ExprList;
487+
Handlers: TExceptionHandlerArray;
488+
ElseBody: XTree_Node; // For the final optional 'except' (catch-all)
489+
490+
constructor Create(ATryBody: XTree_ExprList; AHandlers: TExceptionHandlerArray; AElseBody: XTree_Node; ACTX: TCompilerContext; DocPos: TDocPos); virtual; reintroduce;
491+
function ToString(offset: string = ''): string; override;
492+
function Compile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
493+
function DelayedCompile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
494+
function Copy(): XTree_Node; override;
495+
end;
496496

497497
(* for loop *)
498498
XTree_For = class(XTree_Node)
@@ -506,6 +506,22 @@ XTree_For = class(XTree_Node)
506506
function Copy(): XTree_Node; override;
507507
end;
508508

509+
(* for..in loop *)
510+
XTree_ForIn = class(XTree_Node)
511+
ItemVar: XTree_Node;
512+
Collection: XTree_Node;
513+
Body: XTree_ExprList;
514+
DeclareIdent: Byte;
515+
516+
constructor Create(AItemVar: XTree_Node; ACollection: XTree_Node; ADeclareIdent: Byte; ABody: XTree_ExprList; ACTX: TCompilerContext; DocPos: TDocPos); virtual; reintroduce;
517+
function ToString(offset:string=''): string; override;
518+
519+
function Compile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
520+
function DelayedCompile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar; override;
521+
function Copy(): XTree_Node; override;
522+
end;
523+
524+
509525
(* Pascal-style repeat-until loop *)
510526
XTree_Repeat = class(XTree_Node)
511527
Condition: XTree_Node;
@@ -4228,7 +4244,6 @@ ctx.RaiseException('No exception handlers are implemented, declare `type Exc
42284244
if Self.ElseBody = nil then
42294245
begin
42304246
Self.ElseBody := XTree_ExprList.Create([
4231-
XTree_Print.create([XTree_Int.Create('12345', FContext, FDocPos)], FContext, FDocPos),
42324247
XTree_Raise.Create(XTree_VarStub.Create(ExceptionTempVar,ctx, FDocPos), ctx, Fdocpos)
42334248
], ctx, Fdocpos);
42344249
end;
@@ -4396,6 +4411,216 @@ function XTree_For.DelayedCompile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar
43964411

43974412

43984413

4414+
// ============================================================================
4415+
// FOR-ITEM-IN-ARRAY loop
4416+
//
4417+
constructor XTree_ForIn.Create(AItemVar: XTree_Node; ACollection: XTree_Node; ADeclareIdent: Byte; ABody: XTree_ExprList; ACTX: TCompilerContext; DocPos: TDocPos);
4418+
begin
4419+
Self.FContext := ACTX;
4420+
Self.FDocPos := DocPos;
4421+
Self.ItemVar := AItemVar;
4422+
Self.Collection := ACollection;
4423+
Self.DeclareIdent:=ADeclareIdent;
4424+
Self.Body := ABody;
4425+
end;
4426+
4427+
function XTree_ForIn.ToString(offset: string): string;
4428+
begin
4429+
Result := offset + _AQUA_+'ForIn'+_WHITE_+'(' + LineEnding;
4430+
Result += Self.ItemVar.ToString(offset+' ') + ' in ' + Self.Collection.ToString('') + ',' + LineEnding;
4431+
Result += Self.Body.ToString(Offset+' ') + LineEnding;
4432+
Result += offset + ')';
4433+
end;
4434+
4435+
function XTree_ForIn.DelayedCompile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar;
4436+
begin
4437+
// A for-in loop itself has no delayed logic, but its children might.
4438+
Self.Collection.DelayedCompile(Dest, Flags);
4439+
Self.Body.DelayedCompile(Dest, Flags);
4440+
Result := NullResVar;
4441+
end;
4442+
4443+
function XTree_ForIn.Copy(): XTree_Node;
4444+
begin
4445+
Result := XTree_ForIn.Create(
4446+
Self.ItemVar.Copy as XTree_Identifier,
4447+
Self.Collection.Copy,
4448+
Self.DeclareIdent,
4449+
Self.Body.Copy as XTree_ExprList,
4450+
FContext,
4451+
FDocPos
4452+
);
4453+
end;
4454+
4455+
(*
4456+
This is the core of the implementation. It doesn't emit bytecode directly.
4457+
Instead, it builds an equivalent C-style XTree_For node and then compiles that.
4458+
This is the "desugaring" process.
4459+
*)
4460+
function XTree_ForIn.Compile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar;
4461+
var
4462+
// Nodes for the generated C-style for loop
4463+
highVarDecl, indexVarDecl: XTree_VarDecl;
4464+
forLoopCondition, forLoopIncrement: XTree_Node;
4465+
forLoop: XTree_For;
4466+
itemAssignment: XTree_Node;
4467+
newBody: XTree_ExprList;
4468+
4469+
// Names for compiler-generated internal variables
4470+
highVarName, indexVarName: string;
4471+
4472+
// Type information
4473+
collectionType: XType;
4474+
itemType, ptrType: XType;
4475+
refItemVar: TXprVar;
4476+
_ptrIdx: Int32;
4477+
begin
4478+
// --- 1. Type Checking ---
4479+
collectionType := Self.Collection.ResType();
4480+
4481+
// todo: once classes has properties, allow classes!
4482+
if not (collectionType is XType_Array) then // Also covers XType_String
4483+
ctx.RaiseExceptionFmt('Cannot iterate over non-array type `%s` in a for-in loop.',
4484+
[collectionType.ToString()], Self.Collection.FDocPos);
4485+
4486+
itemType := (collectionType as XType_Array).ItemType;
4487+
4488+
highVarName := '!h';
4489+
indexVarName := '!i';
4490+
4491+
// --- 3. Build the AST for: `var !high := collection.High()` ---
4492+
highVarDecl := XTree_VarDecl.Create(
4493+
highVarName,
4494+
XTree_Invoke.Create(
4495+
XTree_Identifier.Create('High', ctx, Self.Collection.FDocPos),
4496+
[], ctx, Self.Collection.FDocPos
4497+
),
4498+
nil, False, ctx, FDocPos
4499+
);
4500+
XTree_Invoke(highVarDecl.Expr).SelfExpr := Self.Collection; // Attach the collection to the invoke node
4501+
4502+
4503+
// Entry: `var index := 0`
4504+
indexVarDecl := XTree_VarDecl.Create(
4505+
indexVarName,
4506+
XTree_Int.Create('0', ctx, FDocPos),
4507+
nil, False, ctx, FDocPos
4508+
);
4509+
4510+
// Condition: `index <= __high`
4511+
forLoopCondition := XTree_BinaryOp.Create(
4512+
op_LTE,
4513+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4514+
XTree_Identifier.Create(highVarName, ctx, FDocPos),
4515+
ctx, FDocPos
4516+
);
4517+
4518+
// Increment: `index += 1`
4519+
forLoopIncrement := XTree_Assign.Create(op_Asgn,
4520+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4521+
XTree_BinaryOp.Create(
4522+
op_add,
4523+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4524+
XTree_Int.Create('1', ctx, FDocPos),
4525+
ctx, Self.Collection.FDocPos
4526+
),
4527+
ctx, FDocPos
4528+
);
4529+
4530+
// `var expr := collection[__index]`
4531+
if DeclareIdent = 1 then
4532+
begin
4533+
if Self.ItemVar is XTree_Identifier then
4534+
begin
4535+
itemAssignment := XTree_VarDecl.Create(
4536+
XTree_Identifier(Self.ItemVar).Name,
4537+
XTree_Index.Create(
4538+
Self.Collection,
4539+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4540+
ctx, FDocPos
4541+
),
4542+
itemType,
4543+
// Explicitly provide the type
4544+
False, ctx, Self.ItemVar.FDocPos
4545+
);
4546+
end else if Self.ItemVar is XTree_Destructure then
4547+
begin
4548+
itemAssignment := XTree_DestructureDecl.Create(
4549+
XTree_Destructure(Self.ItemVar),
4550+
XTree_Index.Create(
4551+
Self.Collection,
4552+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4553+
ctx, FDocPos
4554+
),
4555+
ctx, Self.ItemVar.FDocPos
4556+
);
4557+
end else
4558+
ctx.RaiseException('Illegal var declaration in for loop', FDocPos);
4559+
end else
4560+
// `ref ident := collection[__index]`
4561+
if DeclareIdent = 2 then
4562+
begin
4563+
if not(Self.ItemVar is XTree_Identifier) then
4564+
ctx.RaiseException('Reference variable in for-in-loop must be an identifier', Self.ItemVar.FDocPos);
4565+
4566+
refItemVar := ctx.RegVar(XTree_Identifier(Self.ItemVar).Name, ctx.GetType(xtPointer), FDocPos, _ptrIdx);
4567+
itemAssignment := XTree_Assign.Create(op_Asgn,
4568+
XTree_VarStub.Create(refItemVar, ctx, FDocPos),
4569+
XTree_UnaryOp.Create(op_Addr,
4570+
XTree_Index.Create(Self.Collection, XTree_Identifier.Create(indexVarName, ctx, FDocPos), ctx, FDocPos),
4571+
ctx, FDocPos
4572+
),
4573+
ctx, FDocPos
4574+
);
4575+
4576+
// all future uses is reference uses
4577+
ctx.Variables.Data[_ptrIdx].Reference := True;
4578+
ctx.Variables.Data[_ptrIdx].VarType := ItemType;
4579+
end else
4580+
begin
4581+
itemAssignment := XTree_Assign.Create(op_asgn,
4582+
Self.ItemVar,
4583+
XTree_Index.Create(
4584+
Self.Collection,
4585+
XTree_Identifier.Create(indexVarName, ctx, FDocPos),
4586+
ctx, FDocPos
4587+
),
4588+
ctx, Self.ItemVar.FDocPos
4589+
);
4590+
end;
4591+
4592+
// Create the new body, starting with our assignment, then adding the user's code.
4593+
newBody := XTree_ExprList.Create(ctx, FDocPos);
4594+
4595+
SetLength(newBody.List, Length(Self.Body.List)+1);
4596+
if Length(Self.Body.List) > 0 then
4597+
Move(Self.Body.List[0], NewBody.List[1], Length(Self.Body.List)*SizeOf(XTree_Node));
4598+
4599+
newBody.List[0] := itemAssignment;
4600+
4601+
forLoop := XTree_For.Create(
4602+
indexVarDecl,
4603+
forLoopCondition,
4604+
forLoopIncrement,
4605+
newBody,
4606+
ctx, FDocPos
4607+
);
4608+
4609+
try
4610+
// Compile the pre-loop statement: var !high := ...
4611+
highVarDecl.Compile(NullResVar, Flags);
4612+
4613+
// Compile the entire generated for-loop structure
4614+
forLoop.Compile(NullResVar, Flags);
4615+
finally
4616+
highVarDecl.Free;
4617+
forLoop.Free;
4618+
end;
4619+
4620+
Result := NullResVar;
4621+
end;
4622+
4623+
43994624

44004625
// ============================================================================
44014626
// REPEAT-UNTIL loop
@@ -4593,8 +4818,10 @@ function XTree_UnaryOp.Compile(Dest: TXprVar; Flags: TCompilerFlags): TXprVar;
45934818
if not LeftVar.Reference then
45944819
ctx.Emit(GetInstr(OP2IC(OP), [Result, LeftVar]), FDocPos)
45954820
else
4821+
begin
45964822
Result := LeftVar;
4597-
4823+
Result.VarType := Self.ResType();
4824+
end;
45984825
Result.Reference := False;
45994826
end;
46004827

0 commit comments

Comments
 (0)