From 9bb3faf2e384c707c306288e8fac5ccb90cd829e Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Fri, 8 Nov 2024 21:37:22 +0100 Subject: [PATCH 01/27] feat: support nested if separated by other statements and a way to end a single if A small improvement have been done also since now after the first failing if the following one are no more executed --- src/lua/zencode.lua | 33 +++++++++++++++++++++++---------- src/lua/zencode_debug.lua | 4 ++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 2ce287ae8..b8a0e74db 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -56,8 +56,8 @@ ZEN = { endforeach_steps = { endforeach = function() return end }, --nop then_steps = {}, schemas = {}, - branch = false, - branch_valid = false, + branch = 0, + branch_valid = 0, id = 0, checks = {version = false}, -- version, scenario checked, etc. OK = true, -- set false by asserts @@ -390,7 +390,7 @@ function ZEN:begin(new_heap) callbacks.onendifforeach = set_sentence local extra_events = { {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'when'}, - {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endforeach'}, to = 'if'}, + {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endforeach', 'whenif', 'thenif', 'endforeachif'}, to = 'if'}, {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeachif'}, to = 'whenif'}, {name = 'enter_thenif', from = {'if', 'whenif', 'thenif'}, to = 'thenif'}, {name = 'enter_endif', from = {'whenif', 'thenif', 'endforeachif'}, to = 'endif'}, @@ -581,18 +581,31 @@ end -- return false: execute statement local function manage_branching(stack, x) if string.match(x.section, '^if') then - --xxx("START conditional execution: "..x.source, 2) - if not stack.branch then stack.branch_valid = true end - stack.branch = true - return false + -- xxx("START conditional execution: "..x.source, 2) + -- if stack.branch == 0 then stack.branch_valid = true end + stack.branch = stack.branch+1 + if stack.branch_valid == stack.branch-1 then + stack.branch_valid = stack.branch_valid+1 + return false + end + return true end if string.match(x.section, '^endif') then --xxx("END conditional execution: "..x.source, 2) - stack.branch = false + stack.branch = 0 + stack.branch_valid = 0 + return true + end + if string.match(x.section, '^endoneif') then + --xxx("END conditional execution: "..x.source, 2) + if stack.branch_valid == stack.branch then + stack.branch_valid = stack.branch_valid-1 + end + stack.branch = stack.branch-1 return true end - if not stack.branch then return false end - if not stack.branch_valid then + if stack.branch == 0 then return false end + if stack.branch > stack.branch_valid then --xxx('skip execution in false conditional branch: '..x.source, 2) return true end diff --git a/src/lua/zencode_debug.lua b/src/lua/zencode_debug.lua index 11e220956..c9feb6832 100644 --- a/src/lua/zencode_debug.lua +++ b/src/lua/zencode_debug.lua @@ -117,10 +117,10 @@ zencode_assert = function(condition, errmsg) if condition then return true else - ZEN.branch_valid = false + ZEN.branch_valid = fif(ZEN.branch_valid > 0, ZEN.branch_valid - 1, 0) end -- in conditional branching zencode_assert doesn't quit - if ZEN.branch then + if ZEN.branch ~= 0 then table.insert(traceback, errmsg) else -- ZEN.debug() -- prints all data in memory From 7436222b29255508f57ae032d1dc825dc6f0f831 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 11 Nov 2024 08:34:50 +0100 Subject: [PATCH 02/27] fix: first update to parser to support nested if now parser detect also opened but not closed if branching --- src/lua/zencode.lua | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index b8a0e74db..ac8f06497 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -52,6 +52,7 @@ ZEN = { when_steps = {}, if_steps = {}, endif_steps = { endif = function() return end }, --nop + endoneif_steps = { endoneif = function() return end }, --nop foreach_steps = {}, endforeach_steps = { endforeach = function() return end }, --nop then_steps = {}, @@ -388,12 +389,14 @@ function ZEN:begin(new_heap) callbacks.onifforeach = set_sentence callbacks.onwhenifforeach = set_sentence callbacks.onendifforeach = set_sentence + callbacks.onendoneif = set_sentence local extra_events = { - {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'when'}, - {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endforeach', 'whenif', 'thenif', 'endforeachif'}, to = 'if'}, - {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeachif'}, to = 'whenif'}, - {name = 'enter_thenif', from = {'if', 'whenif', 'thenif'}, to = 'thenif'}, - {name = 'enter_endif', from = {'whenif', 'thenif', 'endforeachif'}, to = 'endif'}, + {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endoneif', 'endforeach'}, to = 'when'}, + {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endoneif', 'endforeach', 'whenif', 'thenif', 'endforeachif'}, to = 'if'}, + {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'whenif'}, + {name = 'enter_thenif', from = {'if', 'whenif', 'thenif', 'endoneif'}, to = 'thenif'}, + {name = 'enter_endif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'endif'}, + {name = 'enter_endoneif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'endoneif'}, {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeachif', 'foreachif'}, to = 'foreachif'}, {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif'}, to = 'whenforeachif'}, {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif'}, to = 'endforeachif'}, @@ -455,7 +458,7 @@ function ZEN:parse(text) error("Zencode text too short to parse") return false end - local branching = false + local branching = {} local looping = false local prefixes = {} local parse_prefix = parse_prefix -- optimization @@ -483,15 +486,22 @@ function ZEN:parse(text) break -- stop parsing after given block end - if not branching and prefix == 'if' then - branching = true - table.insert(prefixes, 1, 'if') + if prefix == 'if' then + if #branching == 0 then + table.insert(prefixes, 1, 'if') + end + table.insert(branching, self.linenum) elseif not looping and prefix == 'foreach' then looping = true table.insert(prefixes, 1, 'foreach') elseif prefix == 'endif' then - branching = false + branching = {} table.remove(prefixes, 1) + elseif prefix == 'endoneif' then + table.remove(branching) + if #branching == 0 then + table.remove(prefixes, 1) + end elseif prefix == 'endforeach' then looping = false table.remove(prefixes, 1) @@ -555,6 +565,9 @@ function ZEN:parse(text) end -- continue end + if #branching > 0 then + error("Ivalid branching opened at lines "..table.concat(branching, ", ").." and never closed") + end collectgarbage'collect' if res == true then return true From 14300a18c3d34b96cad9b7265f13f995d42d0368 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 11 Nov 2024 08:46:58 +0100 Subject: [PATCH 03/27] fix: typos in error message --- src/lua/zencode.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index ac8f06497..4fda70856 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -566,7 +566,7 @@ function ZEN:parse(text) -- continue end if #branching > 0 then - error("Ivalid branching opened at lines "..table.concat(branching, ", ").." and never closed") + error("Invalid branching opened at line "..table.concat(branching, ", ").." and never closed") end collectgarbage'collect' if res == true then From f8d5e5e44aeaccc276841f0e02ab3ad3615a61c6 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 11 Nov 2024 09:23:00 +0100 Subject: [PATCH 04/27] test: add first tests for nested branching --- test/zencode/branching.bats | 89 +++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index decd1981e..b91cbf494 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -599,3 +599,92 @@ EOF save_output verify_length.json assert_output '{"output":["all_comparison_succedded"]}' } + +@test "Detect open but not closed if branching" { + cat << EOF | save_asset not_closed_if.zen +Given nothing +When I set 'my_string' to 'test' as 'string' +If I verify 'my_string' is found +Then I print 'my string' +EOF + run $ZENROOM_EXECUTABLE -z not_closed_if.zen + assert_line --partial 'Invalid branching opened at line 3 and never closed' +} + +@test "Detect multiple open but not closed if branching" { + cat << EOF | save_asset multiple_not_closed_if.zen +Given nothing +When I set 'my_string' to 'test' as 'string' +If I verify 'my_string' is found +If I verify 'my_string' is equal to 'test' +Then I print 'my string' +If I verify 'my_string' is not equal to 'not_test' +EOF + run $ZENROOM_EXECUTABLE -z multiple_not_closed_if.zen + assert_line --partial 'Invalid branching opened at line 3, 4, 6 and never closed' +} + +@test "Invalid transition to if" { + cat << EOF | save_asset invalid_transition.zen +If I verify 'my_string' is found +Given nothing +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z invalid_transition.zen + assert_line --partial "Invalid transition from: init to: If I verify 'my_string' is found" +} + +@test "Nested if branching" { + cat << EOF | save_asset nested_if.data.json +{ + "external_qr_content": { + "credential_issuer": "https://ministerie-agent.dev.impierce.com/", + "credential_configuration_ids": [ + "openbadge_credential" + ], + "grants": { + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": "ebb90f2db21a4708b93217a686f91e134b370b350aae18dc25a382507b141c13" + } + } + } +} +EOF + + cat << EOF | zexe nested_if.zen nested_if.data.json +Given I have a 'string dictionary' named 'external_qr_content' +Given I have a 'string' named 'credential_issuer' inside 'external_qr_content' +Given I have a 'string array' named 'credential_configuration_ids' inside 'external_qr_content' + +If I verify 'grants' is found in 'external_qr_content' + When I pickup from path 'external_qr_content.grants' + If I verify 'authorization_code' is found in 'grants' + When I pickup from path 'grants.authorization_code' + If I verify 'authorization_server' is found in 'authorization_code' + When I pickup from path 'authorization_code.authorization_server' + Then print the 'authorization_server' + EndOneIf + EndOneIf + If I verify 'urn:ietf:params:oauth:grant-type:pre-authorized_code' is found in 'grants' + When I pickup from path 'grants.urn:ietf:params:oauth:grant-type:pre-authorized_code' + If I verify 'pre-authorized_code' is found in 'urn:ietf:params:oauth:grant-type:pre-authorized_code' + When I pickup from path 'urn:ietf:params:oauth:grant-type:pre-authorized_code.pre-authorized_code' + Then print the 'pre-authorized_code' + EndOneIf + EndOneIf +EndOneIf + +When I create copy of element '1' from array 'credential_configuration_ids' +When I rename the 'copy' to 'credential_configuration_id' + +If I verify 'credential_issuer' ends with '/' + When I split rightmost '1' bytes of 'credential_issuer' +EndIf +When I append the string '/.well-known/openid-credential-issuer' to 'credential_issuer' + +Then print the 'credential_configuration_id' +Then print the 'credential_issuer' +EOF + save_output 'nested_if.out.json' + assert_output '{"credential_configuration_id":"openbadge_credential","credential_issuer":"https://ministerie-agent.dev.impierce.com/.well-known/openid-credential-issuer","pre-authorized_code":"ebb90f2db21a4708b93217a686f91e134b370b350aae18dc25a382507b141c13"}' +} From e47087e431b656f3b9e7582765e884b627794815 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 11 Nov 2024 09:27:14 +0100 Subject: [PATCH 05/27] test(array): close unclosed if branches --- test/zencode/array.bats | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/zencode/array.bats b/test/zencode/array.bats index 05b00f073..d577cd656 100755 --- a/test/zencode/array.bats +++ b/test/zencode/array.bats @@ -11,7 +11,6 @@ and I rename the 'array' to 'bonnetjes' Then print the 'bonnetjes' EOF save_output arr.json - } @test "When I create the size of" { @@ -331,7 +330,7 @@ Given I have a 'string array' named 'haystack' and I have a 'number' named 'quorum' and I have a 'string' named 'needle' When I verify the 'needle' is found in 'haystack' at least 'quorum' times -Then Print the string 'Success' +Then Print the string 'Success' EOF save_output "needle_in_haystack.json" assert_output '{"output":["Success"]}' @@ -685,6 +684,7 @@ If I verify the elements in 'dict' are all equal If I verify the elements in 'nested arr' are all equal If I verify the elements in 'empty dict' are all equal Then print string 'OK' +EndIf EOF save_output "compare-equal.json" assert_output '{"output":["OK"]}' @@ -716,6 +716,7 @@ If I verify the elements in 'arr' are not all equal If I verify the elements in 'dict' are not all equal If I verify the elements in 'nested arr' are not all equal Then print string 'OK' +EndIf EOF save_output "compare-not-equal.json" assert_output '{"output":["OK"]}' From e68e42b7be4c50553e6283e1c0d0cca43fd8739d Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 11 Nov 2024 15:14:03 +0100 Subject: [PATCH 06/27] fix: parser detect wrong if branching exit --- src/lua/zencode.lua | 6 ++++++ test/zencode/branching.bats | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 4fda70856..f9d35d3f7 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -495,9 +495,15 @@ function ZEN:parse(text) looping = true table.insert(prefixes, 1, 'foreach') elseif prefix == 'endif' then + if #branching == 0 then + error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') + end branching = {} table.remove(prefixes, 1) elseif prefix == 'endoneif' then + if #branching == 0 then + error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') + end table.remove(branching) if #branching == 0 then table.remove(prefixes, 1) diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index b91cbf494..9f100c958 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -688,3 +688,45 @@ EOF save_output 'nested_if.out.json' assert_output '{"credential_configuration_id":"openbadge_credential","credential_issuer":"https://ministerie-agent.dev.impierce.com/.well-known/openid-credential-issuer","pre-authorized_code":"ebb90f2db21a4708b93217a686f91e134b370b350aae18dc25a382507b141c13"}' } + +@test "Invalid signle endif and endoneif" { + cat << EOF | save_asset invalid_single_endif.zen +Given nothing +EndIf +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z invalid_single_endif.zen + assert_line --partial "Ivalid branching closing at line 2: nothing to be closed" + + cat << EOF | save_asset invalid_single_endoneif.zen +Given nothing +EndOneIf +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z invalid_single_endoneif.zen + assert_line --partial "Ivalid branching closing at line 2: nothing to be closed" +} + +@test "Invalid exit from an already closed branch" { + cat << EOF | save_asset endif_on_closed_branch.zen +Given nothing +When I set 'my_string' to 'test' as 'string' +If I verify 'my_string' is found +Then I print 'my string' +EndIf +EndOneIf +EOF + run $ZENROOM_EXECUTABLE -z endif_on_closed_branch.zen + assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" + + cat << EOF | save_asset endoneif_on_closed_branch.zen +Given nothing +When I set 'my_string' to 'test' as 'string' +If I verify 'my_string' is found +Then I print 'my string' +EndOneIf +EndIf +EOF + run $ZENROOM_EXECUTABLE -z endoneif_on_closed_branch.zen + assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" +} From ece7b32eb4c4e2eb8a8116452fd8cc648029ba24 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Wed, 13 Nov 2024 18:04:07 +0100 Subject: [PATCH 07/27] feat: support also nested foreach loops This is a first draft, more test and improvements are needed --- src/lua/zencode.lua | 97 +++++++++++++++++++++++++------------ src/lua/zencode_foreach.lua | 17 ++++--- test/zencode/branching.bats | 86 ++++++++++++++++++++++++++++++++ test/zencode/foreach.bats | 50 +++++++++++++++++++ 4 files changed, 211 insertions(+), 39 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index f9d35d3f7..00fd913b4 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -64,7 +64,7 @@ ZEN = { OK = true, -- set false by asserts current_instruction = 0, -- first instruction next_instruction = 1, -- first instruction - ITER = nil, -- foreach infos + ITER = {}, -- foreach infos traceback = {}, -- transferred into HEAP by zencode_begin linenum = 0, last_valid_statement = false, @@ -126,6 +126,7 @@ function ZEN:begin(new_heap) whenifforeach = 'when', foreachif = 'foreach', endforeachif = 'endforeach', + endforeachifforeach = 'endforeach', ifforeach = 'if', endifforeach = 'endif', } @@ -390,22 +391,26 @@ function ZEN:begin(new_heap) callbacks.onwhenifforeach = set_sentence callbacks.onendifforeach = set_sentence callbacks.onendoneif = set_sentence + callbacks.onendforeachifforeach = set_sentence + callbacks.onendforeachforeach = set_sentence local extra_events = { {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endoneif', 'endforeach'}, to = 'when'}, - {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endoneif', 'endforeach', 'whenif', 'thenif', 'endforeachif'}, to = 'if'}, + {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endoneif', 'endforeach', 'whenif', 'thenif', 'endforeachif', 'foreachif'}, to = 'if'}, {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'whenif'}, {name = 'enter_thenif', from = {'if', 'whenif', 'thenif', 'endoneif'}, to = 'thenif'}, {name = 'enter_endif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'endif'}, - {name = 'enter_endoneif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'endoneif'}, - {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeachif', 'foreachif'}, to = 'foreachif'}, - {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif'}, to = 'whenforeachif'}, - {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif'}, to = 'endforeachif'}, - {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endifforeach'}, to = 'ifforeach'}, - {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach'}, to = 'whenifforeach'}, + {name = 'enter_endoneif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif', 'whenifforeach'}, to = 'endoneif'}, + {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeachif', 'foreachif', 'endoneif', 'whenifforeach'}, to = 'foreachif'}, + {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'whenforeachif'}, + {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'endforeachif'}, + {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endifforeach', 'foreachif', 'whenifforeach', 'endoneif'}, to = 'ifforeach'}, + {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endoneif', 'endforeachifforeach'}, to = 'whenifforeach'}, {name = 'enter_endifforeach', from = {'ifforeach', 'whenifforeach'}, to = 'endifforeach'}, - {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach'}, to = 'foreach'}, - {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endifforeach'}, to = 'whenforeach'}, - {name = 'enter_endforeach', from = {'whenforeach', 'endifforeach'}, to = 'endforeach'}, + {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach', 'whenforeach'}, to = 'foreach'}, + {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endifforeach', 'endforeach'}, to = 'whenforeach'}, + {name = 'enter_endforeach', from = {'whenforeach', 'endifforeach', 'endforeach'}, to = 'endforeach'}, + {name = 'enter_endforeachforeach', from = {'whenforeach', 'endifforeach', 'endforeach'}, to = 'endforeach'}, + {name = 'enter_endforeachifforeach', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'endforeachifforeach'}, {name = 'enter_and', from = 'when', to = 'when'}, {name = 'enter_and', from = 'whenif', to = 'whenif'}, {name = 'enter_and', from = 'thenif', to = 'thenif'}, @@ -415,7 +420,8 @@ function ZEN:begin(new_heap) {name = 'enter_and', from = 'foreachif', to = 'foreachif'}, {name = 'enter_and', from = 'whenforeach', to = 'whenforeach'}, {name = 'enter_and', from = 'whenifforeach', to = 'whenifforeach'}, - {name = 'enter_then', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'then'}, + {name = 'enter_and', from = 'whenforeachif', to = 'whenforeachif'}, + {name = 'enter_then', from = {'given', 'when', 'then', 'endif', 'endforeach', 'endoneif'}, to = 'then'}, {name = 'enter_and', from = 'then', to = 'then'}, } for _,v in pairs(extra_events) do table.insert(events, v) end @@ -459,7 +465,7 @@ function ZEN:parse(text) return false end local branching = {} - local looping = false + local looping = {} local prefixes = {} local parse_prefix = parse_prefix -- optimization self.linenum = 0 @@ -487,13 +493,17 @@ function ZEN:parse(text) end if prefix == 'if' then - if #branching == 0 then + local already_if_prefix = prefixes[1] == 'if' + if not already_if_prefix then table.insert(prefixes, 1, 'if') end - table.insert(branching, self.linenum) - elseif not looping and prefix == 'foreach' then - looping = true - table.insert(prefixes, 1, 'foreach') + table.insert(branching, { self.linenum, already_if_prefix }) + elseif prefix == 'foreach' then + local already_foreach_prefix = prefixes[1] == 'foreach' + if not already_foreach_prefix then + table.insert(prefixes, 1, 'foreach') + end + table.insert(looping, { self.linenum, already_foreach_prefix }) elseif prefix == 'endif' then if #branching == 0 then error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') @@ -504,19 +514,24 @@ function ZEN:parse(text) if #branching == 0 then error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') end - table.remove(branching) - if #branching == 0 then + local removed = table.remove(branching) + if not removed[2] then table.remove(prefixes, 1) end elseif prefix == 'endforeach' then - looping = false - table.remove(prefixes, 1) - end + if #looping == 0 then + error('Ivalid loop closing at line '..self.linenum..': nothing to be closed') + end + local removed = table.remove(looping) + if not removed[2] then + table.remove(prefixes, 1) + end + end if prefix == 'if' or prefix == 'foreach' then - prefix = table.concat(prefixes,'') + prefix = prefixes[1]..(prefixes[2] or '') elseif prefix == 'when' or prefix == 'then' or prefix == 'endif' or prefix == 'endforeach' then - prefix = prefix .. table.concat(prefixes,'') + prefix = prefix .. (prefixes[1] or '').. (prefixes[2] or '') end if prefix == "when" or prefix == "if" or prefix == "foreach" then @@ -572,7 +587,18 @@ function ZEN:parse(text) -- continue end if #branching > 0 then - error("Invalid branching opened at line "..table.concat(branching, ", ").." and never closed") + local err_lines = {} + for _, v in pairs(branching) do + table.insert(err_lines, v[1]) + end + error("Invalid branching opened at line "..table.concat(err_lines, ", ").." and never closed") + end + if #looping > 0 then + local err_lines = {} + for _, v in pairs(looping) do + table.insert(err_lines, v[1]) + end + error("Invalid looping opened at line "..table.concat(err_lines, ", ").." and never closed") end collectgarbage'collect' if res == true then @@ -636,24 +662,31 @@ end -- TODO(optimization): introduce a second jump to skip all -- statements in the foreach in the last iteration local function manage_foreach(stack, x) - if string.match(x.section, '^foreach') and not stack.ITER then - stack.ITER = {jump = stack.current_instruction, pos = 1} + if string.match(x.section, '^foreach') and + ( #stack.ITER == 0 or + ( stack.ITER[#stack.ITER].jump ~= stack.current_instruction and + stack.ITER[#stack.ITER].pos ~=0 )) then + table.insert(stack.ITER, {jump = stack.current_instruction, pos = 1}) return false end if string.match(x.section, '^endforeach') then - local info = stack.ITER + local info = stack.ITER[#stack.ITER] + if info.pos == 0 and info.end_line ~= x.linenum then + return true + end + if not info.end_line then info.end_line = x.linenum end if info.pos > 0 then info.pos = info.pos + 1 stack.next_instruction = info.jump return true else - stack.ITER = nil + table.remove(stack.ITER) end end - if stack.ITER and stack.ITER.pos >= MAXITER then + if #stack.ITER ~= 0 and stack.ITER[#stack.ITER].pos >= MAXITER then error("Limit of iterations reached: " .. MAXITER) end - return stack.ITER and stack.ITER.pos == 0 + return #stack.ITER ~= 0 and stack.ITER[#stack.ITER].pos == 0 end function ZEN:run() diff --git a/src/lua/zencode_foreach.lua b/src/lua/zencode_foreach.lua index 719b8efab..b1189c2ad 100644 --- a/src/lua/zencode_foreach.lua +++ b/src/lua/zencode_foreach.lua @@ -1,14 +1,15 @@ local function clear_iterators() - if not ZEN.ITER.names then return end - for _,v in pairs(ZEN.ITER.names) do + local info = ZEN.ITER[#ZEN.ITER] + if not info.names then return end + for _,v in pairs(info.names) do ACK[v] = nil CODEC[v] = nil end - ZEN.ITER.names = nil + info.names = nil end Foreach("'' in ''", function(name, collection) - local info = ZEN.ITER + local info = ZEN.ITER[#ZEN.ITER] local col = have(collection) local collection_codec = CODEC[collection] zencode_assert(collection_codec.zentype == "a", "Can only iterate over arrays") @@ -38,7 +39,7 @@ Foreach("'' in ''", function(name, collection) end) Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, to_name, step_name) - local info = ZEN.ITER + local info = ZEN.ITER[#ZEN.ITER] local from = have(from_name) local to = have(to_name) local step = have(step_name) @@ -87,8 +88,10 @@ Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, t end) local function break_foreach() - zencode_assert(ZEN.ITER and ZEN.ITER.pos ~=0, "Can only exit from foreach loop") - ZEN.ITER.pos = 0 + if #ZEN.ITER == 0 or not ZEN.ITER[#ZEN.ITER].pos then + error("Can only exit from foreach loop", 2) + end + ZEN.ITER[#ZEN.ITER].pos = 0 clear_iterators() end diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index 9f100c958..42d814d56 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -729,4 +729,90 @@ EndIf EOF run $ZENROOM_EXECUTABLE -z endoneif_on_closed_branch.zen assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" + + cat << EOF | save_asset endoneif_on_closed_branch_2.zen +Given nothing +When I set 'my_string' to 'test' as 'string' +If I verify 'my_string' is found +Then I print 'my string' +EndOneIf +EndOneIf +EOF + run $ZENROOM_EXECUTABLE -z endoneif_on_closed_branch_2.zen + assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" +} + +@test "Nested if branching in foreach" { + cat << EOF | save_asset nested_if_in_foreach.data.json +{ + "my_array": [ + { + "data": { + "key": "value" + } + }, + { + "other_data": { + "other_key": [ + "other_value_1", + "other_value_2" + ] + } + } + ], + "one": 1, + "filter": "other_value_2" +} +EOF + + cat << EOF | zexe nested_if_in_foreach.zen nested_if_in_foreach.data.json +Given I have a 'string array' named 'my_array' +and I have a 'number' named 'one' +and I have a 'string' named 'filter' +When I create the 'string array' named 'res' +When I create the 'string array' named 'res_foreach' +If I verify 'my_array' is found + If I verify size of 'my_array' is more than 'one' + Then print the string 'long array' + EndOneIf + Foreach 'el' in 'my_array' + If I verify 'data' is found in 'el' + When I pickup from path 'el.data' + If I verify 'other_key' is found in 'data' + When I pickup from path 'data.other_key' + Foreach 'e' in 'other_key' + When I copy 'e' in 'res_foreach' + EndForeach + When I remove 'other_key' + EndOneIf + If I verify 'key' is found in 'data' + When I move 'key' from 'data' in 'res' + EndOneIf + When I remove 'data' + EndOneIf + If I verify 'other_data' is found in 'el' + When I pickup from path 'el.other_data' + If I verify 'other_key' is found in 'other_data' + When I pickup from path 'other_data.other_key' + Foreach 'e' in 'other_key' + If I verify 'e' is equal to 'filter' + When I copy 'e' in 'res_foreach' + EndOneIf + When done + EndForeach + When I remove 'other_key' + EndOneIf + If I verify 'key' is found in 'other_data' + When I move 'key' from 'other_data' in 'res' + EndOneIf + When I remove 'other_data' + EndOneIf + EndForeach +EndOneIf + +Then print 'res' +Then print 'res_foreach' +EOF + save_output 'nested_if_in_foreach.out.json' + assert_output '{"output":["long_array"],"res":["value"],"res_foreach":["other_value_2"]}' } diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index 37ec1ae09..fdb99c3a9 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -371,3 +371,53 @@ EOF save_output foreach_schema.out assert_output '{"res":"eyJhbGciOiAiRVMyNTYiLCAidHlwIjogInZjK3NkLWp3dCJ9.eyJfc2QiOiBbInZucGt1cWZBSWFBTlBmZXl6WXhUVllWUGxJY3JBWlVvU3N5TGFhQ0tlWUkiLCAiM1BEeUhOMXphcklJMG1TdDUwMkV2ZVIwVHhlOHlTQ1hDOFlYd1NvV1lZWSIsICJFMS12Wnl1Wmhlbkhlam1nYi1kTFhtaDBPODFLTUtWU25RYjN2Mjl6SGlRIl0sICJfc2RfYWxnIjogInNoYS0yNTYiLCAiaXNzIjogImh0dHA6Ly9leGFtcGxlLm9yZyIsICJzdWIiOiAidXNlciA0MiJ9.-BY5L0dcz2p-nCIhmL_0RK5QjzmKOI45E7anmZqg0Vct16lyF2_em3R8GzewmcrVT-NnZaT6EKrO79F4VGkFeQ~WyJ1eURGMUgwQVlSYmI0TVFubUx2dmVBIiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJWam9zM2ZoOFZVU3Z2NHVYUVhuSWlRIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyIzV2JpbGZtNk1rdDFBUzlERXFtOS1nIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~"}' } + +@test "Invalid signle endforeach" { + cat << EOF | save_asset invalid_single_endforeach.zen +Given nothing +EndForeach +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z invalid_single_endforeach.zen + assert_line --partial "Ivalid loop closing at line 2: nothing to be closed" +} + +@test "Detect multiple open but not closed foreach loop" { + cat << EOF | save_asset multiple_not_closed_foreach.zen +Given nothing +When I create the 'string array' named 'loop' +When I write string '' in 'res' +Foreach 'a' in 'loop' +Foreach 'b' in 'loop' +Foreach 'c' in 'loop' +When I append 'c' to 'res' +When I append 'b' to 'res' +When I append 'a' to 'res' +EOF + run $ZENROOM_EXECUTABLE -z multiple_not_closed_foreach.zen + assert_line --partial 'Invalid looping opened at line 4, 5, 6 and never closed' +} + +@test "Nested foreach loop" { + cat << EOF | zexe multiple_not_closed_foreach.zen +Given nothing +When I create the 'string array' named 'loop' +When I set 'str' to 'a' as 'string' +and I move 'str' in 'loop' +When I set 'str' to 'b' as 'string' +and I move 'str' in 'loop' +When I write string '' in 'res' +Foreach 'a' in 'loop' + When I append 'a' to 'res' + Foreach 'b' in 'loop' + When I append 'b' to 'res' + Foreach 'c' in 'loop' + When I append 'c' to 'res' + EndForeach + EndForeach +EndForeach +Then print 'res' +EOF + save_output multiple_not_closed_foreach.out + assert_output '{"res":"aaabbabbaabbab"}' +} From 89bdcfde940df7f48c0f9c63ecee21f037dd0674 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Wed, 13 Nov 2024 19:00:28 +0100 Subject: [PATCH 08/27] fix: some fixes on transitions table skip also test for foreach zip, this will need a separated statement --- src/lua/zencode.lua | 10 ++++++---- test/zencode/foreach.bats | 9 ++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 00fd913b4..9ca2220d6 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -127,6 +127,7 @@ function ZEN:begin(new_heap) foreachif = 'foreach', endforeachif = 'endforeach', endforeachifforeach = 'endforeach', + endforeachforeach = 'endforeach', ifforeach = 'if', endifforeach = 'endif', } @@ -402,14 +403,15 @@ function ZEN:begin(new_heap) {name = 'enter_endoneif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif', 'whenifforeach'}, to = 'endoneif'}, {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeachif', 'foreachif', 'endoneif', 'whenifforeach'}, to = 'foreachif'}, {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'whenforeachif'}, - {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'endforeachif'}, + {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif', 'endoneif', 'endforeachforeachif'}, to = 'endforeachif'}, {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endifforeach', 'foreachif', 'whenifforeach', 'endoneif'}, to = 'ifforeach'}, {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endoneif', 'endforeachifforeach'}, to = 'whenifforeach'}, {name = 'enter_endifforeach', from = {'ifforeach', 'whenifforeach'}, to = 'endifforeach'}, {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach', 'whenforeach'}, to = 'foreach'}, {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endifforeach', 'endforeach'}, to = 'whenforeach'}, - {name = 'enter_endforeach', from = {'whenforeach', 'endifforeach', 'endforeach'}, to = 'endforeach'}, - {name = 'enter_endforeachforeach', from = {'whenforeach', 'endifforeach', 'endforeach'}, to = 'endforeach'}, + {name = 'enter_endforeach', from = {'whenforeach', 'endifforeach', 'endforeach', 'endforeachforeach'}, to = 'endforeach'}, + {name = 'enter_endforeachforeach', from = {'whenforeach', 'endifforeach', 'endforeach', 'endforeachforeach'}, to = 'endforeachforeach'}, + {name = 'enter_endforeachforeachif', from = {'whenforeachif', 'endifforeach', 'endforeach'}, to = 'endforeachforeachif'}, {name = 'enter_endforeachifforeach', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'endforeachifforeach'}, {name = 'enter_and', from = 'when', to = 'when'}, {name = 'enter_and', from = 'whenif', to = 'whenif'}, @@ -671,10 +673,10 @@ local function manage_foreach(stack, x) end if string.match(x.section, '^endforeach') then local info = stack.ITER[#stack.ITER] + if not info.end_line then info.end_line = x.linenum end if info.pos == 0 and info.end_line ~= x.linenum then return true end - if not info.end_line then info.end_line = x.linenum end if info.pos > 0 then info.pos = info.pos + 1 stack.next_instruction = info.jump diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index fdb99c3a9..532dc1cd5 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -199,6 +199,7 @@ EOF } @test "Zip foreach" { + skip cat << EOF | save_asset foreach_zip.data { "numbers": ["42","37","55","78"], @@ -255,20 +256,22 @@ Foreach 'x' in 'numbers' Foreach 'y' in 'numbers2' If I verify number 'x' is more than 'limit' If I verify number 'y' is more than 'limit' -When I move 'x' in 'floats' +When I copy 'x' in 'floats' endif EndForeach +EndForeach If I verify number 'zero' is less than 'limit' If I verify number 'zero' is less than 'limit' Foreach 'a' in 'numbers' Foreach 'b' in 'numbers2' -When I move 'b' in 'floats' +When I copy 'b' in 'floats' +EndForeach EndForeach endif Then print 'floats' EOF save_output "foreach_nestedif.out" - assert_output '{"floats":[5,6,10,9,8,7,6,5,4,3]}' + assert_output '{"floats":[5,5,5,5,5,5,6,6,6,6,6,6,7,7,7,7,7,7,8,8,8,8,8,8,10,9,8,7,6,5,4,3,2,1,0,-1]}' } @test "exit from foreach loop" { From 484291dd80866b9a0c71d4dc05007b5a95eb50f0 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 10:35:24 +0100 Subject: [PATCH 09/27] fix: improve checks on branch and loop ending --- src/lua/zencode.lua | 32 ++++++++++++++++---------------- test/zencode/foreach.bats | 30 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 9ca2220d6..9b385fe65 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -458,7 +458,18 @@ local function zencode_iscomment(b) return false end end - +local function end_branching_and_looping(type, info, prefixes, ln) + local n = fif(type == 'if', 'branching', 'loop') + if #info == 0 then + error('Ivalid '..n..' closing at line '..ln..': nothing to be closed', 2) + elseif prefixes[1] ~= type then + error('Invalid '..n..' closing at line '..ln..': need to close first the '..prefixes[1], 2) + end + local rm = table.remove(info) + if not rm[2] then + table.remove(prefixes, 1) + end +end function ZEN:parse(text) self:crumb() @@ -507,27 +518,16 @@ function ZEN:parse(text) end table.insert(looping, { self.linenum, already_foreach_prefix }) elseif prefix == 'endif' then + -- back compatibility if #branching == 0 then error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') end branching = {} table.remove(prefixes, 1) elseif prefix == 'endoneif' then - if #branching == 0 then - error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') - end - local removed = table.remove(branching) - if not removed[2] then - table.remove(prefixes, 1) - end - elseif prefix == 'endforeach' then - if #looping == 0 then - error('Ivalid loop closing at line '..self.linenum..': nothing to be closed') - end - local removed = table.remove(looping) - if not removed[2] then - table.remove(prefixes, 1) - end + end_branching_and_looping('if', branching, prefixes, self.linenum) + elseif prefix == 'endforeach' then + end_branching_and_looping('foreach', looping, prefixes, self.linenum) end if prefix == 'if' or prefix == 'foreach' then prefix = prefixes[1]..(prefixes[2] or '') diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index 532dc1cd5..319ed840c 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -401,6 +401,36 @@ EOF assert_line --partial 'Invalid looping opened at line 4, 5, 6 and never closed' } +@test "Detect the close of foreach before the if and viceversa" { + cat << EOF | save_asset error_closing_foreach_before_if.zen +Given nothing +When I create the 'string dictionary' named 'arr' +Foreach 'el' in 'arr' +If I verify 'el' is found +When done +# the following line are inverted +Endforeach +EndIf +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z error_closing_foreach_before_if.zen + assert_line --partial "Invalid loop closing at line 7: need to close first the if" + +cat << EOF | save_asset error_closing_if_before_foreach.zen +Given nothing +When I create the 'string dictionary' named 'arr' +If I verify 'arr' is found +Foreach 'el' in 'arr' +When done +# the following line are inverted +EndOneIf +Endforeach +Then print the data +EOF + run $ZENROOM_EXECUTABLE -z error_closing_if_before_foreach.zen + assert_line --partial "Invalid branching closing at line 7: need to close first the foreach" +} + @test "Nested foreach loop" { cat << EOF | zexe multiple_not_closed_foreach.zen Given nothing From 275c2c9b8b6e68cfe783dd1af08bca3c533b64cc Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 10:48:37 +0100 Subject: [PATCH 10/27] refactor: remove code duplication --- src/lua/zencode.lua | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 9b385fe65..02d65c068 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -458,6 +458,13 @@ local function zencode_iscomment(b) return false end end +local function enter_branching_and_looping(type, info, prefixes, ln) + local already_prefix = prefixes[1] == type + if not already_prefix then + table.insert(prefixes, 1, type) + end + table.insert(info, { ln, already_prefix }) +end local function end_branching_and_looping(type, info, prefixes, ln) local n = fif(type == 'if', 'branching', 'loop') if #info == 0 then @@ -506,17 +513,9 @@ function ZEN:parse(text) end if prefix == 'if' then - local already_if_prefix = prefixes[1] == 'if' - if not already_if_prefix then - table.insert(prefixes, 1, 'if') - end - table.insert(branching, { self.linenum, already_if_prefix }) + enter_branching_and_looping('if', branching, prefixes, self.linenum) elseif prefix == 'foreach' then - local already_foreach_prefix = prefixes[1] == 'foreach' - if not already_foreach_prefix then - table.insert(prefixes, 1, 'foreach') - end - table.insert(looping, { self.linenum, already_foreach_prefix }) + enter_branching_and_looping('foreach', looping, prefixes, self.linenum) elseif prefix == 'endif' then -- back compatibility if #branching == 0 then From 0a4ed67298bcd92ffc5a20a9c4a9f0d9d1389e29 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 11:01:36 +0100 Subject: [PATCH 11/27] refactor: remove more duplicated code --- src/lua/zencode.lua | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 02d65c068..8548dfa0a 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -477,6 +477,14 @@ local function end_branching_and_looping(type, info, prefixes, ln) table.remove(prefixes, 1) end end +local function check_open_branching_or_looping(type, info) + if #info == 0 then return end + local err_lines = {} + for _, v in pairs(info) do + table.insert(err_lines, v[1]) + end + error('Invalid '..type..' opened at line '..table.concat(err_lines, ', ')..' and never closed', 2) +end function ZEN:parse(text) self:crumb() @@ -587,20 +595,8 @@ function ZEN:parse(text) end -- continue end - if #branching > 0 then - local err_lines = {} - for _, v in pairs(branching) do - table.insert(err_lines, v[1]) - end - error("Invalid branching opened at line "..table.concat(err_lines, ", ").." and never closed") - end - if #looping > 0 then - local err_lines = {} - for _, v in pairs(looping) do - table.insert(err_lines, v[1]) - end - error("Invalid looping opened at line "..table.concat(err_lines, ", ").." and never closed") - end + check_open_branching_or_looping('branching', branching) + check_open_branching_or_looping('looping', looping) collectgarbage'collect' if res == true then return true From e402a2121f7890980fec9da3558d48b7ff49e2d4 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 11:17:21 +0100 Subject: [PATCH 12/27] fix: small optimization to manage_foreach --- src/lua/zencode.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 8548dfa0a..7612cb1ee 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -624,7 +624,6 @@ end local function manage_branching(stack, x) if string.match(x.section, '^if') then -- xxx("START conditional execution: "..x.source, 2) - -- if stack.branch == 0 then stack.branch_valid = true end stack.branch = stack.branch+1 if stack.branch_valid == stack.branch-1 then stack.branch_valid = stack.branch_valid+1 @@ -659,31 +658,32 @@ end -- TODO(optimization): introduce a second jump to skip all -- statements in the foreach in the last iteration local function manage_foreach(stack, x) + local last_iter = stack.ITER[#stack.ITER] if string.match(x.section, '^foreach') and - ( #stack.ITER == 0 or - ( stack.ITER[#stack.ITER].jump ~= stack.current_instruction and - stack.ITER[#stack.ITER].pos ~=0 )) then + ( not last_iter or + ( last_iter.jump ~= stack.current_instruction and + last_iter.pos ~=0 )) then table.insert(stack.ITER, {jump = stack.current_instruction, pos = 1}) return false end if string.match(x.section, '^endforeach') then - local info = stack.ITER[#stack.ITER] - if not info.end_line then info.end_line = x.linenum end - if info.pos == 0 and info.end_line ~= x.linenum then + if not last_iter.end_line then last_iter.end_line = x.linenum end + if last_iter.pos == 0 and last_iter.end_line ~= x.linenum then return true end - if info.pos > 0 then - info.pos = info.pos + 1 - stack.next_instruction = info.jump + if last_iter.pos > 0 then + last_iter.pos = last_iter.pos + 1 + stack.next_instruction = last_iter.jump return true else table.remove(stack.ITER) + last_iter = stack.ITER[#stack.ITER] end end - if #stack.ITER ~= 0 and stack.ITER[#stack.ITER].pos >= MAXITER then + if last_iter and last_iter.pos >= MAXITER then error("Limit of iterations reached: " .. MAXITER) end - return #stack.ITER ~= 0 and stack.ITER[#stack.ITER].pos == 0 + return last_iter and last_iter.pos == 0 end function ZEN:run() From c57eed4b8e2808d4e88851e29eed28251a7e36c9 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 11:43:44 +0100 Subject: [PATCH 13/27] feat: manage_foreach optimization skip all statements in the last (already not valid) loop --- src/lua/zencode.lua | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 7612cb1ee..f1ca8b413 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -655,8 +655,6 @@ end -- return true: caller skip execution and go to ::continue:: -- return false: execute statement --- TODO(optimization): introduce a second jump to skip all --- statements in the foreach in the last iteration local function manage_foreach(stack, x) local last_iter = stack.ITER[#stack.ITER] if string.match(x.section, '^foreach') and @@ -667,8 +665,8 @@ local function manage_foreach(stack, x) return false end if string.match(x.section, '^endforeach') then - if not last_iter.end_line then last_iter.end_line = x.linenum end - if last_iter.pos == 0 and last_iter.end_line ~= x.linenum then + if not last_iter.end_id then last_iter.end_id = x.id end + if last_iter.pos == 0 and last_iter.end_id ~= x.id then return true end if last_iter.pos > 0 then @@ -680,8 +678,14 @@ local function manage_foreach(stack, x) last_iter = stack.ITER[#stack.ITER] end end - if last_iter and last_iter.pos >= MAXITER then - error("Limit of iterations reached: " .. MAXITER) + if last_iter then + if last_iter.pos >= MAXITER then + error("Limit of iterations reached: " .. MAXITER) + -- skip all statements on last (not valid) loop + elseif last_iter.pos == 0 and last_iter.end_id then + stack.next_instruction = last_iter.end_id + return true + end end return last_iter and last_iter.pos == 0 end From b8c5848bd2e8c6265af1391c5e36c2e80f65c524 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 15:28:32 +0100 Subject: [PATCH 14/27] feat: endif ends only the latest opend if MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚠ This is a breaking change! ⚠ Try also to simplify end events, to be see if this is a good choice or bring in some bugs! Time for testing --- src/lua/zencode.lua | 91 ++++++++++++++----------------------- test/zencode/array.bats | 5 ++ test/zencode/branching.bats | 64 ++++++++------------------ test/zencode/foreach.bats | 6 ++- test/zencode/kyber.bats | 3 +- test/zencode/mlkem512.bats | 1 + test/zencode/ntrup.bats | 3 +- 7 files changed, 66 insertions(+), 107 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index f1ca8b413..dbe0a64be 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -52,7 +52,6 @@ ZEN = { when_steps = {}, if_steps = {}, endif_steps = { endif = function() return end }, --nop - endoneif_steps = { endoneif = function() return end }, --nop foreach_steps = {}, endforeach_steps = { endforeach = function() return end }, --nop then_steps = {}, @@ -125,11 +124,7 @@ function ZEN:begin(new_heap) whenforeachif = 'when', whenifforeach = 'when', foreachif = 'foreach', - endforeachif = 'endforeach', - endforeachifforeach = 'endforeach', - endforeachforeach = 'endforeach', - ifforeach = 'if', - endifforeach = 'endif', + ifforeach = 'if' } local current = self.current local index = translate[current] or current @@ -387,44 +382,33 @@ function ZEN:begin(new_heap) callbacks.onwhenforeach = set_sentence callbacks.onforeachif = set_sentence callbacks.onwhenforeachif = set_sentence - callbacks.onendforeachif = set_sentence callbacks.onifforeach = set_sentence callbacks.onwhenifforeach = set_sentence - callbacks.onendifforeach = set_sentence - callbacks.onendoneif = set_sentence - callbacks.onendforeachifforeach = set_sentence - callbacks.onendforeachforeach = set_sentence local extra_events = { - {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endoneif', 'endforeach'}, to = 'when'}, - {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endoneif', 'endforeach', 'whenif', 'thenif', 'endforeachif', 'foreachif'}, to = 'if'}, - {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'whenif'}, - {name = 'enter_thenif', from = {'if', 'whenif', 'thenif', 'endoneif'}, to = 'thenif'}, - {name = 'enter_endif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif'}, to = 'endif'}, - {name = 'enter_endoneif', from = {'whenif', 'thenif', 'endforeachif', 'endoneif', 'whenifforeach'}, to = 'endoneif'}, - {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeachif', 'foreachif', 'endoneif', 'whenifforeach'}, to = 'foreachif'}, - {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'whenforeachif'}, - {name = 'enter_endforeachif', from = {'foreachif', 'whenforeachif', 'endoneif', 'endforeachforeachif'}, to = 'endforeachif'}, - {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endifforeach', 'foreachif', 'whenifforeach', 'endoneif'}, to = 'ifforeach'}, - {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endoneif', 'endforeachifforeach'}, to = 'whenifforeach'}, - {name = 'enter_endifforeach', from = {'ifforeach', 'whenifforeach'}, to = 'endifforeach'}, + {name = 'enter_when', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'when'}, + {name = 'enter_then', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'then'}, + {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endforeach', 'whenif', 'thenif', 'endforeach', 'foreachif'}, to = 'if'}, + {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeach', 'endif'}, to = 'whenif'}, + {name = 'enter_thenif', from = {'if', 'whenif', 'thenif'}, to = 'thenif'}, + {name = 'enter_endif', from = {'whenif', 'whenifforeach', 'thenif', 'endforeach', 'endif'}, to = 'endif'}, {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach', 'whenforeach'}, to = 'foreach'}, - {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endifforeach', 'endforeach'}, to = 'whenforeach'}, - {name = 'enter_endforeach', from = {'whenforeach', 'endifforeach', 'endforeach', 'endforeachforeach'}, to = 'endforeach'}, - {name = 'enter_endforeachforeach', from = {'whenforeach', 'endifforeach', 'endforeach', 'endforeachforeach'}, to = 'endforeachforeach'}, - {name = 'enter_endforeachforeachif', from = {'whenforeachif', 'endifforeach', 'endforeach'}, to = 'endforeachforeachif'}, - {name = 'enter_endforeachifforeach', from = {'foreachif', 'whenforeachif', 'endoneif'}, to = 'endforeachifforeach'}, + {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endif', 'endforeach'}, to = 'whenforeach'}, + {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeach', 'foreachif', 'whenifforeach', 'endif'}, to = 'foreachif'}, + {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endif'}, to = 'whenforeachif'}, + {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endif', 'foreachif', 'whenifforeach'}, to = 'ifforeach'}, + {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endforeach', 'endif'}, to = 'whenifforeach'}, + {name = 'enter_endforeach', from = {'whenforeach', 'whenforeachif', 'endif', 'endforeach'}, to = 'endforeach'}, {name = 'enter_and', from = 'when', to = 'when'}, + {name = 'enter_and', from = 'then', to = 'then'}, + {name = 'enter_and', from = 'if', to = 'if'}, {name = 'enter_and', from = 'whenif', to = 'whenif'}, {name = 'enter_and', from = 'thenif', to = 'thenif'}, - {name = 'enter_and', from = 'if', to = 'if'}, {name = 'enter_and', from = 'foreach', to = 'foreach'}, - {name = 'enter_and', from = 'ifforeach', to = 'ifforeach'}, - {name = 'enter_and', from = 'foreachif', to = 'foreachif'}, {name = 'enter_and', from = 'whenforeach', to = 'whenforeach'}, - {name = 'enter_and', from = 'whenifforeach', to = 'whenifforeach'}, + {name = 'enter_and', from = 'foreachif', to = 'foreachif'}, {name = 'enter_and', from = 'whenforeachif', to = 'whenforeachif'}, - {name = 'enter_then', from = {'given', 'when', 'then', 'endif', 'endforeach', 'endoneif'}, to = 'then'}, - {name = 'enter_and', from = 'then', to = 'then'}, + {name = 'enter_and', from = 'ifforeach', to = 'ifforeach'}, + {name = 'enter_and', from = 'whenifforeach', to = 'whenifforeach'}, } for _,v in pairs(extra_events) do table.insert(events, v) end end @@ -464,7 +448,9 @@ local function enter_branching_and_looping(type, info, prefixes, ln) table.insert(prefixes, 1, type) end table.insert(info, { ln, already_prefix }) + return prefixes[1]..(prefixes[2] or '') end + local function end_branching_and_looping(type, info, prefixes, ln) local n = fif(type == 'if', 'branching', 'loop') if #info == 0 then @@ -496,6 +482,7 @@ function ZEN:parse(text) local looping = {} local prefixes = {} local parse_prefix = parse_prefix -- optimization + local last_prefix self.linenum = 0 local res = fif(CONF.parser.strict_parse, true, { ignored={}, invalid={} }) for line in zencode_newline_iter(text) do @@ -520,28 +507,24 @@ function ZEN:parse(text) break -- stop parsing after given block end - if prefix == 'if' then - enter_branching_and_looping('if', branching, prefixes, self.linenum) - elseif prefix == 'foreach' then - enter_branching_and_looping('foreach', looping, prefixes, self.linenum) - elseif prefix == 'endif' then - -- back compatibility - if #branching == 0 then - error('Ivalid branching closing at line '..self.linenum..': nothing to be closed') - end - branching = {} - table.remove(prefixes, 1) - elseif prefix == 'endoneif' then + if prefix == 'if' or (prefix == 'and' and last_prefix == 'if') then + last_prefix = 'if' + prefix = enter_branching_and_looping('if', branching, prefixes, self.linenum) + elseif prefix == 'foreach' or (prefix == 'and' and last_prefix == 'foreach') then + last_prefix = 'foreach' + prefix = enter_branching_and_looping('foreach', looping, prefixes, self.linenum) + elseif prefix == 'endif' then + last_prefix = prefix end_branching_and_looping('if', branching, prefixes, self.linenum) elseif prefix == 'endforeach' then + last_prefix = prefix end_branching_and_looping('foreach', looping, prefixes, self.linenum) + else + last_prefix = prefix end - if prefix == 'if' or prefix == 'foreach' then - prefix = prefixes[1]..(prefixes[2] or '') - elseif prefix == 'when' or prefix == 'then' - or prefix == 'endif' or prefix == 'endforeach' then + if prefix == 'when' or prefix == 'then' then prefix = prefix .. (prefixes[1] or '').. (prefixes[2] or '') - end + end if prefix == "when" or prefix == "if" or prefix == "foreach" then ZEN.phase = "w" @@ -632,12 +615,6 @@ local function manage_branching(stack, x) return true end if string.match(x.section, '^endif') then - --xxx("END conditional execution: "..x.source, 2) - stack.branch = 0 - stack.branch_valid = 0 - return true - end - if string.match(x.section, '^endoneif') then --xxx("END conditional execution: "..x.source, 2) if stack.branch_valid == stack.branch then stack.branch_valid = stack.branch_valid-1 diff --git a/test/zencode/array.bats b/test/zencode/array.bats index d577cd656..e563aba4e 100755 --- a/test/zencode/array.bats +++ b/test/zencode/array.bats @@ -685,6 +685,9 @@ If I verify the elements in 'nested arr' are all equal If I verify the elements in 'empty dict' are all equal Then print string 'OK' EndIf +EndIf +EndIf +EndIf EOF save_output "compare-equal.json" assert_output '{"output":["OK"]}' @@ -717,6 +720,8 @@ If I verify the elements in 'dict' are not all equal If I verify the elements in 'nested arr' are not all equal Then print string 'OK' EndIf +EndIf +EndIf EOF save_output "compare-not-equal.json" assert_output '{"output":["OK"]}' diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index 42d814d56..1ab64b55a 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -45,6 +45,7 @@ If I verify 'number_lower' is equal to 'number_higher' When I create the random 'random_this_is_impossible' Then print string 'the conditions can never be satisfied' Endif +EndIf # You can also check if an object exists at a certain point of the execution, with the statement: # If I verify 'objectName' is found @@ -129,6 +130,7 @@ and I verify 'right' is equal to 'right' Then print string 'right is higher' and print string 'and I am right' endif +endif If I verify number 'left' is more than 'right' Then print string 'left is higher' @@ -663,16 +665,16 @@ If I verify 'grants' is found in 'external_qr_content' If I verify 'authorization_server' is found in 'authorization_code' When I pickup from path 'authorization_code.authorization_server' Then print the 'authorization_server' - EndOneIf - EndOneIf + EndIf + EndIf If I verify 'urn:ietf:params:oauth:grant-type:pre-authorized_code' is found in 'grants' When I pickup from path 'grants.urn:ietf:params:oauth:grant-type:pre-authorized_code' If I verify 'pre-authorized_code' is found in 'urn:ietf:params:oauth:grant-type:pre-authorized_code' When I pickup from path 'urn:ietf:params:oauth:grant-type:pre-authorized_code.pre-authorized_code' Then print the 'pre-authorized_code' - EndOneIf - EndOneIf -EndOneIf + EndIf + EndIf +EndIf When I create copy of element '1' from array 'credential_configuration_ids' When I rename the 'copy' to 'credential_configuration_id' @@ -689,7 +691,7 @@ EOF assert_output '{"credential_configuration_id":"openbadge_credential","credential_issuer":"https://ministerie-agent.dev.impierce.com/.well-known/openid-credential-issuer","pre-authorized_code":"ebb90f2db21a4708b93217a686f91e134b370b350aae18dc25a382507b141c13"}' } -@test "Invalid signle endif and endoneif" { +@test "Invalid signle endif" { cat << EOF | save_asset invalid_single_endif.zen Given nothing EndIf @@ -697,14 +699,6 @@ Then print the data EOF run $ZENROOM_EXECUTABLE -z invalid_single_endif.zen assert_line --partial "Ivalid branching closing at line 2: nothing to be closed" - - cat << EOF | save_asset invalid_single_endoneif.zen -Given nothing -EndOneIf -Then print the data -EOF - run $ZENROOM_EXECUTABLE -z invalid_single_endoneif.zen - assert_line --partial "Ivalid branching closing at line 2: nothing to be closed" } @test "Invalid exit from an already closed branch" { @@ -714,31 +708,9 @@ When I set 'my_string' to 'test' as 'string' If I verify 'my_string' is found Then I print 'my string' EndIf -EndOneIf -EOF - run $ZENROOM_EXECUTABLE -z endif_on_closed_branch.zen - assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" - - cat << EOF | save_asset endoneif_on_closed_branch.zen -Given nothing -When I set 'my_string' to 'test' as 'string' -If I verify 'my_string' is found -Then I print 'my string' -EndOneIf EndIf EOF - run $ZENROOM_EXECUTABLE -z endoneif_on_closed_branch.zen - assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" - - cat << EOF | save_asset endoneif_on_closed_branch_2.zen -Given nothing -When I set 'my_string' to 'test' as 'string' -If I verify 'my_string' is found -Then I print 'my string' -EndOneIf -EndOneIf -EOF - run $ZENROOM_EXECUTABLE -z endoneif_on_closed_branch_2.zen + run $ZENROOM_EXECUTABLE -z endif_on_closed_branch.zen assert_line --partial "Ivalid branching closing at line 6: nothing to be closed" } @@ -774,7 +746,7 @@ When I create the 'string array' named 'res_foreach' If I verify 'my_array' is found If I verify size of 'my_array' is more than 'one' Then print the string 'long array' - EndOneIf + EndIf Foreach 'el' in 'my_array' If I verify 'data' is found in 'el' When I pickup from path 'el.data' @@ -784,12 +756,12 @@ If I verify 'my_array' is found When I copy 'e' in 'res_foreach' EndForeach When I remove 'other_key' - EndOneIf + EndIf If I verify 'key' is found in 'data' When I move 'key' from 'data' in 'res' - EndOneIf + EndIf When I remove 'data' - EndOneIf + EndIf If I verify 'other_data' is found in 'el' When I pickup from path 'el.other_data' If I verify 'other_key' is found in 'other_data' @@ -797,18 +769,18 @@ If I verify 'my_array' is found Foreach 'e' in 'other_key' If I verify 'e' is equal to 'filter' When I copy 'e' in 'res_foreach' - EndOneIf + EndIf When done EndForeach When I remove 'other_key' - EndOneIf + EndIf If I verify 'key' is found in 'other_data' When I move 'key' from 'other_data' in 'res' - EndOneIf + EndIf When I remove 'other_data' - EndOneIf + EndIf EndForeach -EndOneIf +EndIf Then print 'res' Then print 'res_foreach' diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index 319ed840c..53ae62f9e 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -258,6 +258,7 @@ If I verify number 'x' is more than 'limit' If I verify number 'y' is more than 'limit' When I copy 'x' in 'floats' endif +endif EndForeach EndForeach If I verify number 'zero' is less than 'limit' @@ -268,10 +269,11 @@ When I copy 'b' in 'floats' EndForeach EndForeach endif +endif Then print 'floats' EOF save_output "foreach_nestedif.out" - assert_output '{"floats":[5,5,5,5,5,5,6,6,6,6,6,6,7,7,7,7,7,7,8,8,8,8,8,8,10,9,8,7,6,5,4,3,2,1,0,-1]}' + assert_output '{"floats":[5,5,5,5,5,5,6,6,6,6,6,6,7,7,7,7,7,7,8,8,8,8,8,8,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1,10,9,8,7,6,5,4,3,2,1,0,-1]}' } @test "exit from foreach loop" { @@ -423,7 +425,7 @@ If I verify 'arr' is found Foreach 'el' in 'arr' When done # the following line are inverted -EndOneIf +EndIf Endforeach Then print the data EOF diff --git a/test/zencode/kyber.bats b/test/zencode/kyber.bats index 267e56541..b928f469a 100644 --- a/test/zencode/kyber.bats +++ b/test/zencode/kyber.bats @@ -182,7 +182,8 @@ and I create the kyber secret from 'kyber ciphertext' If I verify 'Bob kyber secret' is equal to 'kyber secret' If I verify 'Dave message' is equal to 'text' Then print string 'Success!!!' -Endif +EndIf +EndIf EOF save_output 'Carl_dec.out' assert_output '{"output":["Success!!!"]}' diff --git a/test/zencode/mlkem512.bats b/test/zencode/mlkem512.bats index 24f8a5093..b6f512007 100644 --- a/test/zencode/mlkem512.bats +++ b/test/zencode/mlkem512.bats @@ -183,6 +183,7 @@ If I verify 'Bob mlkem512 secret' is equal to 'mlkem512 secret' If I verify 'Dave message' is equal to 'text' Then print string 'Success!!!' Endif +Endif EOF save_output 'Carl_ECDH_mlkem512_dec.out' assert_output '{"output":["Success!!!"]}' diff --git a/test/zencode/ntrup.bats b/test/zencode/ntrup.bats index b94b4cdff..c25ee904d 100644 --- a/test/zencode/ntrup.bats +++ b/test/zencode/ntrup.bats @@ -170,7 +170,8 @@ and I create the kyber secret from 'kyber ciphertext' If I verify 'Bob ntrup secret' is equal to 'ntrup secret' If I verify 'Bob kyber secret' is equal to 'kyber secret' Then print string 'Success!!!' -Endif +EndIf +EndIf EOF save_output 'Carl_dec.out' assert_output '{"output":["Success!!!"]}' From 7d4943858790e4f32166f20af41abe5aed03bf9d Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 16:50:35 +0100 Subject: [PATCH 15/27] fix: manage loops over empty array --- src/lua/zencode.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index dbe0a64be..1930e7fad 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -389,13 +389,13 @@ function ZEN:begin(new_heap) {name = 'enter_then', from = {'given', 'when', 'then', 'endif', 'endforeach'}, to = 'then'}, {name = 'enter_if', from = {'if', 'given', 'when', 'then', 'endif', 'endforeach', 'whenif', 'thenif', 'endforeach', 'foreachif'}, to = 'if'}, {name = 'enter_whenif', from = {'if', 'whenif', 'thenif', 'endforeach', 'endif'}, to = 'whenif'}, - {name = 'enter_thenif', from = {'if', 'whenif', 'thenif'}, to = 'thenif'}, + {name = 'enter_thenif', from = {'if', 'whenif', 'thenif', 'endforeach', 'endif'}, to = 'thenif'}, {name = 'enter_endif', from = {'whenif', 'whenifforeach', 'thenif', 'endforeach', 'endif'}, to = 'endif'}, {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach', 'whenforeach'}, to = 'foreach'}, {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endif', 'endforeach'}, to = 'whenforeach'}, - {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeach', 'foreachif', 'whenifforeach', 'endif'}, to = 'foreachif'}, - {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endif'}, to = 'whenforeachif'}, - {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'endif', 'foreachif', 'whenifforeach'}, to = 'ifforeach'}, + {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeach', 'foreachif', 'ifforeach', 'whenifforeach', 'endif'}, to = 'foreachif'}, + {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endif', 'endforeach'}, to = 'whenforeachif'}, + {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'whenifforeach', 'foreachif', 'whenforeachif', 'endif' }, to = 'ifforeach'}, {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endforeach', 'endif'}, to = 'whenifforeach'}, {name = 'enter_endforeach', from = {'whenforeach', 'whenforeachif', 'endif', 'endforeach'}, to = 'endforeach'}, {name = 'enter_and', from = 'when', to = 'when'}, @@ -634,12 +634,15 @@ end -- return false: execute statement local function manage_foreach(stack, x) local last_iter = stack.ITER[#stack.ITER] - if string.match(x.section, '^foreach') and - ( not last_iter or - ( last_iter.jump ~= stack.current_instruction and - last_iter.pos ~=0 )) then - table.insert(stack.ITER, {jump = stack.current_instruction, pos = 1}) - return false + if string.match(x.section, '^foreach') then + if not last_iter then + table.insert(stack.ITER, {jump = stack.current_instruction, pos = 1 }) + return false + elseif last_iter.jump ~= stack.current_instruction then + local not_valid_parent_loop = last_iter.pos == 0 + table.insert(stack.ITER, {jump = stack.current_instruction, pos = fif(not_valid_parent_loop, 0, 1)}) + return not_valid_parent_loop + end end if string.match(x.section, '^endforeach') then if not last_iter.end_id then last_iter.end_id = x.id end From 065421cf0e0f6b3431ec129c2eaeb9327c9dcd08 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 16:52:35 +0100 Subject: [PATCH 16/27] test: one more test --- test/zencode/branching.bats | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index 1ab64b55a..4df2c4924 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -788,3 +788,34 @@ EOF save_output 'nested_if_in_foreach.out.json' assert_output '{"output":["long_array"],"res":["value"],"res_foreach":["other_value_2"]}' } + +@test "Some nested branching and loop tests" { +cat << EOF | zexe nested_1.zen +Given nothing +When I create the 'string array' named 'arr' +When I create the 'string array' named 'res' +If I verify 'arr' is found + When done + Foreach 'el1' in 'arr' + -- this statements are skipped since arr is empty + When I set 'pippo' to 'pippo' as 'string' + and I move 'pippo' in 'res' + If I verify 'el1' is found + Foreach 'el2' in 'el1' + Foreach 'el3' in 'el2' + If I verify 'el3' is found + When done + EndIf + Endforeach + When done + Endforeach + EndIf + When done + EndForeach + Then print the 'arr' +EndIf +Then print 'res' +EOF + save_output nested_1.out.json + assert_output '{"arr":[],"res":[]}' +} From d2d274ee0f8a3e96252e133aab7309568a659899 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Thu, 14 Nov 2024 16:58:13 +0100 Subject: [PATCH 17/27] test: fix comment char --- test/zencode/branching.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index 4df2c4924..7373fab53 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -797,7 +797,7 @@ When I create the 'string array' named 'res' If I verify 'arr' is found When done Foreach 'el1' in 'arr' - -- this statements are skipped since arr is empty + # this statements are skipped since arr is empty When I set 'pippo' to 'pippo' as 'string' and I move 'pippo' in 'res' If I verify 'el1' is found From b1c455b45dc476f65d0191706d0a7e2f2e7d86f2 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Fri, 15 Nov 2024 11:40:16 +0100 Subject: [PATCH 18/27] fix: correctly propagate errors in if branching, only the errors in the if condition will not make the contract to fail fix also some more transitions --- src/lua/zencode.lua | 6 ++++-- src/lua/zencode_debug.lua | 26 +++++++++++--------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 1930e7fad..7d7a20de9 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -393,9 +393,9 @@ function ZEN:begin(new_heap) {name = 'enter_endif', from = {'whenif', 'whenifforeach', 'thenif', 'endforeach', 'endif'}, to = 'endif'}, {name = 'enter_foreach', from = {'given', 'when', 'endif', 'foreach', 'endforeach', 'whenforeach'}, to = 'foreach'}, {name = 'enter_whenforeach', from = {'foreach', 'whenforeach', 'endif', 'endforeach'}, to = 'whenforeach'}, - {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeach', 'foreachif', 'ifforeach', 'whenifforeach', 'endif'}, to = 'foreachif'}, + {name = 'enter_foreachif', from = {'if', 'whenif', 'endforeach', 'foreachif', 'whenforeachif', 'ifforeach', 'whenifforeach', 'endif'}, to = 'foreachif'}, {name = 'enter_whenforeachif', from = {'foreachif', 'whenforeachif', 'endif', 'endforeach'}, to = 'whenforeachif'}, - {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'whenifforeach', 'foreachif', 'whenforeachif', 'endif' }, to = 'ifforeach'}, + {name = 'enter_ifforeach', from = {'foreach', 'whenforeach', 'ifforeach', 'whenifforeach', 'foreachif', 'whenforeachif', 'endif', 'endforeach' }, to = 'ifforeach'}, {name = 'enter_whenifforeach', from = {'ifforeach', 'whenifforeach', 'endforeach', 'endif'}, to = 'whenifforeach'}, {name = 'enter_endforeach', from = {'whenforeach', 'whenforeachif', 'endif', 'endforeach'}, to = 'endforeach'}, {name = 'enter_and', from = 'when', to = 'when'}, @@ -605,7 +605,9 @@ end -- return true: caller skip execution and go to ::continue:: -- return false: execute statement local function manage_branching(stack, x) + stack.branch_condition = nil if string.match(x.section, '^if') then + stack.branch_condition = true -- xxx("START conditional execution: "..x.source, 2) stack.branch = stack.branch+1 if stack.branch_valid == stack.branch-1 then diff --git a/src/lua/zencode_debug.lua b/src/lua/zencode_debug.lua index c9feb6832..79979bd59 100644 --- a/src/lua/zencode_debug.lua +++ b/src/lua/zencode_debug.lua @@ -114,21 +114,17 @@ end zencode_assert = function(condition, errmsg) - if condition then - return true - else - ZEN.branch_valid = fif(ZEN.branch_valid > 0, ZEN.branch_valid - 1, 0) - end - -- in conditional branching zencode_assert doesn't quit - if ZEN.branch ~= 0 then - table.insert(traceback, errmsg) - else - -- ZEN.debug() -- prints all data in memory - -- table.insert(traceback, '[!] '..errmsg) - ZEN.OK = false - exitcode(1) - error(errmsg, 3) - end + if condition then return true end + if ZEN.branch_condition then + ZEN.branch_valid = ZEN.branch_valid - 1 + table.insert(traceback, errmsg) + return true + end + -- ZEN.debug() -- prints all data in memory + -- table.insert(traceback, '[!] '..errmsg) + ZEN.OK = false + exitcode(1) + error(errmsg, 3) end function ZEN:debug() From 7f14b9862bb27e9e1b27d5ff85278601b9653955 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Fri, 15 Nov 2024 14:07:17 +0100 Subject: [PATCH 19/27] test: add some educational for loop examples found around (like reverse list, prime numbers, factorial) this can maybe serve as docs if we want to have a page in the docs where we challenge people with zencode exercises --- build/meson.build | 2 +- test/zencode/educational.bats | 312 ++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 test/zencode/educational.bats diff --git a/build/meson.build b/build/meson.build index bf0f39096..c0832bbff 100644 --- a/build/meson.build +++ b/build/meson.build @@ -64,7 +64,7 @@ tests = [ 'bitcoin', 'branching', 'cookbook_debug', 'cookbook_dictionaries', 'cookbook_ecdh', 'cookbook_ecdh_encrypt_json', 'cookbook_given', 'cookbook_intro', 'cookbook_then', 'cookbook_when', 'credential', -'dictionary', 'dp3t', 'ecdh', 'float', 'foreach', 'fsp', +'dictionary', 'dp3t', 'ecdh', 'educational', 'float', 'foreach', 'fsp', 'generic_bbs', 'generic_dilithium', 'generic_ecdh', 'generic_eddsa', 'generic_es256', 'generic_mldsa44', 'generic_schnorr', 'given', 'hash', 'http', 'keys', 'kyber', 'mlkem512', 'ntrup', 'numbers', diff --git a/test/zencode/educational.bats b/test/zencode/educational.bats new file mode 100644 index 000000000..aa925af80 --- /dev/null +++ b/test/zencode/educational.bats @@ -0,0 +1,312 @@ +load ../bats_setup +load ../bats_zencode +SUBDOC=educational + +@test "Foreach: display number divisible by 5 from a list following also other conditions" { + cat << EOF | save_asset foreach_divisible_by_five.data.json +{ + "numbers": [12, 75, 150, 180, 145, 525, 50] +} +EOF + cat << EOF | save_asset foreach_divisible_by_five.keys.json +{ + "0": 0, + "5": 5, + "150": 150, + "500": 500 +} +EOF + cat << EOF | zexe foreach_divisible_by_five.zen foreach_divisible_by_five.data.json foreach_divisible_by_five.keys.json +# Problem: +# display only those numbers from a list that satisfy the following conditions +# 1. The number must be divisible by five +# 2. If the number is greater than 150, then skip it and move to the following number +# 3. If the number is greater than 500, then stop the loop + +Given I have a 'number array' named 'numbers' +Given I have a 'number' named '0' +Given I have a 'number' named '5' +Given I have a 'number' named '150' +Given I have a 'number' named '500' + +When I create the 'number array' named 'res' + +Foreach 'num' in 'numbers' + # point 3 + If I verify number 'num' is more than '500' + When I break foreach + EndIf + # point 2 + If I verify number 'num' is less or equal than '150' + When I create the result of 'num' % '5' + # point 1 + If I verify 'result' is equal to '0' + When I copy 'num' in 'res' + EndIf + When I remove 'result' + EndIf +EndForeach + +Then print the 'res' +EOF + save_output foreach_divisible_by_five.out.json + assert_output '{"res":[75,150,145]}' +} + + +@test "Foreach: list in reverse order" { + cat << EOF | save_asset foreach_reverse_order.data.json +{ + "list": [10, 20, 30, 40, 50] +} +EOF + cat << EOF | save_asset foreach_reverse_order.keys.json +{ + "0": 0, + "1": 1 +} +EOF + cat << EOF | zexe foreach_reverse_order.zen foreach_reverse_order.data.json foreach_reverse_order.keys.json +# Problem: +# reverse the list in input + +Given I have a 'number array' named 'list' +Given I have a 'number' named '0' +Given I have a 'number' named '1' + +When I create the 'number array' named 'res' + +# limit of iteration +When I create the size of 'list' +and I create the result of 'size' - '1' +and I rename 'result' to 'limit' + +Foreach 'i' in sequence from '0' to 'limit' with step '1' + When I create the result of 'size' - 'i' + and I create the copy of element 'result' from array 'list' + and I move 'copy' in 'res' + and I remove 'result' +EndForeach + +Then print 'res' +EOF + save_output foreach_reverse_order.out.json + assert_output '{"res":[50,40,30,20,10]}' +} + +@test "Foreach: naive list of prime number within a range" { + cat << EOF | save_asset foreach_naive_prime_in_range.data.json +{ + "start": 25, + "end": 100 +} +EOF + cat << EOF | save_asset foreach_naive_prime_in_range.keys.json +{ + "0": 0, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6 +} +EOF + cat << EOF | zexe foreach_naive_prime_in_range.zen foreach_naive_prime_in_range.data.json foreach_naive_prime_in_range.keys.json +# Problem: +# print all prime numbers in a range +# Note: +# this is just for educational purpose, there are better method that are not just check if the +# the number is divisible for all the numbers in the range (2, n/2) + +Given I have a 'number' named 'start' +Given I have a 'number' named 'end' +Given I have a 'number' named '0' +Given I have a 'number' named '1' +Given I have a 'number' named '2' +Given I have a 'number' named '3' +Given I have a 'number' named '4' +Given I have a 'number' named '5' +Given I have a 'number' named '6' + +When I create the 'number array' named 'res' + +If I verify number 'start' is more than 'end' + When I exit with error message 'star must be less than end' +EndIf + +# In zenroom when we try to do a foreach where the start is less than the end +# an error is thrown, thus since the inner loop start from 2, the end, that is the +# floor of n/2, should be at least 3 thus n should start from 6. +# Thus this if here take into considerations the prime number before 6 and if they +# are in the range, they are added +If I verify number 'end' is more or equal than '2' + If I verify number 'start' is less or equal than '2' + When I copy '2' in 'res' + EndIf +EndIf +If I verify number 'end' is more or equal than '3' + If I verify number 'start' is less or equal than '3' + When I copy '3' in 'res' + EndIf +EndIf +If I verify number 'end' is more or equal than '5' + If I verify number 'start' is less or equal than '5' + When I copy '5' in 'res' + EndIf +EndIf + +# prime numbers grater than 6 +If I verify number 'end' is more than '6' + If I verify number 'start' is less than '6' + When I remove 'start' + and I copy '6' to 'start' + EndIf + Foreach 'i' in sequence from 'start' to 'end' with step '1' + When I create the result of 'i' % '2' + When I rename 'result' to 'is_even' + If I verify 'is_even' is equal to '0' + When I set 'not_prime' to 'true' as 'string' + EndIf + If I verify 'is_even' is equal to '1' + When I create the result of 'i' - '1' + When I rename 'result' to 'even' + When I create the result of 'even' / '2' + When I remove 'even' + When I rename the 'result' to 'limit' + Foreach 'j' in sequence from '2' to 'limit' with step '1' + When I create the result of 'i' % 'j' + If I verify 'result' is equal to '0' + When I set 'not_prime' to 'true' as 'string' + When I remove 'result' + When I break the foreach + EndIf + When I remove 'result' + EndForeach + When I remove 'limit' + EndIf + If I verify 'not_prime' is not found + When I copy 'i' in 'res' + EndIf + If I verify 'not_prime' is found + When I remove 'not_prime' + EndIf + When I remove 'is_even' + EndForeach +EndIf + +Then print 'res' +EOF + save_output foreach_prime_in_range.out.json + assert_output '{"res":[29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]}' +} + +@test "Foreach: less naive list of prime number till a number" { + cat << EOF | save_asset foreach_less_naive_prime_till_number.data.json +{ + "end": 100 +} +EOF + cat << EOF | save_asset foreach_less_naive_prime_till_number.keys.json +{ + "0": 0, + "1": 1, + "2": 2 +} +EOF + cat << EOF | zexe foreach_less_naive_prime_till_number.zen foreach_less_naive_prime_till_number.data.json foreach_less_naive_prime_till_number.keys.json +# Problem: +# print all prime numbers till a certain number 'end' +# Note: +# The total complexity of this algorithm is O((n*sqrt(n))/logn), we are checking only for prime divisors till sqrt(n) + +Given I have a 'number' named 'end' + +Given I have a 'number' named '0' +Given I have a 'number' named '1' +Given I have a 'number' named '2' + +When I create the 'number array' named 'res' + +If I verify number 'end' is less or equal than '2' + When I exit with error message 'end should be grater than 2' +EndIf + +When I set 'sqrt' to '1' as 'number' +When I set 'sqrt^2' to '1' as 'number' + +If I verify number 'end' is more than '1' + Foreach 'i' in sequence from '2' to 'end' with step '1' + If I verify number 'sqrt^2' is less than 'i' + When I create the result of 'sqrt' + '1' + and I remove 'sqrt' + and I rename 'result' to 'sqrt' + When I create the result of 'sqrt' * 'sqrt' + and I remove 'sqrt^2' + and I rename 'result' to 'sqrt^2' + EndIf + Foreach 'j' in 'res' + If I verify number 'j' is more than 'sqrt' + When I break foreach + EndIf + When I create the result of 'i' % 'j' + If I verify 'result' is equal to '0' + When I set 'not_prime' to 'true' as 'string' + When I remove 'result' + When I break the foreach + EndIf + When I remove 'result' + EndForeach + If I verify 'not_prime' is not found + When I copy 'i' in 'res' + EndIf + If I verify 'not_prime' is found + When I remove 'not_prime' + EndIf + EndForeach +EndIf + +Then print 'res' +EOF + save_output foreach_less_naive_prime_till_number.out.json + assert_output '{"res":[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]}' +} + +@test "Foreach: factorial of given number" { + cat << EOF | save_asset foreach_factorial.data.json +{ + "number": 5 +} +EOF + cat << EOF | save_asset foreach_factorial.keys.json +{ + "0": 0, + "1": 1 +} +EOF + cat << EOF | zexe foreach_factorial.zen foreach_factorial.data.json foreach_factorial.keys.json +# Problem: +# calcuate the factorial of 'number' + +Given I have a 'number' named 'number' +Given I have a 'number' named '0' +Given I have a 'number' named '1' + +When I set 'factorial' to '1' as 'number' + +If I verify number 'number' is less than '0' + When I exit with error message 'factorial exists only for positive number' +EndIf +If I verify number 'number' is more than '1' + Foreach 'i' in sequence from '1' to 'number' with step '1' + When I create the result of 'factorial' * 'i' + When I remove 'factorial' + When I rename 'result' to 'factorial' + EndForeach +EndIf + +Then print 'factorial' +EOF + save_output foreach_factorial.out.json + assert_output '{"factorial":120}' +} From 1e651cb3ae66f3a2d03ab41e7a8de3baea99183b Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Sun, 17 Nov 2024 19:29:26 +0100 Subject: [PATCH 20/27] fix: improve foreach performance checks are done only on the first loop and store some more data in iter object to avoid some calculations at each step --- src/lua/zencode_foreach.lua | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/lua/zencode_foreach.lua b/src/lua/zencode_foreach.lua index b1189c2ad..e3379c38c 100644 --- a/src/lua/zencode_foreach.lua +++ b/src/lua/zencode_foreach.lua @@ -52,34 +52,38 @@ Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, t zencode_assert(type(from) == type(to) and type(to) == type(step), "Types must be equal in foreach declaration") zencode_assert(type(from) == 'zenroom.big' or type(from) == 'zenroom.float', "Unknown number type") - local current_value local finished - -- TODO(optimization): we are currently doing multiplication at each iteration if type(from) == 'zenroom.big' then - zencode_assert(BIG.zenpositive(step) - and BIG.zenpositive(BIG.zensub(to, from)), - "only positive step is supported") - zencode_assert(step ~= BIG.new(0), "step cannot be zero") - local bigpos = BIG.new(info.pos-1) - current_value = BIG.zenadd(from, BIG.zenmul(bigpos, step)) + -- only on first iteration: do checks and save usefull values + if info.pos == 1 then + zencode_assert(BIG.zenpositive(step) and step ~= BIG.new(0), "only positive step is supported") + zencode_assert(BIG.zenpositive(BIG.zensub(to, from)), "end of foreach must be bigger than the start") + info.to_plus_1 = BIG.zenadd(to, BIG.new(1)) -- store to avoid repeating at each step + info.cv = from + else + info.cv = BIG.zenadd(info.cv, step) + end -- current_value > to -- current_value >= to + 1 -- (current_value - (to+1)) >= 0 - finished = BIG.zenpositive( - BIG.zensub(current_value, BIG.zenadd(to,BIG.new(1)))) + finished = BIG.zenpositive(BIG.zensub(info.cv, info.to_plus_1)) else - zencode_assert(step > F.new(0) and from < to, - "only positive step is supported") - local floatpos = F.new(info.pos-1) - current_value = from + floatpos * step - finished = current_value > to + -- only on first iteration: do checks and save usefull values + if info.pos == 1 then + zencode_assert(step > F.new(0) and from < to, + "only positive step is supported") + info.cv = from + else + info.cv = info.cv + step + end + finished = info.cv > to end if finished then info.pos = 0 clear_iterators() else - ACK[name] = current_value + ACK[name] = info.cv if not CODEC[name] then new_codec(name, CODEC[from]) CODEC[name].name = name From 99732cc328a36ca395342359aaee1cd72e058bb8 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 18 Nov 2024 11:18:33 +0100 Subject: [PATCH 21/27] fix: loop over integers was doing one more step --- src/lua/zencode_foreach.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lua/zencode_foreach.lua b/src/lua/zencode_foreach.lua index e3379c38c..f6a588b55 100644 --- a/src/lua/zencode_foreach.lua +++ b/src/lua/zencode_foreach.lua @@ -66,7 +66,8 @@ Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, t -- current_value > to -- current_value >= to + 1 -- (current_value - (to+1)) >= 0 - finished = BIG.zenpositive(BIG.zensub(info.cv, info.to_plus_1)) + local diff = BIG.zensub(info.cv, info.to_plus_1) + finished = BIG.zenpositive(diff) or diff == BIG.new(0) else -- only on first iteration: do checks and save usefull values if info.pos == 1 then From 30984e12b617d705d972b151af7a6ea74a0a2047 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 18 Nov 2024 11:21:28 +0100 Subject: [PATCH 22/27] refactor: in foreach do not throw an error when reaching maxiter, but when exeeding it add also tests for it --- src/lua/zencode.lua | 6 ++- test/zencode/foreach.bats | 111 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 7d7a20de9..45a575014 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -644,6 +644,8 @@ local function manage_foreach(stack, x) local not_valid_parent_loop = last_iter.pos == 0 table.insert(stack.ITER, {jump = stack.current_instruction, pos = fif(not_valid_parent_loop, 0, 1)}) return not_valid_parent_loop + else + return false end end if string.match(x.section, '^endforeach') then @@ -661,8 +663,8 @@ local function manage_foreach(stack, x) end end if last_iter then - if last_iter.pos >= MAXITER then - error("Limit of iterations reached: " .. MAXITER) + if last_iter.pos > MAXITER then + error("Limit of iterations exceeded: " .. MAXITER) -- skip all statements on last (not valid) loop elseif last_iter.pos == 0 and last_iter.end_id then stack.next_instruction = last_iter.end_id diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index 53ae62f9e..92ac85aad 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -456,3 +456,114 @@ EOF save_output multiple_not_closed_foreach.out assert_output '{"res":"aaabbabbaabbab"}' } + +@test "maxiter" { + cat << EOF | save_asset maxiter.data.json +{ + "start_int": "1", + "step_int": "1", + "end_in_limit_int": "10", + "end_out_of_limit_int": "11", + "start_float": 1, + "step_float": 1, + "end_in_limit_float": 10, + "end_out_of_limit_float": 11, + "array_in_limit": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j" + ], + "array_out_of_limit": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k" + ] +} +EOF + conf="maxiter=dec:10" + cat << EOF | zexe maxiter.work.zen maxiter.data.json +Given I have a 'integer' named 'start_int' +Given I have a 'integer' named 'step_int' +Given I have a 'integer' named 'end_in_limit_int' +Given I have a 'float' named 'start_float' +Given I have a 'float' named 'step_float' +Given I have a 'float' named 'end_in_limit_float' +Given I have a 'string array' named 'array_in_limit' + +When I create the 'string array' named 'res' + +Foreach 'i' in 'array_in_limit' + When I move 'i' in 'res' +EndForeach +Foreach 'i' in sequence from 'start_int' to 'end_in_limit_int' with step 'step_int' + When I move 'i' in 'res' +EndForeach +Foreach 'i' in sequence from 'start_float' to 'end_in_limit_float' with step 'step_float' + When I move 'i' in 'res' +EndForeach +Then print the 'res' +EOF + save_output maxiter_work.out.json + assert_output '{"res":["a","b","c","d","e","f","g","h","i","j","1","2","3","4","5","6","7","8","9","10",1,2,3,4,5,6,7,8,9,10]}' + + cat << EOF | save_asset maxiter_error_1.zen +Given I have a 'integer' named 'start_int' +Given I have a 'integer' named 'step_int' +Given I have a 'integer' named 'end_out_of_limit_int' + +When I create the 'string array' named 'res' + +Foreach 'i' in sequence from 'start_int' to 'end_out_of_limit_int' with step 'step_int' + When I move 'i' in 'res' +EndForeach + +Then print the 'res' +EOF + run $ZENROOM_EXECUTABLE -c "maxiter=dec:10" -a maxiter.data.json -z maxiter_error_1.zen + assert_line --partial 'Limit of iterations exceeded: 10' + + cat << EOF | save_asset maxiter_error_2.zen +Given I have a 'float' named 'start_float' +Given I have a 'float' named 'step_float' +Given I have a 'float' named 'end_out_of_limit_float' + +When I create the 'string array' named 'res' + +Foreach 'i' in sequence from 'start_float' to 'end_out_of_limit_float' with step 'step_float' + When I move 'i' in 'res' +EndForeach + +Then print the 'res' +EOF + run $ZENROOM_EXECUTABLE -c "maxiter=dec:10" -a maxiter.data.json -z maxiter_error_2.zen + assert_line --partial 'Limit of iterations exceeded: 10' + + cat << EOF | save_asset maxiter_error_3.zen +Given I have a 'string array' named 'array_out_of_limit' + +When I create the 'string array' named 'res' + +Foreach 'i' in 'array_out_of_limit' + When I move 'i' in 'res' +EndForeach + +Then print the 'res' +EOF + run $ZENROOM_EXECUTABLE -c "maxiter=dec:10" -a maxiter.data.json -z maxiter_error_3.zen + assert_line --partial 'Limit of iterations exceeded: 10' +} From ad0c7821998a7e0084ebf2bdef1293ed8fe9cb91 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 18 Nov 2024 17:20:28 +0100 Subject: [PATCH 23/27] refactor: create a AST_iterator unfortunately this do not improve performance, but anyway code is cleaner for sure --- src/lua/zencode.lua | 85 ++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 45a575014..b10321eb0 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -61,8 +61,7 @@ ZEN = { id = 0, checks = {version = false}, -- version, scenario checked, etc. OK = true, -- set false by asserts - current_instruction = 0, -- first instruction - next_instruction = 1, -- first instruction + jump = nil, ITER = {}, -- foreach infos traceback = {}, -- transferred into HEAP by zencode_begin linenum = 0, @@ -634,15 +633,15 @@ end -- return true: caller skip execution and go to ::continue:: -- return false: execute statement -local function manage_foreach(stack, x) +local function manage_foreach(stack, x, ci) local last_iter = stack.ITER[#stack.ITER] if string.match(x.section, '^foreach') then if not last_iter then - table.insert(stack.ITER, {jump = stack.current_instruction, pos = 1 }) + table.insert(stack.ITER, {jump = ci, pos = 1 }) return false - elseif last_iter.jump ~= stack.current_instruction then + elseif last_iter.jump ~= ci then local not_valid_parent_loop = last_iter.pos == 0 - table.insert(stack.ITER, {jump = stack.current_instruction, pos = fif(not_valid_parent_loop, 0, 1)}) + table.insert(stack.ITER, {jump = ci, pos = fif(not_valid_parent_loop, 0, 1)}) return not_valid_parent_loop else return false @@ -655,7 +654,7 @@ local function manage_foreach(stack, x) end if last_iter.pos > 0 then last_iter.pos = last_iter.pos + 1 - stack.next_instruction = last_iter.jump + stack.jump = last_iter.jump return true else table.remove(stack.ITER) @@ -667,13 +666,36 @@ local function manage_foreach(stack, x) error("Limit of iterations exceeded: " .. MAXITER) -- skip all statements on last (not valid) loop elseif last_iter.pos == 0 and last_iter.end_id then - stack.next_instruction = last_iter.end_id + stack.jump = last_iter.end_id return true end end return last_iter and last_iter.pos == 0 end +local function AST_iterator() + local i = 0 + local AST_size = table_size(AST) + return function() + i = i+1 + -- End of iteration (i exceeds the AST length) + if i > AST_size then + return nil + end + local value = AST[i] + while manage_branching(ZEN, value) or manage_foreach(ZEN, value, i) do + if ZEN.jump then + i = ZEN.jump - 1 + ZEN.jump = nil + end + i = i+1 + value = AST[i] + end + return value + end +end + + function ZEN:run() self:crumb() local runtime_trace = function(x) @@ -724,44 +746,35 @@ function ZEN:run() -- convert all spaces in keys to underscore IN = IN_uscore(IN) - -- EXEC zencode - -- TODO: for optimization, to develop a lua iterator, which would save lookup time - -- https://www.lua.org/pil/7.1.html - local AST_size = table_size(AST) - while self.next_instruction <= AST_size do - self.current_instruction = self.next_instruction - local x = AST[self.current_instruction] - self.next_instruction = self.next_instruction + 1 - if not manage_branching(self, x) and not manage_foreach(self, x) then - -- trigger upon switch to when or then section - if x.from == 'given' and x.to ~= 'given' then + -- EXEC zencode + for x in AST_iterator() do + -- trigger upon switch to when or then section + if x.from == 'given' and x.to ~= 'given' then -- delete IN memory IN = {} collectgarbage 'collect' - end - -- HEAP integrity guard - if CONF.heapguard then -- watchdog + end + -- HEAP integrity guard + if CONF.heapguard then -- watchdog -- guard ACK's contents on section switch deepmap(zenguard, ACK) -- check that everythink in HEAP.ACK has a CODEC self:codecguard() - end + end - self.OK = true - exitcode(0) - runtime_trace(x) - local ok, err = pcall(x.hook, table.unpack(x.args)) - if not ok or not self.OK then + self.OK = true + exitcode(0) + runtime_trace(x) + local ok, err = pcall(x.hook, table.unpack(x.args)) + if not ok or not self.OK then runtime_error(x, err) fatal({msg=x.source, linenum=x.linenum}) -- traceback print inside - end - -- give a notice about the CACHE being used - -- TODO: print it in debug - if #CACHE > 0 then xxx('Contract CACHE is in use') end - collectgarbage 'collect' - end - -- ::continue:: - end + end + -- give a notice about the CACHE being used + -- TODO: print it in debug + if #CACHE > 0 then xxx('Contract CACHE is in use') end + collectgarbage 'collect' + end -- PRINT output self:ftrace('--- Zencode execution completed') if CONF.exec.scope == 'full' then From 039b3de1fb9eda9ec73771bd94510da4ba426a80 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 18 Nov 2024 17:33:39 +0100 Subject: [PATCH 24/27] fix: cover case in which contract ends with endif or endforeach --- src/lua/zencode.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index b10321eb0..dd0b00b88 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -683,7 +683,7 @@ local function AST_iterator() return nil end local value = AST[i] - while manage_branching(ZEN, value) or manage_foreach(ZEN, value, i) do + while i < AST_size and (manage_branching(ZEN, value) or manage_foreach(ZEN, value, i)) do if ZEN.jump then i = ZEN.jump - 1 ZEN.jump = nil From c70a736ebc49d9dd615d9d998db1cd1aa33d98a4 Mon Sep 17 00:00:00 2001 From: matteo-cristino Date: Mon, 18 Nov 2024 19:34:08 +0100 Subject: [PATCH 25/27] refactor: simplify a bit manage_branching and use more local varibales to save lookup times --- src/lua/zencode.lua | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index dd0b00b88..2a84b8edf 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -605,30 +605,26 @@ end -- return false: execute statement local function manage_branching(stack, x) stack.branch_condition = nil - if string.match(x.section, '^if') then + local b = stack.branch + local v = stack.branch_valid + local s = x.section + + if s:sub(1, 2) == 'if' then stack.branch_condition = true - -- xxx("START conditional execution: "..x.source, 2) - stack.branch = stack.branch+1 - if stack.branch_valid == stack.branch-1 then - stack.branch_valid = stack.branch_valid+1 + stack.branch = b+1 + if v == b then + stack.branch_valid = v+1 return false end return true - end - if string.match(x.section, '^endif') then - --xxx("END conditional execution: "..x.source, 2) - if stack.branch_valid == stack.branch then - stack.branch_valid = stack.branch_valid-1 + elseif s:sub(1, 5) == 'endif' then + if v == b then + stack.branch_valid = v-1 end - stack.branch = stack.branch-1 - return true - end - if stack.branch == 0 then return false end - if stack.branch > stack.branch_valid then - --xxx('skip execution in false conditional branch: '..x.source, 2) + stack.branch = b-1 return true end - return false + return b ~= 0 and b > v end -- return true: caller skip execution and go to ::continue:: From 75eb7a7f35bdf0096b1832692294adf119f69189 Mon Sep 17 00:00:00 2001 From: Matteo Cristino <102997993+matteo-cristino@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:37:17 +0100 Subject: [PATCH 26/27] feat: improve general performance by reducing the total numbers of call to garbage collector (#969) * refactor: try to improve performance * ITER stack built at parse time * avoid string/regexp checks in manage functions by doing it in the parse part and just do a lookup in the latter * run manage functions only when needed * feat: on zencode run perform garbage collection only if memory being used is more than MAXMEM MAXMEM default value is 1024MB, but can be changed using the maxmem conf * fix: typo in default value of maxmem * docs: update config page remove some old and not anymore present config and add scope and maxmem, try also to improve description of other config. --- docs/pages/zenroom-config.md | 164 +++++++++++++++++++++++++---------- src/lua/init.lua | 2 +- src/lua/zencode.lua | 76 ++++++++++------ src/lua/zencode_foreach.lua | 10 +-- src/lua/zencode_given.lua | 4 +- src/zen_config.c | 31 ++++++- src/zenroom.c | 10 +++ src/zenroom.h | 3 +- 8 files changed, 216 insertions(+), 84 deletions(-) diff --git a/docs/pages/zenroom-config.md b/docs/pages/zenroom-config.md index 94572a860..ff48601e3 100755 --- a/docs/pages/zenroom-config.md +++ b/docs/pages/zenroom-config.md @@ -1,87 +1,157 @@ ## Usage -Zenroom configuration is used to set some application wide parameters at initialization time. The configuration is passed as a parameter (not as file!) using the "-c" option, or as a parameter if Zenroom is used as lib. You can pass Zenroom several attributes, wrapped in quotes and separated by a comma, as in the example below: +Zenroom configuration is used to set some application wide parameters at initialization time. +The configuration is passed as a parameter (not as file!) using the "-c" option, or as a parameter +if Zenroom is used as lib. You can pass Zenroom several attributes, wrapped in quotes and separated +by a comma, as in the example below: ```shell -zenroom -z keypair.zen -c "debug=3, rngseed=hex:74eeeab870a394175fae808dd5dd3b047f3ee2d6a8d01e14bff94271565625e98a63babe8dd6cbea6fedf3e19de4bc80314b861599522e44409fdd20f7cd6cfc" +zenroom -z keyring.zen -c "debug=3, rngseed=hex:74eeeab870a394175fae808dd5dd3b047f3ee2d6a8d01e14bff94271565625e98a63babe8dd6cbea6fedf3e19de4bc80314b861599522e44409fdd20f7cd6cfc" ``` Below a list of the config parameters with a description and usage examples. ## Debug verbosity -### Syntax and values: **debug=1, 2, 3** +Syntax and values: **debug=1|2|3** or **verbose=1|2|3** -Define the verbosity of Zenroom's output, bug reports need to be sent with "debug=3" +*debug* and *verbose* are synonyms. They define the verbosity of Zenroom's output. +Moreove if this value is grater than 1 the zenroom watchdog is activated and at each step a check on all internal +data is performed to assert all values in memory are converted to zenroom types. +*Default*: 2 +## Scope -## Color - -### Syntax and values: **color=1, 0** - -Defines color from printout log, "color=0" removes colors. - - -## Seccomp (CLI isolation) - -### Syntax and values: **seccomp=0, 1** - -Secure execution isolation for the CLI: separates the CLI to the OS kernel, file system and network in a provable way. Works only in Linux. +Syntax and values: **scope=given|full** +Scope represent wich part of the zenroom contract should be executed. When it is set to *full*, that is the default value, +all the contract is run, on the other hand when it is *given* only the given part is run and the result is the CODEC +status after the given phase, it can be used to know what data a user needs to pass to the contract. +*Default*: full. ## Random seed -### Syntax and values: **rngseed=hex:[64 bytes in hex notation]** +Syntax and values: **rngseed=hex:[64 bytes in hex notation]** -Loads a random seed at start, that will be used through the Zenroom execution whenever a random seed is requested. A fixed random can be used to test determinism. For example, when generating an ECDH keypair, using: +Loads a random seed at start, that will be used through the Zenroom execution whenever a random seed is requested. +A fixed random can be used to test determinism. For example, when generating an ECDH key, using: ```shell rngseed=hex:74eeeab870a394175fae808dd5dd3b047f3ee2d6a8d01e14bff94271565625e98a63babe8dd6cbea6fedf3e19de4bc80314b861599522e44409fdd20f7cd6cfc ``` -Should always generate the keypair: +Should always generate the keyring: ```json - { - "Alice": { - "keypair": { - "private_key": "Aku7vkJ7K01gQehKELav3qaQfTeTMZKgK+5VhaR3Ui0=", - "public_key": "BBCQg21VcjsmfTmNsg+I+8m1Cm0neaYONTqRnXUjsJLPa8075IYH+a9w2wRO7rFM1cKmv19Igd7ntDZcUvLq3xI=" - } +{ + "keyring": { + "private_key": "Aku7vkJ7K01gQehKELav3qaQfTeTMZKgK+5VhaR3Ui0=" } - } -``` - -## Limit iterations +``` -### Syntax and values: **maxiter=dec:[at most 10 decimal digits]** +## Log format -Define the maximum number of iterations the contract is allowed to do. +Syntax and values: **logfmt=text|json** + +When using *text* as log format, all the log is printed as a text, while using the *json* it is an array, +where, when an error happens, the trace and heap are base64 encoded and can be found respectively in the +values starting with *J64 TRACE:* and *J64 HEAP:*. +Example of log in text: +``` +Release version: v4.36.0 +Build commit hash: 760ca7bb +Memory manager: libc +ECDH curve is SECP256K1 +ECP curve is BLS381 +[W] Zencode is missing version check, please add: rule check version N.N.N +[W] { + KEYRING = { + bbs = int[32] , + bitcoin = octet[32] , + dilithium = octet[2528] , + ecdh = octet[32] , + eddsa = octet[32] , + es256 = octet[32] , + ethereum = octet[32] , + pvss = int[32] , + reflow = int[32] , + schnorr = octet[32] + } +} +[W] { + a_GIVEN_in = {}, + c_CACHE_ack = {}, + c_CODEC_ack = { + keyring = { + encoding = "def", + name = "keyring", + schema = "keyring", + zentype = "e" + } + }, + c_WHEN_ack = { + keyring = "(hidden)" + }, + d_THEN_out = {} +} ++18 Given nothing ++23 When I create the ecdh key ++24 When I create the es256 key ++25 When I create the ethereum key ++26 When I create the reflow key ++27 When I create the schnorr key ++28 When I create the bitcoin key ++29 When I create the eddsa key ++30 When I create the bbs key ++31 When I create the pvss key ++32 When I create the dilithium key ++34 Then print the 'keyrig' +[!] Error at Zencode line 34 +[!] /zencode_then.lua:158: Cannot find object: keyrig +[!] Zencode runtime error +[!] /zencode.lua:706: Zencode line 34: Then print the 'keyrig' +[!] Execution aborted with errors. +[*] Zenroom teardown. +Memory used: 606 KB +``` +the same log in json +```json +[ "ZENROOM JSON LOG START", +" Release version: v4.36.0", +" Build commit hash: 760ca7bb", +" Memory manager: libc", +" ECDH curve is SECP256K1", +" ECP curve is BLS381", +"[W] Zencode is missing version check, please add: rule check version N.N.N", +"J64 HEAP: eyJDQUNIRSI6W10sIkNPREVDIjp7ImtleXJpbmciOnsiZW5jb2RpbmciOiJkZWYiLCJuYW1lIjoia2V5cmluZyIsInNjaGVtYSI6ImtleXJpbmciLCJ6ZW50eXBlIjoiZSJ9fSwiR0lWRU5fZGF0YSI6W10sIlRIRU4iOltdLCJXSEVOIjp7ImtleXJpbmciOiIoaGlkZGVuKSJ9fQ==", +"J64 TRACE: WyIrMTggIEdpdmVuIG5vdGhpbmciLCIrMjMgIFdoZW4gSSBjcmVhdGUgdGhlIGVjZGgga2V5IiwiKzI0ICBXaGVuIEkgY3JlYXRlIHRoZSBlczI1NiBrZXkiLCIrMjUgIFdoZW4gSSBjcmVhdGUgdGhlIGV0aGVyZXVtIGtleSIsIisyNiAgV2hlbiBJIGNyZWF0ZSB0aGUgcmVmbG93IGtleSIsIisyNyAgV2hlbiBJIGNyZWF0ZSB0aGUgc2Nobm9yciBrZXkiLCIrMjggIFdoZW4gSSBjcmVhdGUgdGhlIGJpdGNvaW4ga2V5IiwiKzI5ICBXaGVuIEkgY3JlYXRlIHRoZSBlZGRzYSBrZXkiLCIrMzAgIFdoZW4gSSBjcmVhdGUgdGhlIGJicyBrZXkiLCIrMzEgIFdoZW4gSSBjcmVhdGUgdGhlIHB2c3Mga2V5IiwiKzMyICBXaGVuIEkgY3JlYXRlIHRoZSBkaWxpdGhpdW0ga2V5IiwiKzM0ICBUaGVuIHByaW50IHRoZSAna2V5cmlnJyIsIlshXSBFcnJvciBhdCBaZW5jb2RlIGxpbmUgMzQiLCJbIV0gL3plbmNvZGVfdGhlbi5sdWE6MTU4OiBDYW5ub3QgZmluZCBvYmplY3Q6IGtleXJpZyJd", +"[!] Zencode runtime error", +"[!] /zencode.lua:706: Zencode line 34: Then print the 'keyrig'", +"[!] Execution aborted with errors.", +"[*] Zenroom teardown.", +" Memory used: 663 KB", +"ZENROOM JSON LOG END" ] +``` +*Default*: *json* in javascript bindings and *text* otherwise. -## Memory manager -### Syntax and values: **memmanager=sys, lw, je** +## Limit iterations -Switch the use of a different memory manager, between: +Syntax and values: **maxiter=dec:[at most 10 decimal digits]** -- **sys**: system memory manager -- **lw**: "lightweight" memory manager, internal to Zenroom, can be more performant on some situation and required when running Zenroom on embedded systems with RTOS, baremetal or situations where there is no memory manager offered by the OS -- **je**: experimental memory manager based on "jemalloc", safe and fast alternative supported by some OS. +Define the maximum number of iterations the contract is allowed to do. -## Print output -### Syntax and values: **print=sys, stb** +*Default*: 1000. -Defines the function used to print the output. +## Limit of memory -- **sys** = uses system defined print, typically "sprintf" or "vsprintf" -- **stb** = internal print function based on [stb](https://github.com/nothings/stb) to be used with on embedded systems with RTOS, baremetal +Syntax and values: **maxmem=dec:[at most 10 decimal digits]** -## Log format -Since Zenroom 3, the default error log is printed out in base64. To print out the errors in text, use -```shell -logfmt=text -``` +Define the maximum memory in MB that lua can occupy before calling the garbage collector +during the run phase. + +*Default*: 1024 (1GB) diff --git a/src/lua/init.lua b/src/lua/init.lua index 76629cd59..78dce7b40 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -59,6 +59,7 @@ MACHINE = require('statemachine') SEMVER = require('semver') _G['ZENROOM_VERSION'] = SEMVER(VERSION) _G['MAXITER'] = tonumber(STR_MAXITER) +_G['MAXMEM'] = tonumber(STR_MAXMEM) * 1024 OCTET = require('zenroom_octet') BIG = require('zenroom_big') @@ -203,4 +204,3 @@ function ZKP_challenge(list) end collectgarbage 'collect' - diff --git a/src/lua/zencode.lua b/src/lua/zencode.lua index 2a84b8edf..171d2bc66 100644 --- a/src/lua/zencode.lua +++ b/src/lua/zencode.lua @@ -62,7 +62,11 @@ ZEN = { checks = {version = false}, -- version, scenario checked, etc. OK = true, -- set false by asserts jump = nil, - ITER = {}, -- foreach infos + ITER_present = false, + BRANCH_present = false, + ITER = {}, -- foreach infos, + ITER_parse = {}, + ITER_head = nil, traceback = {}, -- transferred into HEAP by zencode_begin linenum = 0, last_valid_statement = false, @@ -194,9 +198,22 @@ function ZEN:begin(new_heap) from = from, to = to, hook = func, - linenum = linenum + linenum = linenum, + f = index == 'foreach' or nil, + ef = index == 'endforeach' or nil, + i = index == 'if' or nil, + ei = index == 'endif' or nil } ) -- function + if index == 'foreach' then + ctx.Z.ITER_present = true + ctx.Z.ITER[ctx.Z.id] = { jump = ctx.Z.id, pos = 1 } + table.insert(ctx.Z.ITER_parse, ctx.Z.id) + elseif index == 'endforeach' then + local id = table.remove(ctx.Z.ITER_parse) + ctx.Z.ITER[id].end_id = ctx.Z.id + end + ctx.Z.BRANCH_present = ctx.Z.BRANCH_present or index == 'if' ctx.Z.OK = true end if not ctx.Z.OK and CONF.parser.strict_match then @@ -609,7 +626,7 @@ local function manage_branching(stack, x) local v = stack.branch_valid local s = x.section - if s:sub(1, 2) == 'if' then + if x.i then stack.branch_condition = true stack.branch = b+1 if v == b then @@ -617,7 +634,7 @@ local function manage_branching(stack, x) return false end return true - elseif s:sub(1, 5) == 'endif' then + elseif x.ei then if v == b then stack.branch_valid = v-1 end @@ -630,38 +647,35 @@ end -- return true: caller skip execution and go to ::continue:: -- return false: execute statement local function manage_foreach(stack, x, ci) - local last_iter = stack.ITER[#stack.ITER] - if string.match(x.section, '^foreach') then - if not last_iter then - table.insert(stack.ITER, {jump = ci, pos = 1 }) - return false - elseif last_iter.jump ~= ci then - local not_valid_parent_loop = last_iter.pos == 0 - table.insert(stack.ITER, {jump = ci, pos = fif(not_valid_parent_loop, 0, 1)}) - return not_valid_parent_loop - else - return false - end - end - if string.match(x.section, '^endforeach') then - if not last_iter.end_id then last_iter.end_id = x.id end - if last_iter.pos == 0 and last_iter.end_id ~= x.id then + local last_iter = stack.ITER_head + -- if no foreach is defined skip all + if not last_iter and not x.f then return false end + if x.f then + if last_iter and last_iter.pos == 0 then + stack.jump = last_iter.end_id return true end + stack.ITER_head = stack.ITER[x.id] + if last_iter and stack.ITER_head.pos == 1 then + stack.ITER_head.parent = last_iter + end + return false + elseif x.ef then if last_iter.pos > 0 then last_iter.pos = last_iter.pos + 1 stack.jump = last_iter.jump return true else - table.remove(stack.ITER) - last_iter = stack.ITER[#stack.ITER] + last_iter.pos = 1 + last_iter = stack.ITER_head.parent + stack.ITER_head = last_iter end end if last_iter then if last_iter.pos > MAXITER then error("Limit of iterations exceeded: " .. MAXITER) -- skip all statements on last (not valid) loop - elseif last_iter.pos == 0 and last_iter.end_id then + elseif last_iter.pos == 0 then stack.jump = last_iter.end_id return true end @@ -672,6 +686,16 @@ end local function AST_iterator() local i = 0 local AST_size = table_size(AST) + local manage + if ZEN.ITER_present and ZEN.BRANCH_present then + manage = function(z, v, j) return manage_branching(z, v) or manage_foreach(z, v, j) end + elseif ZEN.ITER_present and not ZEN.BRANCH_present then + manage = function(z, v, j) return manage_foreach(z, v, j) end + elseif not ZEN.ITER_present and ZEN.BRANCH_present then + manage = function(z, v, j) return manage_branching(z, v) end + else + manage = function(z, v, j) return false end + end return function() i = i+1 -- End of iteration (i exceeds the AST length) @@ -679,7 +703,7 @@ local function AST_iterator() return nil end local value = AST[i] - while i < AST_size and (manage_branching(ZEN, value) or manage_foreach(ZEN, value, i)) do + while i < AST_size and manage(ZEN, value, i) do if ZEN.jump then i = ZEN.jump - 1 ZEN.jump = nil @@ -769,7 +793,9 @@ function ZEN:run() -- give a notice about the CACHE being used -- TODO: print it in debug if #CACHE > 0 then xxx('Contract CACHE is in use') end - collectgarbage 'collect' + if collectgarbage('count') > MAXMEM then + collectgarbage('collect') + end end -- PRINT output self:ftrace('--- Zencode execution completed') diff --git a/src/lua/zencode_foreach.lua b/src/lua/zencode_foreach.lua index f6a588b55..a2aaa6083 100644 --- a/src/lua/zencode_foreach.lua +++ b/src/lua/zencode_foreach.lua @@ -1,5 +1,5 @@ local function clear_iterators() - local info = ZEN.ITER[#ZEN.ITER] + local info = ZEN.ITER_head if not info.names then return end for _,v in pairs(info.names) do ACK[v] = nil @@ -9,7 +9,7 @@ local function clear_iterators() end Foreach("'' in ''", function(name, collection) - local info = ZEN.ITER[#ZEN.ITER] + local info = ZEN.ITER_head local col = have(collection) local collection_codec = CODEC[collection] zencode_assert(collection_codec.zentype == "a", "Can only iterate over arrays") @@ -39,7 +39,7 @@ Foreach("'' in ''", function(name, collection) end) Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, to_name, step_name) - local info = ZEN.ITER[#ZEN.ITER] + local info = ZEN.ITER_head local from = have(from_name) local to = have(to_name) local step = have(step_name) @@ -93,10 +93,10 @@ Foreach("'' in sequence from '' to '' with step ''", function(name, from_name, t end) local function break_foreach() - if #ZEN.ITER == 0 or not ZEN.ITER[#ZEN.ITER].pos then + if not ZEN.ITER_head then error("Can only exit from foreach loop", 2) end - ZEN.ITER[#ZEN.ITER].pos = 0 + ZEN.ITER_head.pos = 0 clear_iterators() end diff --git a/src/lua/zencode_given.lua b/src/lua/zencode_given.lua index 44e914128..4ba4380fc 100644 --- a/src/lua/zencode_given.lua +++ b/src/lua/zencode_given.lua @@ -26,13 +26,12 @@ -- inline input. -- data coming in is analyzed through a series of functions: --- guess_conversion(raw, conv or name) -> zenode_data.lua L:100 approx +-- guess_conversion(raw, conv or name) -> zenode_data.lua L:100 approx --- |_ if luatype(string) && format -> input encoding(format) -- GIVEN local function gc() ZEN.TMP = {} - collectgarbage 'collect' end -- safely take any zenroom object as index @@ -444,4 +443,3 @@ Given("'' in path ''", function(enc, path) ack(dest) gc() end) - diff --git a/src/zen_config.c b/src/zen_config.c index e2f1cf68a..44181d0ec 100644 --- a/src/zen_config.c +++ b/src/zen_config.c @@ -108,12 +108,13 @@ int zen_conf_parse(zenroom_t *ZZ, const char *configuration) { switch (lex.token) { // first token parsed, set enum for value case CLEX_id: - if(strcasecmp(lex.string,"debug") ==0) { curconf = VERBOSE; break; } // bool + if(strcasecmp(lex.string,"debug")==0) { curconf = VERBOSE; break; } // bool if(strcasecmp(lex.string,"verbose")==0) { curconf = VERBOSE; break; } // int - if(strcasecmp(lex.string,"scope")==0) { curconf = SCOPE; break; } // str + if(strcasecmp(lex.string,"scope")==0) { curconf = SCOPE; break; } // str if(strcasecmp(lex.string,"rngseed")==0) { curconf = RNGSEED; break; } // str if(strcasecmp(lex.string,"logfmt") ==0) { curconf = LOGFMT; break; } // str if(strcasecmp(lex.string,"maxiter")==0) { curconf = MAXITER; break; } // str + if(strcasecmp(lex.string,"maxmem")==0) { curconf = MAXMEM; break; } // str if(curconf==RNGSEED) { if(strncasecmp(lex.string, "hex:", 4) != 0) { // hex: prefix needed _err( "Invalid rngseed data prefix (must be hex:)\n"); @@ -195,6 +196,32 @@ int zen_conf_parse(zenroom_t *ZZ, const char *configuration) { ZZ->str_maxiter[len-4] = 0x0; break; } + if(curconf==MAXMEM) { + int len = strlen(lex.string); + if( len-4 > STR_MAXITER_LEN || len < 5) { + _err( "Invalid length of maxmem, must be less than %u digits", + STR_MAXITER_LEN); + // free(lexbuf); + return 0; + } + if(strncasecmp(lex.string, "dec:", 4) != 0) { // dec: prefix needed + _err( "Invalid maxmem data prefix (must be dec:)\n"); + // free(lexbuf); + return 0; + } + for(p=4; pstr_maxmem, lex.string+4, len-4); + ZZ->str_maxmem[len-4] = 0x0; + break; + } // free(lexbuf); _err( "Invalid configuration: %s\n", lex.string); curconf = NIL; diff --git a/src/zenroom.c b/src/zenroom.c index 0086f80b9..5f8c01624 100644 --- a/src/zenroom.c +++ b/src/zenroom.c @@ -185,11 +185,18 @@ zenroom_t *zen_init(const char *conf, const char *keys, const char *data) { #else ZZ->logformat = LOG_TEXT; #endif + // default maxiter 1000 steps ZZ->str_maxiter[0] = '1'; ZZ->str_maxiter[1] = '0'; ZZ->str_maxiter[2] = '0'; ZZ->str_maxiter[3] = '0'; ZZ->str_maxiter[4] = '\0'; + // default maxmem 1GB + ZZ->str_maxmem[0] = '1'; + ZZ->str_maxmem[1] = '0'; + ZZ->str_maxmem[2] = '2'; + ZZ->str_maxmem[3] = '4'; + ZZ->str_maxmem[4] = '\0'; ZZ->memcount_octets = 0; ZZ->memcount_bigs = 0; ZZ->memcount_hashes = 0; @@ -238,6 +245,9 @@ zenroom_t *zen_init(const char *conf, const char *keys, const char *data) { lua_pushstring(ZZ->lua, ZZ->str_maxiter); lua_setglobal (ZZ->lua, "STR_MAXITER"); + lua_pushstring(ZZ->lua, ZZ->str_maxmem); + lua_setglobal (ZZ->lua, "STR_MAXMEM"); + if(ZZ->scope == SCOPE_GIVEN) { lua_pushstring(ZZ->lua, "GIVEN"); lua_setglobal (ZZ->lua, "ZENCODE_SCOPE"); diff --git a/src/zenroom.h b/src/zenroom.h index 4cc7ff102..c86cc4c62 100644 --- a/src/zenroom.h +++ b/src/zenroom.h @@ -75,7 +75,7 @@ int zenroom_sign_verify(const char *algo, const char *pk, const char *msg, const // conf switches typedef enum { STB, MUTT, LIBC } printftype; -typedef enum { NIL, VERBOSE, SCOPE, RNGSEED, LOGFMT, MAXITER } zconf; +typedef enum { NIL, VERBOSE, SCOPE, RNGSEED, LOGFMT, MAXITER, MAXMEM } zconf; // zenroom context, also available as "_Z" global in lua space // contents are opaque in lua and available only as lightuserdata @@ -106,6 +106,7 @@ typedef struct { char zconf_rngseed[(RANDOM_SEED_LEN*2)+4]; // 0x and terminating \0 char str_maxiter[STR_MAXITER_LEN + 1]; + char str_maxmem[STR_MAXITER_LEN + 1]; int memcount_octets; int memcount_bigs; From e1e91f4c175e08462e75f793f300424036c8fa38 Mon Sep 17 00:00:00 2001 From: Matteo Cristino <102997993+matteo-cristino@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:55:27 +0100 Subject: [PATCH 27/27] docs: add foreach documentation and update if documentation (#973) * docs: add foreach to zenroom documentation * docs(foreach): rename some section title and add section on how to create the material in this page * docs(if): exlpain the new way in which the if and endif work --- docs/_sidebar.md | 25 ++--- .../branching/nested_if_in_foreach.data.json | 19 ++++ .../branching/nested_if_in_foreach.out.json | 1 + .../branching/nested_if_in_foreach.zen | 46 +++++++++ .../branching/number_comparison.zen | 1 + .../foreach_divisible_by_five.data.json | 3 + .../foreach_divisible_by_five.keys.json | 6 ++ .../foreach_divisible_by_five.out.json | 1 + .../educational/foreach_divisible_by_five.zen | 31 ++++++ .../zencode_cookbook/foreach/verysimple.data | 3 + .../zencode_cookbook/foreach/verysimple.out | 1 + .../zencode_cookbook/foreach/verysimple.zen | 6 ++ docs/pages/zencode-foreach-endforeach.md | 95 +++++++++++++++++++ docs/pages/zencode-if-endif.md | 82 ++++++++-------- test/zencode/branching.bats | 4 +- test/zencode/foreach.bats | 6 +- 16 files changed, 271 insertions(+), 59 deletions(-) create mode 100644 docs/examples/zencode_cookbook/branching/nested_if_in_foreach.data.json create mode 100644 docs/examples/zencode_cookbook/branching/nested_if_in_foreach.out.json create mode 100644 docs/examples/zencode_cookbook/branching/nested_if_in_foreach.zen create mode 100644 docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.data.json create mode 100644 docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.keys.json create mode 100644 docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.out.json create mode 100644 docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.zen create mode 100644 docs/examples/zencode_cookbook/foreach/verysimple.data create mode 100644 docs/examples/zencode_cookbook/foreach/verysimple.out create mode 100644 docs/examples/zencode_cookbook/foreach/verysimple.zen create mode 100644 docs/pages/zencode-foreach-endforeach.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index ef7e6723d..f110e3fa2 100755 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -6,14 +6,15 @@ - Zencode Basics - - [Quickstart](/pages/zencode-cookbook-intro.md "Zencode Quickstart") - - [Zencode: "Given" phase](/pages/zencode-cookbook-given.md "Zencode cookbook Given") - - [Zencode: "When phase](/pages/zencode-cookbook-when.md "Zencode cookbook When") - - [Zencode: "Then" phase](/pages/zencode-cookbook-then.md "Zencode cookbook Then") - - [Zencode: "If/Endif" branching](/pages/zencode-if-endif.md "Zencode branching") - - [Zencode: debug](/pages/zencode-debug.md "Zencode debug") + - [Quickstart](/pages/zencode-cookbook-intro.md "Zencode Quickstart") + - [Given phase](/pages/zencode-cookbook-given.md "Zencode cookbook Given") + - [When phase](/pages/zencode-cookbook-when.md "Zencode cookbook When") + - [Then phase](/pages/zencode-cookbook-then.md "Zencode cookbook Then") + - [If/Endif branching](/pages/zencode-if-endif.md "Zencode branching") + - [Foreach/EndForeach looping](/pages/zencode-foreach-endforeach.md "Zencode looping") + - [Debug](/pages/zencode-debug.md "Zencode debug") -- Zencode Advanced +- Zencode Advanced - [Cryptography](/pages/zencode-crypto.md "Zencode") - [Scenario 'credentials': zero knowledge proof and attribute based credentials](/pages/zencode-scenario-credentials.md "Zencode credentials") - [Scenario 'ecdh': encryption, signature](/pages/zencode-scenarios-ecdh "Zencode ecdh") @@ -38,7 +39,7 @@ - [Zencode command list](/pages/zencode-list.md "Zencode command list") - Bindings - - [ Use Zenroom as a library](/pages/how-to-embed.md "Embed") + - [ Use Zenroom as a library](/pages/how-to-embed.md "Embed") - [JavaScript ](/pages/javascript.md "Use Zenroom in JavaScript") - [Python](/pages/python.md "Use Zenroom in JavaScript") - [Java](/pages/java.md "Use Zenroom in Java") @@ -49,7 +50,7 @@ - [Zenroom in the browser](/pages/zenroom-javascript2b.md "Use Zenroom in the browser") - [Zenroom in React](/pages/zenroom-javascript3.md "Use Zenroom in React") - [Zenroom in React Native](/pages/zenroom-react-native.md "Use Zenroom in React Native") - - [Zenroom ClojureScript](/pages/zenroom-clojurescript.md "Zenroom ClojureScript]") + - [Zenroom ClojureScript](/pages/zenroom-clojurescript.md "Zenroom ClojureScript]") - Extensions - [RESTroom-mw](/pages/restroom-mw) @@ -60,7 +61,7 @@ - Build & Test - - [Build instructions](/pages/how-to-build.md "Build Zenroom") + - [Build instructions](/pages/how-to-build.md "Build Zenroom") - [Zenroom on Cortex M](/pages/cortex.md "Zenroom on Cortex M") - [Random quality](/pages/random.md "Random quality measurement") - [Browser-encryption demo](/pages/encrypt.md "Browser-encryption demo") @@ -70,8 +71,8 @@ - Programming Lua - [Lua](/pages/lua.md "in Lua") - - [Lua full reference](/pages/ldoc/o/README.md "in Lua") - + - [Lua full reference](/pages/ldoc/o/README.md "in Lua") + - Misc - [Changelog](CHANGELOG) - [Contributing](CONTRIBUTING) diff --git a/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.data.json b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.data.json new file mode 100644 index 000000000..353e57f69 --- /dev/null +++ b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.data.json @@ -0,0 +1,19 @@ +{ + "my_array": [ + { + "data": { + "key": "value" + } + }, + { + "other_data": { + "other_key": [ + "other_value_1", + "other_value_2" + ] + } + } + ], + "one": 1, + "filter": "other_value_2" +} diff --git a/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.out.json b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.out.json new file mode 100644 index 000000000..99bd304ac --- /dev/null +++ b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.out.json @@ -0,0 +1 @@ +{"output":["long_array"],"res":["value"],"res_foreach":["other_value_2"]} diff --git a/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.zen b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.zen new file mode 100644 index 000000000..430509397 --- /dev/null +++ b/docs/examples/zencode_cookbook/branching/nested_if_in_foreach.zen @@ -0,0 +1,46 @@ +Given I have a 'string array' named 'my_array' +Given I have a 'number' named 'one' +Given I have a 'string' named 'filter' +When I create the 'string array' named 'res' +When I create the 'string array' named 'res_foreach' +If I verify 'my_array' is found + If I verify size of 'my_array' is more than 'one' + Then print the string 'long array' + EndIf + Foreach 'el' in 'my_array' + If I verify 'data' is found in 'el' + When I pickup from path 'el.data' + If I verify 'other_key' is found in 'data' + When I pickup from path 'data.other_key' + Foreach 'e' in 'other_key' + When I copy 'e' in 'res_foreach' + EndForeach + When I remove 'other_key' + EndIf + If I verify 'key' is found in 'data' + When I move 'key' from 'data' in 'res' + EndIf + When I remove 'data' + EndIf + If I verify 'other_data' is found in 'el' + When I pickup from path 'el.other_data' + If I verify 'other_key' is found in 'other_data' + When I pickup from path 'other_data.other_key' + Foreach 'e' in 'other_key' + If I verify 'e' is equal to 'filter' + When I copy 'e' in 'res_foreach' + EndIf + When done + EndForeach + When I remove 'other_key' + EndIf + If I verify 'key' is found in 'other_data' + When I move 'key' from 'other_data' in 'res' + EndIf + When I remove 'other_data' + EndIf + EndForeach +EndIf + +Then print 'res' +Then print 'res_foreach' diff --git a/docs/examples/zencode_cookbook/branching/number_comparison.zen b/docs/examples/zencode_cookbook/branching/number_comparison.zen index 5efdcc67d..f335a4179 100644 --- a/docs/examples/zencode_cookbook/branching/number_comparison.zen +++ b/docs/examples/zencode_cookbook/branching/number_comparison.zen @@ -33,6 +33,7 @@ If I verify 'number_lower' is equal to 'number_higher' When I create the random 'random_this_is_impossible' Then print string 'the conditions can never be satisfied' Endif +EndIf # You can also check if an object exists at a certain point of the execution, with the statement: # If I verify 'objectName' is found diff --git a/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.data.json b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.data.json new file mode 100644 index 000000000..5daaf62bf --- /dev/null +++ b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.data.json @@ -0,0 +1,3 @@ +{ + "numbers": [12, 75, 150, 180, 145, 525, 50] +} diff --git a/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.keys.json b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.keys.json new file mode 100644 index 000000000..b6d4a6793 --- /dev/null +++ b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.keys.json @@ -0,0 +1,6 @@ +{ + "0": 0, + "5": 5, + "150": 150, + "500": 500 +} diff --git a/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.out.json b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.out.json new file mode 100644 index 000000000..6e0017ae9 --- /dev/null +++ b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.out.json @@ -0,0 +1 @@ +{"res":[75,150,145]} diff --git a/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.zen b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.zen new file mode 100644 index 000000000..1ec548d53 --- /dev/null +++ b/docs/examples/zencode_cookbook/educational/foreach_divisible_by_five.zen @@ -0,0 +1,31 @@ +# Problem: +# display only those numbers from a list that satisfy the following conditions +# 1. The number must be divisible by five +# 2. If the number is greater than 150, then skip it and move to the following number +# 3. If the number is greater than 500, then stop the loop + +Given I have a 'number array' named 'numbers' +Given I have a 'number' named '0' +Given I have a 'number' named '5' +Given I have a 'number' named '150' +Given I have a 'number' named '500' + +When I create the 'number array' named 'res' + +Foreach 'num' in 'numbers' + # point 3 + If I verify number 'num' is more than '500' + When I break foreach + EndIf + # point 2 + If I verify number 'num' is less or equal than '150' + When I create the result of 'num' % '5' + # point 1 + If I verify 'result' is equal to '0' + When I copy 'num' in 'res' + EndIf + When I remove 'result' + EndIf +EndForeach + +Then print the 'res' diff --git a/docs/examples/zencode_cookbook/foreach/verysimple.data b/docs/examples/zencode_cookbook/foreach/verysimple.data new file mode 100644 index 000000000..298874685 --- /dev/null +++ b/docs/examples/zencode_cookbook/foreach/verysimple.data @@ -0,0 +1,3 @@ +{ + "xs": ["a", "b"] +} diff --git a/docs/examples/zencode_cookbook/foreach/verysimple.out b/docs/examples/zencode_cookbook/foreach/verysimple.out new file mode 100644 index 000000000..cdd83365f --- /dev/null +++ b/docs/examples/zencode_cookbook/foreach/verysimple.out @@ -0,0 +1 @@ +{"new_array":["a","b"]} diff --git a/docs/examples/zencode_cookbook/foreach/verysimple.zen b/docs/examples/zencode_cookbook/foreach/verysimple.zen new file mode 100644 index 000000000..b579593ef --- /dev/null +++ b/docs/examples/zencode_cookbook/foreach/verysimple.zen @@ -0,0 +1,6 @@ +Given I have a 'string array' named 'xs' +When I create the 'string array' named 'new array' +Foreach 'x' in 'xs' +When I move 'x' in 'new array' +EndForeach +Then print 'new array' diff --git a/docs/pages/zencode-foreach-endforeach.md b/docs/pages/zencode-foreach-endforeach.md new file mode 100644 index 000000000..dffff527a --- /dev/null +++ b/docs/pages/zencode-foreach-endforeach.md @@ -0,0 +1,95 @@ + +# Looping: Foreach/EndForeach statements + +Zenroom allows looping, done using the **Foreach** and **EndForeach** statements. +There are two possible way of looping that are, over an array +```gherkin +Foreach 'element' in 'array' +``` +or from a number to another with certain step +```gherkin +Foreach 'i' in sequence from 'zero' to 'ten' with step 'one' +``` +As can be seen the keyword **Foreach** is the one that indicates the start of a loop, while +the statement **EndForeach** indicates the ends of loop. +When reaching it if the condition of the foreach is still valid the loop is performed again. + +Inside a **Foreach** you can use: +* **Foreach** statements (any of them must have a corresponding **EndForeach**) +* [**If**](zencode-if-endif) statements that must be closed before the loop end. Morever, even if the basic **If** support +the use of **Then** in it, when it is inside a loop this last property is not true anymore, only **When** statements can be used. +* [**When**](zencode-cookbook-when) statements + +## Simple Foreach/EndForeach example + +In the following script we are looping over all the elements of an array and simply copying them into a new one. + +[](../_media/examples/zencode_cookbook/foreach/verysimple.zen ':include :type=code gherkin') + +Indeed if run with data + +[](../_media/examples/zencode_cookbook/foreach/verysimple.data ':include :type=code json') + +the result will be + +[](../_media/examples/zencode_cookbook/foreach/verysimple.out ':include :type=code json') + + +## Break from a Foreach loop + +The +```gherkin +When I break the foreach +# or equivalently +When I exit the foreach +``` +statements allow you to exit a foreach loop prematurely. When executed, it immediately terminates the loop's iteration, skipping +any remaining items in the collection or sequence. The program then continues with the first statement following the loop. + +The break statement is typically used when a specific condition is met within the loop, +and further iteration is unnecessary or undesirable. An example to understand the foreach and the break can be the following: + +Display only those numbers from the list: + +[](../_media/examples/zencode_cookbook/educational/foreach_divisible_by_five.data.json ':include :type=code json') + +that satisfy the following conditions: +1. The number must be divisible by five +1. If the number is greater than 150, then skip it and move to the following number +1. If the number is greater than 500, then stop the loop + +We start by defineing some usefull variables in a file that we will use as keys file + +[](../_media/examples/zencode_cookbook/educational/foreach_divisible_by_five.keys.json ':include :type=code json') + +then the code will be + +[](../_media/examples/zencode_cookbook/educational/foreach_divisible_by_five.zen ':include :type=code gherkin') + +resulting in + +[](../_media/examples/zencode_cookbook/educational/foreach_divisible_by_five.out.json ':include :type=code json') + +## More complex Foreach/EndForeach example + +You can play with them as much as you want, like: + +[](../_media/examples/zencode_cookbook/branching/nested_if_in_foreach.zen ':include :type=code gherkin') + +that with input data + +[](../_media/examples/zencode_cookbook/branching/nested_if_in_foreach.data.json ':include :type=code json') + +will result in + +[](../_media/examples/zencode_cookbook/branching/nested_if_in_foreach.out.json ':include :type=code json') + +# The script used to create the material in this page + +All the smart contracts and the data you see in this page are generated by the scripts [branching.bats](https://github.com/dyne/Zenroom/blob/master/test/zencode/branching.bats), +[foreach.bats](https://github.com/dyne/Zenroom/blob/master/test/zencode/foreach.bats) and +[branching.bats](https://github.com/dyne/Zenroom/blob/master/test/zencode/educational.bats). +If you want to run the scripts (on Linux) you should: + - *git clone https://github.com/dyne/Zenroom.git* + - install **jq** + - download a [zenroom binary](https://zenroom.org/#downloads) and place it */bin* or */usr/bin* or in *./Zenroom/src* diff --git a/docs/pages/zencode-if-endif.md b/docs/pages/zencode-if-endif.md index 207bc029b..0253f6406 100755 --- a/docs/pages/zencode-if-endif.md +++ b/docs/pages/zencode-if-endif.md @@ -1,32 +1,33 @@ # Branching: If/Endif statements -Zenroom allows branching, done using the ***If*** and ***Endif*** Statements. - -The **first** ***If*** statement creates a *conditional branch* and can be *stacked*: you can have multiple conditions following each other, something like: - -* If (condition1) - * If (condition2) - * If (condition3) - * When (do something) - * Then (print something) -* Endif - -The statement ***Endif*** ends the conditional branch, so takes the execution back to the *main* execution branch, independently on the amount of conditions (on how many ***If*** you have stacked together). - -You can not use a ***When*** or ***Then*** statement, inside a stack of ***If***, so something like this will return an error: - -* If (condition1) -* When (do something) - * If (condition2) - * Then (print something) - * If (condition3) - * Then (print something) -* Endif - - - -## Simple If/Endif example +Zenroom allows branching, done using the **If** and **Endif** Statements. + +The *conditional branch* starts with a **If** statement and ends when the corresponding **EndIf** is found, +the **EndIf** close always only the latest opened **If**. For example + +```gherking +If (condition1) + When (do something) + If (condition2) + If (condition3) + When (do something else) + Then (print something) + EndIf # ends conditional branching of condition 3 + Then (print something else) + EndIf # ends conditional branching of condition 2 +Endif # ends conditional branching of condition 1 +``` + +Inside a **If** you can use: +* **If** statements +* [**When**](zencode-cookbook-when) statements +* [**Then**](zencode-cookbook-then) statements +* [**Foreach**](zencode-foreach-endforeach) statementes (any of them must have a corresponding **EndForeach** +inside the conditional branch), pay attention that inside a **Foreach** you can not use the **Then** statements, +even if it is inside a **If** branching. + +## Simple If/Endif example We'll use a simple JSON file with two numbers as input: @@ -38,7 +39,7 @@ And in this script we can see some simple comparisons between numbers: * two stacked conditions, where the second one will fail * a simple condition, based on output from a the previous branch, that will succeed -The script goes: +The script goes: [](../_media/examples/zencode_cookbook/branching/number_comparison.zen ':include :type=code gherkin') @@ -64,28 +65,25 @@ You should expect an output like this: [](../_media/examples/zencode_cookbook/branching/complex_comparison_output.json ':include :type=code json') -## Statements that can be used as a condition +## Statements that can be used as a condition -Only some statements can be used as a condition, typically the ones that **verify** a value or check do cryptographic checks, specifically: +Only a subset of the **When** statements can be used to create a conditional branch, that are the one that, +after the `When I` part, start with the **verify** keyword. They can be both cryptographic statements +like a singature verification -* all the **verify** statements -* all the **is less than** and **is more than** statements -* all the **is less or equal than** and **is more or equal than** statements -* all the **is equal to** and **is not equal to** statements -* all the **is found** and **is not found** statements +```gherkin +When I verify '' has a eddsa signature in '' by '' +``` +or, as seen above, more simpler check, like number comparison +```gherkin +When I verify number '' is less than '' +``` # The script used to create the material in this page -All the smart contracts and the data you see in this page are generated by the scripts [branching.bats](https://github.com/dyne/Zenroom/blob/master/test/zencode/branching.bats). If you want to run the scripts (on Linux) you should: +All the smart contracts and the data you see in this page are generated by the scripts [branching.bats](https://github.com/dyne/Zenroom/blob/master/test/zencode/branching.bats). If you want to run the scripts (on Linux) you should: - *git clone https://github.com/dyne/Zenroom.git* - install **jq** - download a [zenroom binary](https://zenroom.org/#downloads) and place it */bin* or */usr/bin* or in *./Zenroom/src* - - - - diff --git a/test/zencode/branching.bats b/test/zencode/branching.bats index 7373fab53..2c1d695ee 100644 --- a/test/zencode/branching.bats +++ b/test/zencode/branching.bats @@ -739,8 +739,8 @@ EOF cat << EOF | zexe nested_if_in_foreach.zen nested_if_in_foreach.data.json Given I have a 'string array' named 'my_array' -and I have a 'number' named 'one' -and I have a 'string' named 'filter' +Given I have a 'number' named 'one' +Given I have a 'string' named 'filter' When I create the 'string array' named 'res' When I create the 'string array' named 'res_foreach' If I verify 'my_array' is found diff --git a/test/zencode/foreach.bats b/test/zencode/foreach.bats index 92ac85aad..975ccfebe 100644 --- a/test/zencode/foreach.bats +++ b/test/zencode/foreach.bats @@ -11,13 +11,13 @@ EOF cat << EOF | zexe verysimple.zen verysimple.data Given I have a 'string array' named 'xs' -When I create the new array +When I create the 'string array' named 'new array' Foreach 'x' in 'xs' When I move 'x' in 'new array' EndForeach -Then print 'new array' as 'string' +Then print 'new array' EOF - save_output "very_simple.out" + save_output "verysimple.out" assert_output '{"new_array":["a","b"]}' }