From ff35b91cc06313a2f30a744fa994c5ee9bd4572e Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 5 Dec 2025 19:19:38 +0100 Subject: [PATCH 01/14] feat!: adding 'mut' attribute to the functions' arguments that needed it --- Cli.ark | 2 +- List.ark | 10 +++++----- Random.ark | 2 +- Range.ark | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cli.ark b/Cli.ark index ff726dd..a4dfe1b 100644 --- a/Cli.ark +++ b/Cli.ark @@ -108,7 +108,7 @@ (+ headers synopsis "\n\nOPTIONS\n" options) })) -(let _match_group (fun (parsed args param) { +(let _match_group (fun (parsed (mut args) param) { (mut missing_param false) (mut i 0) (while (and (not missing_param) (< i (len param.children))) { diff --git a/List.ark b/List.ark index 28a477d..c4d508d 100644 --- a/List.ark +++ b/List.ark @@ -355,7 +355,7 @@ # (print (list:take [1 2 3 4 5 6 7 8 9] 4)) # [1 2 3 4] # =end # @author https://github.com/rstefanic -(let take (fun (_L _n) { +(let take (fun (_L (mut _n)) { (mut _index 0) (mut _output []) (set _n (math:min _n (len _L))) @@ -376,14 +376,14 @@ (let takeWhile (fun (_L _f) { (mut _index 0) (mut _output []) - (mut continue true) + (mut _continue true) - (while (and (< _index (len _L)) continue) + (while (and (< _index (len _L)) _continue) (if (_f (@ _L _index)) { (append! _output (@ _L _index)) (set _index (+ 1 _index)) } - (set continue false))) + (set _continue false))) _output })) # @brief Partition a list in two, given a predicate @@ -627,7 +627,7 @@ # (print (list:insert b 1 [1 2])) # [0 1 2 9] # =end # @author https://github.com/SuperFola -(let insert (fun (_L _index _value) { +(let insert (fun (_L _index (mut _value)) { (let _size (len _L)) (assert (<= _index _size) "list:insert can not insert a value outside the list") diff --git a/Random.ark b/Random.ark index ad38f18..6ecf4c0 100644 --- a/Random.ark +++ b/Random.ark @@ -21,7 +21,7 @@ # (let randomized (random:shuffle data)) # =end # @author https://github.com/SuperFola -(let shuffle (fun (_L) { +(let shuffle (fun ((mut _L)) { (mut _output []) (while (not (empty? _L)) { diff --git a/Range.ark b/Range.ark index 0a6b146..bec4f7c 100644 --- a/Range.ark +++ b/Range.ark @@ -12,7 +12,7 @@ # (print (obj.asList)) # [1 2 3 4 5 6 7 8 9] # =end # @author https://github.com/SuperFola -(let range (fun (i _b) { +(let range (fun ((mut i) _b) { (let asList (fun () { # i and _b are going to be captured by the caller (mut _output []) From 53f4ca692769efad3fb3aebf9bc495782468fb40 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 8 Dec 2025 19:17:15 +0100 Subject: [PATCH 02/14] feat(io): adding io:readLinesFile --- IO.ark | 8 ++++++++ tests/io-tests.ark | 1 + 2 files changed, 9 insertions(+) diff --git a/IO.ark b/IO.ark index e3338ed..6d72341 100644 --- a/IO.ark +++ b/IO.ark @@ -24,6 +24,14 @@ # @author https://github.com/SuperFola (let readFile (fun (_name) (builtin__io:readFile _name))) +# @brief Read the content from a file as a List of Strings +# @param filename the path of the file to read +# =begin +# (io:readLinesFile "hello.json") +# =end +# @author https://github.com/SuperFola +(let readLinesFile (fun (_name) (builtin__io:readLinesFile _name))) + # @brief Check if a file exists, return True or False # @param filename the path of the file # =begin diff --git a/tests/io-tests.ark b/tests/io-tests.ark index 2d034a6..1d0a706 100644 --- a/tests/io-tests.ark +++ b/tests/io-tests.ark @@ -25,6 +25,7 @@ (io:writeFile "test.txt" "hello, world!") (test:expect (io:fileExists? "test.txt")) (test:eq (io:readFile "test.txt") "hello, world!") + (test:eq (io:readLinesFile "test.txt") ["hello, world!"]) (io:appendToFile "test.txt" "bis") (test:eq (io:readFile "test.txt") "hello, world!bis") (test:expect (> (len (io:listFiles "./")) 0)) From 7d564045ac95ccbe768dd7384a19298cc89227dc Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 8 Dec 2025 19:17:43 +0100 Subject: [PATCH 03/14] feat(arg attr): adding ref attributes --- List.ark | 61 +++++++++++++++++++++++++++--------------------------- Math.ark | 4 ++++ String.ark | 2 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/List.ark b/List.ark index c4d508d..f15582e 100644 --- a/List.ark +++ b/List.ark @@ -88,7 +88,7 @@ # (print element) })) # =end # @author https://github.com/SuperFola -(let forEach (fun (_L _func) { +(let forEach (fun ((ref _L) _func) { (mut _index 0) (while (< _index (len _L)) { @@ -106,7 +106,7 @@ # (print idx " " element) })) # =end # @author https://github.com/SuperFola -(let enumerate (fun (_L _func) { +(let enumerate (fun ((ref _L) _func) { (mut _index 0) (while (< _index (len _L)) { @@ -122,7 +122,7 @@ # (let p (list:product collection)) # => 120 # =end # @author https://github.com/Unactived -(let product (fun (_L) { +(let product (fun ((ref _L)) { (mut _index 0) (mut _output 1) @@ -139,7 +139,7 @@ # (let p (list:sum collection)) # => 20 # =end # @author https://github.com/Unactived -(let sum (fun (_L) { +(let sum (fun ((ref _L)) { (mut _index 0) (mut _output 0) @@ -155,7 +155,7 @@ # (let value (list:min [0 1 2 3 5 8])) # 0 # =end # @author https://github.com/SuperFola -(let min (fun (_L) { +(let min (fun ((ref _L)) { (mut _index 0) (mut _output nil) @@ -172,7 +172,7 @@ # (let value (list:max [0 1 2 3 5 8])) # 8 # =end # @author https://github.com/SuperFola -(let max (fun (_L) { +(let max (fun ((ref _L)) { (mut _index 0) (mut _output nil) @@ -193,7 +193,7 @@ # (print (list:drop cool-stuff 4)) # [5 6 7 8 9] # =end # @author https://github.com/rstefanic, https://github.com/SuperFola -(let drop (fun (_L _n) +(let drop (fun ((ref _L) _n) (if (< _n (/ (len _L) 2)) (if (> _n 0) (drop (tail _L) (- _n 1)) @@ -216,7 +216,7 @@ # (print (list:dropWhile cool-stuff (fun (a) (< a 4)))) # [4 5 6 7 8 9] # =end # @author https://github.com/SuperFola -(let dropWhile (fun (_L _f) { +(let dropWhile (fun ((ref _L) _f) { (mut _index 0) (mut _output []) @@ -237,7 +237,7 @@ # (print (list:filter [1 2 3 4 5 6 7 8 9] math:even?)) # [2 4 6 8] # =end # @author https://github.com/rstefanic -(let filter (fun (_L _f) { +(let filter (fun ((ref _L) _f) { (mut _index 0) (mut _output []) @@ -256,15 +256,16 @@ # (print sorted) # [[3 5] [10 14] [12 18] [16 20]] # =end # @author https://github.com/SuperFola -(let sortByKey (fun (_L _key) { +(let sortByKey (fun ((ref _L) _key) { (if (empty? _L) [] { (let _pivot_val (head _L)) (let _pivot (_key _pivot_val)) - (mut _less (sortByKey (filter (tail _L) (fun (e) (< (_key e) _pivot))) _key)) - (let _more (sortByKey (filter (tail _L) (fun (e) (>= (_key e) _pivot))) _key)) + (mut _rest (tail _L)) + (mut _less (sortByKey (filter _rest (fun (e) (< (_key e) _pivot))) _key)) + (let _more (sortByKey (filter _rest (fun (e) (>= (_key e) _pivot))) _key)) (concat! _less [_pivot_val] _more) _less }) })) @@ -277,7 +278,7 @@ # (print (list:map [1 2 3 4 5 6 7 8 9] (fun (e) (* e e)))) # [1 4 9 25 36 49 64 81] # =end # @author https://github.com/rstefanic -(let map (fun (_L _f) { +(let map (fun ((ref _L) _f) { (mut _index 0) (mut _output []) @@ -295,7 +296,7 @@ # (print (list:reduce cool (fun (a b) (+ a b)))) # 45 # =end # @author https://github.com/Unactived -(let reduce (fun (_L _f) { +(let reduce (fun ((ref _L) _f) { (mut _index 1) (mut _output (@ _L 0)) @@ -312,7 +313,7 @@ # (print (list:flatten cool)) # [1 2 3 4 5 6 7 8 9] # =end # @author https://github.com/SuperFola -(let flatten (fun (_L) { +(let flatten (fun ((ref _L)) { (mut _index 0) (mut _output []) @@ -334,7 +335,7 @@ # (print (list:flatMap cool (fun (a) [a a]))) # [1 1 2 2 3 3 4 4] # =end # @author https://github.com/SuperFola -(let flatMap (fun (_L _f) { +(let flatMap (fun ((ref _L) _f) { (mut _index 0) (mut _output []) @@ -355,7 +356,7 @@ # (print (list:take [1 2 3 4 5 6 7 8 9] 4)) # [1 2 3 4] # =end # @author https://github.com/rstefanic -(let take (fun (_L (mut _n)) { +(let take (fun ((ref _L) (mut _n)) { (mut _index 0) (mut _output []) (set _n (math:min _n (len _L))) @@ -373,7 +374,7 @@ # (print (list:takeWhile [1 2 3 4 5 6 7 8 9 10] (fun (a) (< a 4)))) # [1 2 3] # =end # @author https://github.com/rakista112 -(let takeWhile (fun (_L _f) { +(let takeWhile (fun ((ref _L) _f) { (mut _index 0) (mut _output []) (mut _continue true) @@ -395,7 +396,7 @@ # (print (list:partition a (fun (c i) (= 0 (mod c 2))))) # [[2] [1 3]] # =end # @author https://github.com/rakista112 -(let partition (fun (_L _f) { +(let partition (fun ((ref _L) _f) { (mut _index 0) (mut _pass []) (mut _fail []) @@ -417,7 +418,7 @@ # (print (list:unzip zipped)) # [[1 2 3 4] [5 6 7 8]] # =end # @author https://github.com/Unactived -(let unzip (fun (_L) { +(let unzip (fun ((ref _L)) { (let _m (len _L)) (mut _list1 []) (mut _list2 []) @@ -440,7 +441,7 @@ # (print (list:zip a b)) # [[1 5] [2 6] [3 7] [4 8]] # =end # @author https://github.com/Unactived -(let zip (fun (_a _b) { +(let zip (fun ((ref _a) (ref _b)) { (let _m (math:min (len _a) (len _b))) (mut _c []) (mut _index 0) @@ -458,7 +459,7 @@ # (print (list:zipWithIndex a)) # [[0 5] [1 6] [2 7] [3 8]] # =end # @author https://github.com/SuperFola -(let zipWithIndex (fun (_L) { +(let zipWithIndex (fun ((ref _L)) { (mut _output []) (mut _index 0) (let _len (len _L)) @@ -477,7 +478,7 @@ # (print (list:foldLeft a 0 (fun (a b) (+ a b)))) # 10 # =end # @author https://github.com/SuperFola -(let foldLeft (fun (_L _init _f) { +(let foldLeft (fun ((ref _L) _init _f) { (mut _index 0) (mut _val _init) @@ -495,7 +496,7 @@ # (print (list:forAll a f)) # true # =end # @author https://github.com/Gryfenfer97 -(let forAll (fun (_L _f) { +(let forAll (fun ((ref _L) _f) { (mut _verified true) (mut _index 0) @@ -514,7 +515,7 @@ # (print (list:any a f)) # true # =end # @author https://github.com/Gryfenfer97 -(let any (fun (_L _f) { +(let any (fun ((ref _L) _f) { (mut _verified false) (mut _index 0) @@ -534,7 +535,7 @@ # (print (list:none [4 5 6] f)) # true # =end # @author https://github.com/SuperFola -(let none (fun (_L _f) (not (any _L _f)))) +(let none (fun ((ref _L) _f) (not (any _L _f)))) # @brief Count the number of elements in a list that match a condition # @param _L the list to work on @@ -545,7 +546,7 @@ # (print (list:countIf lst is_even)) # 4 # =end # @author https://github.com/SuperFola -(let countIf (fun (_L _f) { +(let countIf (fun ((ref _L) _f) { (let _inner (fun (lst cond acc) (if (not (empty? lst)) (_inner @@ -595,7 +596,7 @@ # (print (list:chunkBy indices 3)) # [[1 2 3] [4 5 6] [7 8 9]] # =end # @author https://github.com/SuperFola -(let chunkBy (fun (_L _length) { +(let chunkBy (fun ((ref _L) _length) { (assert (> _length 0) "list:chunkBy needs a chunk size of at least 1") (mut _output []) (mut _current []) @@ -627,7 +628,7 @@ # (print (list:insert b 1 [1 2])) # [0 1 2 9] # =end # @author https://github.com/SuperFola -(let insert (fun (_L _index (mut _value)) { +(let insert (fun ((ref _L) _index (mut _value)) { (let _size (len _L)) (assert (<= _index _size) "list:insert can not insert a value outside the list") @@ -658,7 +659,7 @@ # # [3 4 5] # =end # @author https://github.com/SuperFola -(let window (fun (_L _size _f) { +(let window (fun ((ref _L) _size _f) { (assert (> _size 0) "window size must be at least 1") (mut _i 0) diff --git a/Math.ark b/Math.ark index d2058a6..cddda8d 100644 --- a/Math.ark +++ b/Math.ark @@ -428,3 +428,7 @@ (set _i (+ 1 _i)) }) _result })) + +(let asPercentage (fun (_a _b) + (* 100 (- (/ _a _b) 1)))) + diff --git a/String.ark b/String.ark index 6e30120..dc8a154 100644 --- a/String.ark +++ b/String.ark @@ -276,7 +276,7 @@ # @brief Split a string in multiple substrings in a list, given a separator # @param _string the string to split # @param _separator the separator to use for splitting -# @details Returns a list of strings. Example : +# @details Returns a list of strings # =begin # (let message "hello world, I like boats") # (let as-list (string:split message " ")) From 27c6cfe0ecd2c010ed03e2989d182129df68be9a Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 8 Dec 2025 19:26:21 +0100 Subject: [PATCH 04/14] feat(math): adding math:improvementRatioPercentage --- Math.ark | 14 ++++++++++++-- tests/math-tests.ark | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Math.ark b/Math.ark index cddda8d..47ce917 100644 --- a/Math.ark +++ b/Math.ark @@ -429,6 +429,16 @@ _result })) -(let asPercentage (fun (_a _b) +# @brief Compute a percentage of how much b is better than a +# @details Returns a positive number when _a is bigger than _b, negative otherwise +# @param _a the base +# @param _b new measure to compare +# =begin +# (let base 55) # something takes 55ms to run +# (let new 43) # now it takes 43ms +# (print (math:improvementRatioPercentage base new)) # 27.9069767442 +# # 'base' is 27%~ slower than 'new' +# =end +# @author https://github.com/SuperFola +(let improvementRatioPercentage (fun (_a _b) (* 100 (- (/ _a _b) 1)))) - diff --git a/tests/math-tests.ark b/tests/math-tests.ark index 3271bbc..4db46f1 100644 --- a/tests/math-tests.ark +++ b/tests/math-tests.ark @@ -127,4 +127,8 @@ (test:case "vectors" { (test:eq 32 (math:dotProduct [1 2 3] [4 5 6])) - (test:eq 3 (math:dotProduct [1 3 -5] [4 -2 -1])) })}) + (test:eq 3 (math:dotProduct [1 3 -5] [4 -2 -1])) }) + + (test:eq 0 (math:improvementRatioPercentage 12 12)) + (test:eq 100 (math:improvementRatioPercentage 24 12)) + (test:eq 50 (math:improvementRatioPercentage 18 12)) }) From 5bc5619ac4427118e7664713a6e4c135319ef67f Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Tue, 9 Dec 2025 18:02:55 +0100 Subject: [PATCH 05/14] feat(macros): add unpackPair --- Macros.ark | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Macros.ark b/Macros.ark index 35c34a6..445300b 100644 --- a/Macros.ark +++ b/Macros.ark @@ -116,3 +116,20 @@ # =end # @author https://github.com/SuperFola (macro until (cond body) (while (not cond) body)) + +# @brief Unpack a pair of two elements into two new variables +# @param pair list of two elements to unpack +# @param outx name of the variable which will hold the first element +# @param outy name of the variable which will hold the second element +# =begin +# (let data [[1, 2], [2, 3], [5, 8]]) +# (list:forEach data +# (fun (pair) { +# (unpackPair pair x y) +# (print (+ x y)) })) +# =end +# @author https://github.com/SuperFola +(macro unpackPair (pair outx outy) { + (let outx ($as-is (@ pair 0))) + (let outy ($as-is (@ pair 1))) }) + From 256152a05d2ad03f4f36872ce647f6fd9be5fafb Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Thu, 11 Dec 2025 12:26:18 +0100 Subject: [PATCH 06/14] chore(string): adding a test for negative indexes with string:removeAt --- tests/string-tests.ark | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/string-tests.ark b/tests/string-tests.ark index cbc3bfc..f0992d9 100644 --- a/tests/string-tests.ark +++ b/tests/string-tests.ark @@ -7,6 +7,7 @@ (test:eq (builtin__string:find "aaacd" "a" 1) (string:findAfter "aaacd" "a" 1)) (test:eq (builtin__string:removeAt "abcd" 1) (string:removeAt "abcd" 1)) + (test:eq (builtin__string:removeAt "abcd" -1) "abc") (test:eq (builtin__string:setAt "abcd" 1 "z") (string:setAt "abcd" 1 "z")) (test:expect (string:contains? "hello, world" "h")) From 7603c80bb2de3cdc3b11ff1762955349ea4adf21 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 13:51:58 +0100 Subject: [PATCH 07/14] fix(testing): do not reevaluate expressions when we encounter a failure --- Testing.ark | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Testing.ark b/Testing.ark index ef06abc..d67cc8f 100644 --- a/Testing.ark +++ b/Testing.ark @@ -198,8 +198,9 @@ # =end # @author https://github.com/SuperFola (macro test:expect (_cond ..._desc) { - (if (!= true ($as-is _cond)) - (testing:_report_error true ($as-is _cond) "true" ($repr _cond) _desc) + (mut _cond_res ($as-is _cond)) + (if (!= true _cond_res) + (testing:_report_error true _cond_res "true" ($repr _cond) _desc) (testing:_report_success)) }) # @brief Compare two values that should be equal and generate a test case @@ -213,9 +214,11 @@ # =end # @author https://github.com/SuperFola (macro test:eq (_expr _expected ..._desc) { - (if (= ($as-is _expected) ($as-is _expr)) + (mut _expected_res ($as-is _expected)) + (mut _expr_res ($as-is _expr)) + (if (= _expected_res _expr_res) (testing:_report_success) - (testing:_report_error ($as-is _expected) ($as-is _expr) ($repr _expected) ($repr _expr) _desc)) }) + (testing:_report_error _expected_res _expr_res ($repr _expected) ($repr _expr) _desc)) }) # @brief Compare two values that should **not** be equal and generate a test case # @param _value tested value @@ -227,9 +230,11 @@ # =end # @author https://github.com/SuperFola (macro test:neq (_value _unexpected ..._desc) { - (if (!= ($as-is _unexpected) ($as-is _value)) + (mut _unexpected_res ($as-is _unexpected)) + (mut _value_res ($as-is _value)) + (if (!= _unexpected_res _value_res) (testing:_report_success) - (testing:_report_error ($as-is _unexpected) ($as-is _value) ($repr _unexpected) ($repr _value) _desc)) }) + (testing:_report_error _unexpected_res _value_res ($repr _unexpected) ($repr _value) _desc)) }) # @brief Generate the code for a test suite # @details Create two variables: _name-output (a list: [successes, failures]) and _name-status (boolean, true on success) From 08a660e69c8762e738e6e8b20b1913814cc59f95 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 18:34:40 +0100 Subject: [PATCH 08/14] feat(list): adding support for early returns in forEach/enumerate/window --- List.ark | 52 +++++++++++++++++++++++++++++++++++++++------------- Macros.ark | 2 +- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/List.ark b/List.ark index f15582e..a1a1f6a 100644 --- a/List.ark +++ b/List.ark @@ -78,10 +78,16 @@ # @author https://github.com/SuperFola (let setAt (fun (_L _index _x) (builtin__list:setAt _L _index _x))) +(let _stopIteration_helper (fun () (assert false "This shouldn't be called"))) + +# @brief Value to return from functions passed to forEach, enumerate, window... to stop iteration early +# @author https://github.com/SuperFola +(let stopIteration _stopIteration_helper) + # @brief Iterate over a given list and run a given function on every element. # @param _L the list to iterate over -# @param _func the function to call on each element -# @details The original list is not modified. +# @param _func the function to call on each element. It can return list:stopIteration to stop iteration early +# @details The original list is not modified. Returns true if it returns early, false otherwise # =begin # (let collection [1 2 5 12]) # (list:forEach collection (fun (element) { @@ -90,16 +96,22 @@ # @author https://github.com/SuperFola (let forEach (fun ((ref _L) _func) { (mut _index 0) + (mut _early false) (while (< _index (len _L)) { (mut _element (@ _L _index)) - (_func _element) - (set _index (+ 1 _index)) }) })) + (if (= (_func _element) stopIteration) + { + (set _index (len _L)) + (set _early true) }) + (set _index (+ 1 _index)) }) + + _early })) # @brief Iterate over a given list and run a given function on every element, passing its index as well. # @param _L the list to iterate over -# @param _func a binary function to call on each element with (index, element) -# @details The original list is not modified. +# @param _func a binary function to call on each element with (index, element). It can return list:stopIteration to stop iteration early +# @details The original list is not modified. Returns true if it returns early, false otherwise # =begin # (let collection [1 2 5 12]) # (list:enumerate collection (fun (idx element) { @@ -108,11 +120,18 @@ # @author https://github.com/SuperFola (let enumerate (fun ((ref _L) _func) { (mut _index 0) + (mut _early false) (while (< _index (len _L)) { (mut _element (@ _L _index)) - (_func _index _element) - (set _index (+ 1 _index)) }) })) + (if (= (_func _index _element) stopIteration) + { + (set _index (len _L)) + (set _early true) + }) + (set _index (+ 1 _index)) }) + + _early })) # @brief Iterate over a given list and multiply all the elements with the others. # @param _L the list to iterate over @@ -340,7 +359,7 @@ (mut _output []) (while (< _index (len _L)) { - (mut _res (_f (@ _L _index))) + (let _res (_f (@ _L _index))) (if (= "List" (type _res)) (concat! _output _res) @@ -647,10 +666,10 @@ (concat (head _parts) _value (@ _parts 1)) })) })) # @brief Create a sliding window of a given size on a list -# @details The original list is not modified +# @details The original list is not modified. Returns true if it returns early, false otherwise # @param _L list to iterate over # @param _size window size, must be at least 1 -# @param _f function to call with the window +# @param _f function to call with the window. It can return list:stopIteration to stop iteration early # =begin # (let f (fun (lst) (print lst)) # (list:window [1 2 3 4 5] 3 f) @@ -662,6 +681,7 @@ (let window (fun ((ref _L) _size _f) { (assert (> _size 0) "window size must be at least 1") + (mut _early false) (mut _i 0) (while (< _i (len _L)) { (mut _win []) @@ -670,11 +690,17 @@ (append! _win (@ _L (+ _i _j))) (set _j (+ 1 _j)) }) - (_f _win) + (if (= (_f _win) stopIteration) + { + (set _i (len _L)) + (set _early true) + }) (if (>= (+ _i _size) (len _L)) (set _i (len _L))) - (set _i (+ 1 _i)) }) })) + (set _i (+ 1 _i)) }) + + _early })) # @brief Transpose a list of lists or list of strings # @details The original list is not modified. Each element should have the same length diff --git a/Macros.ark b/Macros.ark index 445300b..fab345f 100644 --- a/Macros.ark +++ b/Macros.ark @@ -122,7 +122,7 @@ # @param outx name of the variable which will hold the first element # @param outy name of the variable which will hold the second element # =begin -# (let data [[1, 2], [2, 3], [5, 8]]) +# (let data [[1 2] [2 3] [5 8]]) # (list:forEach data # (fun (pair) { # (unpackPair pair x y) From 2b81a0c70eb754727587d723248ba95b147cb465 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 18:36:15 +0100 Subject: [PATCH 09/14] feat(list): adding list:unique --- List.ark | 18 ++++++++++++++++++ tests/list-tests.ark | 8 +++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/List.ark b/List.ark index a1a1f6a..207d7fd 100644 --- a/List.ark +++ b/List.ark @@ -727,3 +727,21 @@ (set _i (+ 1 _i)) }) _output })) +# @brief Get the unique values in a given list +# @details The original list is not modified. +# @param _L list to extract unique values from +# =begin +# (let data [1 1 2 3 4 3 4 5]) +# (print (list:unique data)) # [1 2 3 4 5] +# =end +# @author https://github.com/SuperFola +(let unique (fun ((ref _L)) { + (mut _vals (dict)) + (mut _i 0) + (while (< _i (len _L)) { + (let v (@ _L _i)) + (if (not (builtin__dict:contains _vals v)) + (builtin__dict:add _vals v _i)) + (set _i (+ 1 _i)) }) + + (builtin__dict:keys _vals) })) diff --git a/tests/list-tests.ark b/tests/list-tests.ark index 3f0e118..cd4dc8d 100644 --- a/tests/list-tests.ark +++ b/tests/list-tests.ark @@ -196,5 +196,11 @@ (test:eq (list:transpose data) [["1" " "] ["2" "4"] ["3" "5"] [" " " "] ["3" "6"] ["2" "4"] ["8" " "] [" " " "] [" " "3"] ["5" "8"] ["1" "7"]]) (test:eq (list:transpose (list:transpose zipped)) zipped) (test:eq (list:transpose [[]]) []) - (test:eq (list:transpose [[1]]) [[1]]) }) }) + (test:eq (list:transpose [[1]]) [[1]]) }) + + (test:case "unique" { + (test:eq (list:unique []) []) + (test:eq (list:unique [1 1 1 1]) [1]) + (test:eq (list:unique [1 -1 1 -1]) [1 -1]) + (test:eq (list:unique ["a" "b" "a" "b" "c" "d"]) ["a" "b" "c" "d"]) }) }) From 305a382060d93d8d24a2c1a68134ac0b6c4fd78a Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 18:37:15 +0100 Subject: [PATCH 10/14] fix(testing): test macros return a single node, so that we can use them in functions like (fun () (test:expect false)) --- Testing.ark | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Testing.ark b/Testing.ark index d67cc8f..352d4c2 100644 --- a/Testing.ark +++ b/Testing.ark @@ -197,11 +197,11 @@ # (test:expect (return_true) "return true"}) # =end # @author https://github.com/SuperFola -(macro test:expect (_cond ..._desc) { +(macro test:expect (_cond ..._desc) {{ (mut _cond_res ($as-is _cond)) (if (!= true _cond_res) (testing:_report_error true _cond_res "true" ($repr _cond) _desc) - (testing:_report_success)) }) + (testing:_report_success)) }}) # @brief Compare two values that should be equal and generate a test case # @param _expr computed value to test @@ -213,12 +213,12 @@ # (test:eq 5 (foo) "foo should return 5")}) # =end # @author https://github.com/SuperFola -(macro test:eq (_expr _expected ..._desc) { +(macro test:eq (_expr _expected ..._desc) {{ (mut _expected_res ($as-is _expected)) (mut _expr_res ($as-is _expr)) (if (= _expected_res _expr_res) (testing:_report_success) - (testing:_report_error _expected_res _expr_res ($repr _expected) ($repr _expr) _desc)) }) + (testing:_report_error _expected_res _expr_res ($repr _expected) ($repr _expr) _desc)) }}) # @brief Compare two values that should **not** be equal and generate a test case # @param _value tested value @@ -229,12 +229,12 @@ # (test:neq 0 (my_function 1 2 3))}) # =end # @author https://github.com/SuperFola -(macro test:neq (_value _unexpected ..._desc) { +(macro test:neq (_value _unexpected ..._desc) {{ (mut _unexpected_res ($as-is _unexpected)) (mut _value_res ($as-is _value)) (if (!= _unexpected_res _value_res) (testing:_report_success) - (testing:_report_error _unexpected_res _value_res ($repr _unexpected) ($repr _value) _desc)) }) + (testing:_report_error _unexpected_res _value_res ($repr _unexpected) ($repr _value) _desc)) }}) # @brief Generate the code for a test suite # @details Create two variables: _name-output (a list: [successes, failures]) and _name-status (boolean, true on success) From 9b3b2d8ffc7bc652d005a40f00531690c299b63b Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 19:10:27 +0100 Subject: [PATCH 11/14] fix(testing): evaluating the expressions once inside (mut) nodes caused problems ; storing them permanently and setting variables works better --- Testing.ark | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Testing.ark b/Testing.ark index 352d4c2..b597f85 100644 --- a/Testing.ark +++ b/Testing.ark @@ -198,11 +198,17 @@ # =end # @author https://github.com/SuperFola (macro test:expect (_cond ..._desc) {{ - (mut _cond_res ($as-is _cond)) - (if (!= true _cond_res) - (testing:_report_error true _cond_res "true" ($repr _cond) _desc) + (set testing:_cond_res ($as-is _cond)) + (if (!= true testing:_cond_res) + (testing:_report_error true testing:_cond_res "true" ($repr _cond) _desc) (testing:_report_success)) }}) +(mut _cond_res nil) +(mut _expr_res nil) +(mut _expected_res nil) +(mut _unexpected_res nil) +(mut _value_res nil) + # @brief Compare two values that should be equal and generate a test case # @param _expr computed value to test # @param _expected expected value @@ -214,11 +220,11 @@ # =end # @author https://github.com/SuperFola (macro test:eq (_expr _expected ..._desc) {{ - (mut _expected_res ($as-is _expected)) - (mut _expr_res ($as-is _expr)) - (if (= _expected_res _expr_res) + (set testing:_expected_res ($as-is _expected)) + (set testing:_expr_res ($as-is _expr)) + (if (= testing:_expected_res testing:_expr_res) (testing:_report_success) - (testing:_report_error _expected_res _expr_res ($repr _expected) ($repr _expr) _desc)) }}) + (testing:_report_error testing:_expected_res testing:_expr_res ($repr _expected) ($repr _expr) _desc)) }}) # @brief Compare two values that should **not** be equal and generate a test case # @param _value tested value @@ -230,11 +236,11 @@ # =end # @author https://github.com/SuperFola (macro test:neq (_value _unexpected ..._desc) {{ - (mut _unexpected_res ($as-is _unexpected)) - (mut _value_res ($as-is _value)) - (if (!= _unexpected_res _value_res) + (set testing:_unexpected_res ($as-is _unexpected)) + (set testing:_value_res ($as-is _value)) + (if (!= testing:_unexpected_res testing:_value_res) (testing:_report_success) - (testing:_report_error _unexpected_res _value_res ($repr _unexpected) ($repr _value) _desc)) }}) + (testing:_report_error testing:_unexpected_res testing:_value_res ($repr _unexpected) ($repr _value) _desc)) }}) # @brief Generate the code for a test suite # @details Create two variables: _name-output (a list: [successes, failures]) and _name-status (boolean, true on success) From 5779a46ce4f59c00786e258d2e82f9c6c810592b Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 19:34:30 +0100 Subject: [PATCH 12/14] feat(list): adding list:median --- List.ark | 19 ++++++++++++++++++- tests/list-tests.ark | 8 ++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/List.ark b/List.ark index 207d7fd..668ab12 100644 --- a/List.ark +++ b/List.ark @@ -201,7 +201,24 @@ (set _index (+ 1 _index)) }) _output })) -(import std.Math :min :max) +(import std.Math :min :max :even) + +# @brief Find the median in a list of numbers +# @param _L list of numbers +# @details The original list is not modified. +# =begin +# (let value (list:median [0 1 2 3 5 8])) # 2.5 +# =end +# @author https://github.com/SuperFola +(let median (fun ((ref _L)) { + (let _n (len _L)) + (if (= 0 _n) + nil + { + (let _s (sort _L)) + (if (math:even _n) + (/ (+ (@ _s (- (/ _n 2) 1)) (@ _s (/ _n 2))) 2) + (@ _s (/ _n 2))) }) })) # @brief Drop the first n elements of a list # @param _L the list to work on diff --git a/tests/list-tests.ark b/tests/list-tests.ark index cd4dc8d..8933b2f 100644 --- a/tests/list-tests.ark +++ b/tests/list-tests.ark @@ -64,6 +64,14 @@ (test:eq (list:max b) 6) (test:eq (list:max [-1]) -1) }) + (test:case "median" { + (test:eq (list:median []) nil) + (test:eq (list:median [1]) 1) + (test:eq (list:median [1 2]) 1.5) + (test:eq (list:median [2 1 3]) 2) + (test:eq (list:median [1 2 2 1]) 1.5) + (test:eq (list:median [1 2 2 1 3]) 2) }) + (test:case "drop" { (test:eq (list:drop a 0) [1 2 3]) (test:eq (list:drop a 1) [2 3]) From 40af0a32b5f0226d8c0d0bde4316ab57aaed944b Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 19:34:53 +0100 Subject: [PATCH 13/14] feat(lib): adding std.Benchmark, with macros to run benchmarks --- Benchmark.ark | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Benchmark.ark diff --git a/Benchmark.ark b/Benchmark.ark new file mode 100644 index 0000000..9290101 --- /dev/null +++ b/Benchmark.ark @@ -0,0 +1,44 @@ +# @brief Measure the time it takes to run some given code, in milliseconds +# @param tag Identifier for the code block (string) +# @param code Node of code to run +# @author https://github.com/SuperFola +(macro measureOnce (tag code) { + (mut _start (time)) + { code } + (print (format "{} took {:.4} ms" tag (* 1000 (- (time) _start)))) }) + +(import std.List) + +# @brief Benchmark some given code by running it a given number of times +# @param code code to run, eg. a function call +# @param times number of times to run the code +# =begin +# (let fib (fun (n) +# (if (< n 2) +# n +# (+ (fib (- n 1)) (fib (- n 2)))))) +# (bench (fib 23) 10) +# # (fib 23), 10 times +# # ↪︎ range: [4.7 - 5.02] ms +# # ↪︎ mean: 4.85ms +# # ↪︎ median: 4.86ms +# =end +# @author https://github.com/SuperFola +(macro bench (code times) { + (mut _i 0) + (mut _data []) + (while (< _i times) { + (let _start (time)) + { ($as-is code) } + (let _end (time)) + (append! _data (* 1000 (- _end _start))) + (set _i (+ 1 _i)) }) + + (mut _mean (/ (list:sum _data) (len _data))) + (mut _median (list:median _data)) + (mut _max (list:max _data)) + (mut _min (list:min _data)) + (print + (format + "{}, {} times\n ↪︎ range: [{:.3} - {:.3}] ms\n ↪︎ mean: {:.3}ms\n ↪︎ median: {:.3}ms" + ($repr code) times _min _max _mean _median)) }) From 39ee10112242dd5422c2c1e62d7b493ce1503531 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Fri, 12 Dec 2025 19:55:52 +0100 Subject: [PATCH 14/14] docs: add mising parameter documentation for dict:contains? --- Dict.ark | 1 + 1 file changed, 1 insertion(+) diff --git a/Dict.ark b/Dict.ark index 7ced589..edef425 100644 --- a/Dict.ark +++ b/Dict.ark @@ -24,6 +24,7 @@ # @brief Checks if the dictionary has a given key # @param _D dictionary +# @param _key key to check for its presence in the dict # =begin # (let data (dict "key" "value")) # (print (dict:contains? data "key")) # true