From 551ab9c3ac351368cbda2d595cfaf835e7e692e4 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 16 Mar 2025 17:58:43 +0000 Subject: [PATCH 1/2] #212 make for in/of consistent on objects --- src/Sempare.Template.Evaluate.pas | 9 +++- tests/Sempare.Template.Test.pas | 6 ++- tests/Sempare.Template.TestFor.pas | 29 +++++++++++- tests/Sempare.Template.TestInclude.pas | 2 +- tests/Sempare.Template.TestJson.pas | 61 +++++++++++++++++++++++++- 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index d7d96e4..1da6178 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -521,6 +521,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); LValue: TValue; LValue2: TValue; LRaiseIfMissing: boolean; + LIdx: integer; begin if LLoopExprType.AsInstance.MetaclassType.InheritsFrom(TDataSet) then begin @@ -534,6 +535,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); if LEnumValue.IsEmpty then RaiseErrorRes(AStmt, @SValueIsNotEnumerable); LEnumObj := LEnumValue.AsObject; + LIdx := 0; try LLoopExprType := FContext.RttiContext().GetType(LEnumObj.ClassType); LEnumMoveNextMethod := LLoopExprType.GetMethod('MoveNext'); @@ -543,9 +545,14 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); begin LValue := LEnumCurrentProperty.GetValue(LEnumObj); TryDeref(AStmt, LValue, LRaiseIfMissing, FContext, LValue2); - FStackFrames.peek[LVariableName] := LValue2; + + if AStmt.ForOp = foIn then + FStackFrames.peek[LVariableName] := LIdx + else + FStackFrames.peek[LVariableName] := LValue2; if HandleLoop then break; + inc(LIdx); end; finally LEnumObj.Free; diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 2166ed9..a44c595 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -321,7 +321,8 @@ procedure TTestTemplate.TestList; Assert.AreEqual('2', Template.Eval('<% _.count %>', LList)); Assert.AreEqual('2', Template.Eval('<% data.count %>', LContainer)); Assert.AreEqual('System.Generics.Collections.TObjectList', Template.Eval('<% typeof(data) %>', LContainer)); - Assert.AreEqual(' a b', Template.Eval('<% for x in data %> <% x.data %><% end %>', LContainer)); + Assert.AreEqual(' 0 1', Template.Eval('<% for x in data %> <% x %><% end %>', LContainer)); + Assert.AreEqual(' a b', Template.Eval('<% for x of data %> <% x.data %><% end %>', LContainer)); finally LList.free; end; @@ -858,7 +859,8 @@ procedure TTestTemplate.TestUnderscoreIn; L := TList.Create; try L.AddRange(['1', '2', '3']); - Assert.AreEqual('123', Template.Eval('<% for v in _ %><% v %><% end %>', L)); + Assert.AreEqual('012', Template.Eval('<% for v in _ %><% v %><% end %>', L)); + Assert.AreEqual('123', Template.Eval('<% for v of _ %><% v %><% end %>', L)); finally L.free; end; diff --git a/tests/Sempare.Template.TestFor.pas b/tests/Sempare.Template.TestFor.pas index ab8fec2..4396efc 100644 --- a/tests/Sempare.Template.TestFor.pas +++ b/tests/Sempare.Template.TestFor.pas @@ -232,6 +232,9 @@ TForIn = record try x.range.AddRange([1, 10, 100]); c := Template.parse('<% for i in range %> <% i %> <% end %>'); + Assert.AreEqual(' 0 1 2 ', Template.Eval(c, x)); + + c := Template.parse('<% for i of range %> <% i %> <% end %>'); Assert.AreEqual(' 1 10 100 ', Template.Eval(c, x)); finally x.range.Free; @@ -250,16 +253,26 @@ TForIn = record x.range := TList.Create; try x.range.AddRange([3, 5, 7, 9]); + c := Template.parse('<% for i in range %>
  • <%i%>
  • <%onbegin%>
      <%onend%>
    <%onempty%>empty<%end%>'); + Assert.AreEqual('
    • 0
    • 1
    • 2
    • 3
    ', Template.Eval(c, x)); + + c := Template.parse('<% for i of range %>
  • <%i%>
  • <%onbegin%>
      <%onend%>
    <%onempty%>empty<%end%>'); Assert.AreEqual('
    • 3
    • 5
    • 7
    • 9
    ', Template.Eval(c, x)); c := Template.parse('<% for i in range %><%i%><%betweenitems%>|<%onbegin%>begin<%onend%>end<%onempty%>empty<%end%>'); + Assert.AreEqual('begin0|1|2|3end', Template.Eval(c, x)); + + c := Template.parse('<% for i of range %><%i%><%betweenitems%>|<%onbegin%>begin<%onend%>end<%onempty%>empty<%end%>'); Assert.AreEqual('begin3|5|7|9end', Template.Eval(c, x)); x.range.Clear; c := Template.parse('<% for i in range %>
  • <%i%>
  • <%onbegin%>
      <%onend%>
    <%onempty%>empty<% end %>'); Assert.AreEqual('empty', Template.Eval(c, x)); + c := Template.parse('<% for i of range %>
  • <%i%>
  • <%onbegin%>
      <%onend%>
    <%onempty%>empty<% end %>'); + Assert.AreEqual('empty', Template.Eval(c, x)); + finally x.range.Free; end; @@ -323,7 +336,11 @@ TForIn = record try x.range.AddRange([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); c := Template.parse('<% for i in range offset 5 %> <% i %> <% end %>'); + Assert.AreEqual(' 5 6 7 8 9 ', Template.Eval(c, x)); + + c := Template.parse('<% for i of range offset 5 %> <% i %> <% end %>'); Assert.AreEqual(' 6 7 8 9 10 ', Template.Eval(c, x)); + finally x.range.Free; end; @@ -342,7 +359,11 @@ TForIn = record try x.range.AddRange([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); c := Template.parse('<% for i in range offset 5 limit 2 %> <% i %> <% end %>'); + Assert.AreEqual(' 5 6 ', Template.Eval(c, x)); + + c := Template.parse('<% for i of range offset 5 limit 2 %> <% i %> <% end %>'); Assert.AreEqual(' 6 7 ', Template.Eval(c, x)); + finally x.range.Free; end; @@ -465,7 +486,9 @@ procedure TTestTemplateFor.TestStructure; info := TList.Create; try info.AddRange([TInfo.Create('conrad', 10), TInfo.Create('christa', 20)]); - Assert.AreEqual(' conrad 10 christa 20', Template.Eval('<%for i in _ %> <% i.name %> <% i.age %><%end%>', info)); + Assert.AreEqual(' 0 1', Template.Eval('<%for i in _ %> <% i%><%end%>', info)); + + Assert.AreEqual(' conrad 10 christa 20', Template.Eval('<%for i of _ %> <% i.name %> <% i.age %><%end%>', info)); finally info.Free; end; @@ -480,7 +503,9 @@ procedure TTestTemplateFor.TestForInDict; try LDict.Add('a', 1); LDict.Add('b', 2); - Assert.AreEqual(' b 2 a 1', Template.Eval('<%for i in _ %> <% i.key %> <% i.value %><%end%>', LDict)); + Assert.AreEqual(' 0 1', Template.Eval('<%for i in _ %> <% i %><%end%>', LDict)); + + Assert.AreEqual(' b 2 a 1', Template.Eval('<%for i of _ %> <% i.key %> <% i.value %><%end%>', LDict)); finally LDict.Free; end; diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index 6ac0298..64abe5a 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -292,7 +292,7 @@ procedure TTestTemplateInclude.TestInclude; c := Template.parse( // '<% suffix := ''er''%><% include (''head'' + suffix) %>' + // - '<%for v in content %>' + '<% v %>' + // + '<%for v of content %>' + '<% v %>' + // '<% end %>' + // '<% include (''foot'' + suffix) %>'); diff --git a/tests/Sempare.Template.TestJson.pas b/tests/Sempare.Template.TestJson.pas index 4245a60..2ddd642 100644 --- a/tests/Sempare.Template.TestJson.pas +++ b/tests/Sempare.Template.TestJson.pas @@ -66,11 +66,18 @@ TTestTemplateJson = class [test] procedure TestJsonDerefAV; + [test] + procedure TestJsonIn; + + [test] + procedure TestJsonOf; + end; implementation uses + System.SysUtils, Sempare.Template.JSON, Sempare.Template; @@ -119,13 +126,38 @@ procedure TTestTemplateJson.TestJsonEmptyArray; o.Free; end; +procedure TTestTemplateJson.TestJsonIn; +var + LScript: string; + LResult: string; + LExpect: string; + LJsonArray: TJSONArray; +begin + LScript := // + '<% arr := [5,4,3]; ' + sLineBreak + // + sLineBreak + // + 'for x in arr ; x ; betweenitems %>, <% end %>' + sLineBreak; // + LExpect := '0, 1, 2' + sLineBreak; + LResult := Template.Eval(LScript); + Assert.AreEqual(LExpect, LResult); + + LJsonArray := TJsonValue.ParseJSONValue('[5,4,3]') as TJSONArray; + LScript := '<% for x in _ ; x ; betweenitems %>, <% end %>' + sLineBreak; + try + LResult := Template.Eval(LScript, LJsonArray); + Assert.AreEqual(LExpect, LResult); + finally + LJsonArray.Free; + end; +end; + procedure TTestTemplateJson.TestJsonArray; var o: TJsonValue; begin o := TJsonValue.ParseJSONValue('[1,2,3,4,5]'); - Assert.AreEqual('1 2 3 4 5 ', Template.Eval('<% for i in _ %><% i %> <% end %>', o)); - + Assert.AreEqual('0 1 2 3 4 ', Template.Eval('<% for i in _ %><% i %> <% end %>', o)); + Assert.AreEqual('1 2 3 4 5 ', Template.Eval('<% for i of _ %><% i %> <% end %>', o)); o.Free; end; @@ -146,6 +178,31 @@ procedure TTestTemplateJson.TestJsonObject; o.Free; end; +procedure TTestTemplateJson.TestJsonOf; +var + LScript: string; + LResult: string; + LExpect: string; + LJsonArray: TJSONArray; +begin + LScript := // + '<% arr := [5,4,3]; ' + sLineBreak + // + sLineBreak + // + 'for x of arr ; x ; betweenitems %>, <% end %>' + sLineBreak; // + LExpect := '5, 4, 3' + sLineBreak; + LResult := Template.Eval(LScript); + Assert.AreEqual(LExpect, LResult); + + LJsonArray := TJsonValue.ParseJSONValue('[5,4,3]') as TJSONArray; + LScript := '<% for x of _ ; x ; betweenitems %>, <% end %>' + sLineBreak; + try + LResult := Template.Eval(LScript, LJsonArray); + Assert.AreEqual(LExpect, LResult); + finally + LJsonArray.Free; + end; +end; + procedure TTestTemplateJson.TestParseJson; begin Assert.AreEqual('123.45', Template.Eval('<% ParseJson("123.45") %>')); From f3a37d648d1cab0bf1075cf9bdf4511bfba1731a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 16 Mar 2025 22:01:03 +0000 Subject: [PATCH 2/2] make enumerating datasets consistent with other objects using in/of on for loops --- src/Sempare.Template.Evaluate.pas | 5 ++- tests/Sempare.Template.TestFor.pas | 53 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 1da6178..84e1a72 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -503,7 +503,10 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); while not LDataSetEOFProperty.GetValue(LObj).AsBoolean and ((LLimit = -1) or (LLoops < LLimit)) do begin - FStackFrames.peek[LVariableName] := LIdx; + if AStmt.ForOp = foIn then + FStackFrames.peek[LVariableName] := LIdx - 1 + else + FStackFrames.peek[LVariableName] := LLoopExpr; if HandleLoop then break; LDataSetNextMethod.Invoke(LObj, []); diff --git a/tests/Sempare.Template.TestFor.pas b/tests/Sempare.Template.TestFor.pas index 4396efc..f47e166 100644 --- a/tests/Sempare.Template.TestFor.pas +++ b/tests/Sempare.Template.TestFor.pas @@ -147,6 +147,7 @@ procedure TTestTemplateFor.TestDataSet; var ds: TDataSet; begin + // traditional ds := CreateMockUsersTable(); try Assert.AreEqual('joe pete jane ', // @@ -154,12 +155,30 @@ procedure TTestTemplateFor.TestDataSet; finally ds.Free; end; + // index in + ds := CreateMockUsersTable(); + try + Assert.AreEqual('0 1 2 ', // + Template.Eval('<% for i in _ %><% i %> <%end%>', ds)); + finally + ds.Free; + end; + // index of + ds := CreateMockUsersTable(); + try + Assert.AreEqual('joe pete jane ', // + Template.Eval('<% for i of _ %><% i[''name''] %> <%end%>', ds)); + finally + ds.Free; + end; + end; procedure TTestTemplateFor.TestDataSetWithEvent; var ds: TDataSet; begin + // traditional ds := CreateMockUsersTable(); try Assert.AreEqual('
    • joe
    • pete
    • jane
    ', // @@ -175,6 +194,40 @@ procedure TTestTemplateFor.TestDataSetWithEvent; finally ds.Free; end; + + // using index in + ds := CreateMockUsersTable(); + try + Assert.AreEqual('
    • 0
    • 1
    • 2
    ', // + Template.Eval('<% for i in _ %>
  • <% i %>
  • <% onbegin%>
      <%onend%>
    <%end%>', ds)); + + ds.Delete(); + ds.Delete(); + ds.Delete(); + + Assert.AreEqual('

    No values

    ', // + Template.Eval('<% for i in _ %>
  • <% i %>
  • <% onbegin%>
      <%onend%>
    <% onempty%>

    No values

    <%end%>', ds)); + + finally + ds.Free; + end; + + // using index of + ds := CreateMockUsersTable(); + try + Assert.AreEqual('
    • joe
    • pete
    • jane
    ', // + Template.Eval('<% for i of _ %>
  • <% i[''name''] %>
  • <% onbegin%>
      <%onend%>
    <%end%>', ds)); + + ds.Delete(); + ds.Delete(); + ds.Delete(); + + Assert.AreEqual('

    No values

    ', // + Template.Eval('<% for i in _ %>
  • <% [''name''] %>
  • <% onbegin%>
      <%onend%>
    <% onempty%>

    No values

    <%end%>', ds)); + + finally + ds.Free; + end; end; procedure TTestTemplateFor.TestDataSetCount;