From b1d5b41a403af4706d9f064a9ccb758157250ba8 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Sun, 15 Dec 2024 18:31:26 +0100 Subject: [PATCH 1/3] feat(compiler, vm, name resolution): add a dedicated scope for while loops --- CHANGELOG.md | 1 + include/Ark/Compiler/Instructions.hpp | 76 ++++++++++--------- src/arkreactor/Compiler/Compiler.cpp | 4 + .../NameResolution/NameResolutionPass.cpp | 9 +++ src/arkreactor/VM/VM.cpp | 14 ++++ .../CompilerSuite/ir/99bottles.expected | 2 + .../CompilerSuite/ir/factorial.expected | 2 + .../optimized_ir/99bottles.expected | 2 + .../optimized_ir/factorial.expected | 2 + 9 files changed, 76 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157a7216..d3b5e72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ - `string:find` takes an optional third argument, startIndex (where to start the lookup from, default 0 - `list:setAt` can work with negative indexes, and is now bound checked - re-enabled the AST optimizer, only used for the main `arkscript` executable (not enabled when embedding arkscript, so that one can grab variables from the VM) +- loops have their own scope: variables created inside a loop won't leak outside it ### Removed - removed unused `NodeType::Closure` diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index fb7a1150..4c01c809 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -61,44 +61,46 @@ namespace Ark::internal SET_AT_2_INDEX = 0x19, POP = 0x1a, DUP = 0x1b, + CREATE_SCOPE = 0x1c, + POP_SCOPE = 0x1d, - FIRST_OPERATOR = 0x1c, - ADD = 0x1c, - SUB = 0x1d, - MUL = 0x1e, - DIV = 0x1f, - GT = 0x20, - LT = 0x21, - LE = 0x22, - GE = 0x23, - NEQ = 0x24, - EQ = 0x25, - LEN = 0x26, - EMPTY = 0x27, - TAIL = 0x28, - HEAD = 0x29, - ISNIL = 0x2a, - ASSERT = 0x2b, - TO_NUM = 0x2c, - TO_STR = 0x2d, - AT = 0x2e, - MOD = 0x2f, - TYPE = 0x30, - HASFIELD = 0x31, - NOT = 0x32, + FIRST_OPERATOR = 0x1e, + ADD = 0x1e, + SUB = 0x1f, + MUL = 0x20, + DIV = 0x21, + GT = 0x22, + LT = 0x23, + LE = 0x24, + GE = 0x25, + NEQ = 0x26, + EQ = 0x27, + LEN = 0x28, + EMPTY = 0x29, + TAIL = 0x2a, + HEAD = 0x2b, + ISNIL = 0x2c, + ASSERT = 0x2d, + TO_NUM = 0x2e, + TO_STR = 0x2f, + AT = 0x30, + MOD = 0x31, + TYPE = 0x32, + HASFIELD = 0x33, + NOT = 0x34, - LOAD_CONST_LOAD_CONST = 0x33, - LOAD_CONST_STORE = 0x34, - LOAD_CONST_SET_VAL = 0x35, - STORE_FROM = 0x36, - SET_VAL_FROM = 0x37, - INCREMENT = 0x38, - DECREMENT = 0x39, - STORE_TAIL = 0x3a, - STORE_HEAD = 0x3b, - SET_VAL_TAIL = 0x3c, - SET_VAL_HEAD = 0x3d, - CALL_BUILTIN = 0x3e + LOAD_CONST_LOAD_CONST = 0x35, + LOAD_CONST_STORE = 0x36, + LOAD_CONST_SET_VAL = 0x37, + STORE_FROM = 0x38, + SET_VAL_FROM = 0x39, + INCREMENT = 0x3a, + DECREMENT = 0x3b, + STORE_TAIL = 0x3c, + STORE_HEAD = 0x3d, + SET_VAL_TAIL = 0x3e, + SET_VAL_HEAD = 0x3f, + CALL_BUILTIN = 0x40 }; constexpr std::array InstructionNames = { @@ -130,6 +132,8 @@ namespace Ark::internal "SET_AT_2_INDEX", "POP", "DUP", + "CREATE_SCOPE", + "POP_SCOPE", // operators "ADD", "SUB", diff --git a/src/arkreactor/Compiler/Compiler.cpp b/src/arkreactor/Compiler/Compiler.cpp index badf73bb..e057d31c 100644 --- a/src/arkreactor/Compiler/Compiler.cpp +++ b/src/arkreactor/Compiler/Compiler.cpp @@ -394,6 +394,8 @@ namespace Ark::internal if (x.constList().size() != 3) throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x); + page(p).emplace_back(CREATE_SCOPE); + // save current position to jump there at the end of the loop const auto label_loop = IR::Entity::Label(m_current_label++); page(p).emplace_back(label_loop); @@ -410,6 +412,8 @@ namespace Ark::internal // absolute address to jump to if condition is false page(p).emplace_back(label_end); + + page(p).emplace_back(POP_SCOPE); } void Compiler::compilePluginImport(const Node& x, const Page p) diff --git a/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp b/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp index f9e3fc74..ed18ee3d 100644 --- a/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp +++ b/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp @@ -216,6 +216,15 @@ namespace Ark::internal m_plugin_names.push_back(node.constList()[1].constList().back().string()); break; + case Keyword::While: + // create a new scope to track variables + m_scope_resolver.createNew(); + for (auto& child : node.list()) + visit(child, register_declarations); + // remove the scope once the loop has been compiled, only we were registering declarations + m_scope_resolver.removeLastScope(); + break; + case Keyword::Fun: // create a new scope to track variables m_scope_resolver.createNew(); diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index bf7014e3..ed117924 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -383,6 +383,8 @@ namespace Ark &&TARGET_SET_AT_2_INDEX, &&TARGET_POP, &&TARGET_DUP, + &&TARGET_CREATE_SCOPE, + &&TARGET_POP_SCOPE, &&TARGET_ADD, &&TARGET_SUB, &&TARGET_MUL, @@ -911,6 +913,18 @@ namespace Ark DISPATCH(); } + TARGET(CREATE_SCOPE) + { + context.locals.emplace_back(); + DISPATCH(); + } + + TARGET(POP_SCOPE) + { + context.locals.pop_back(); + DISPATCH(); + } + #pragma endregion #pragma region "Operators" diff --git a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected index 5cf19616..dab5d2fb 100644 --- a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected @@ -24,6 +24,7 @@ page_0 STORE 2 LOAD_SYMBOL 2 STORE 3 + CREATE_SCOPE 0 .L4: LOAD_SYMBOL 3 LOAD_CONST 0 @@ -50,4 +51,5 @@ page_0 POP 0 GOTO L4 .L5: + POP_SCOPE 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/factorial.expected b/tests/unittests/resources/CompilerSuite/ir/factorial.expected index 1d40cee3..65d34e3f 100644 --- a/tests/unittests/resources/CompilerSuite/ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/ir/factorial.expected @@ -15,6 +15,7 @@ page_1 STORE 2 LOAD_CONST 2 STORE 3 + CREATE_SCOPE 0 .L0: LOAD_SYMBOL 3 LOAD_SYMBOL 1 @@ -30,6 +31,7 @@ page_1 SET_VAL 3 GOTO L0 .L1: + POP_SCOPE 0 LOAD_SYMBOL 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected index b085bb75..118f1b9f 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected @@ -23,6 +23,7 @@ page_0 .L3: STORE 2 STORE_FROM 2, 3 + CREATE_SCOPE 0 .L4: LOAD_SYMBOL 3 LOAD_CONST 0 @@ -43,4 +44,5 @@ page_0 POP 0 GOTO L4 .L5: + POP_SCOPE 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected index c003e757..cc5a8d2a 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected @@ -10,6 +10,7 @@ page_1 STORE 1 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 3 + CREATE_SCOPE 0 .L0: LOAD_SYMBOL 3 LOAD_SYMBOL 1 @@ -23,6 +24,7 @@ page_1 SET_VAL 3 GOTO L0 .L1: + POP_SCOPE 0 LOAD_SYMBOL 2 RET 0 HALT 0 From 7b210b6bcac72c031f08377d4bfe69be0ba95e48 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Tue, 17 Dec 2024 12:44:24 +0100 Subject: [PATCH 2/3] feat(vm, compiler): new operator '@@' to get element in 2D list --- CHANGELOG.md | 1 + include/Ark/Compiler/Common.hpp | 4 +- include/Ark/Compiler/Compiler.hpp | 9 ++++ include/Ark/Compiler/Instructions.hpp | 34 +++++++------- src/arkreactor/Compiler/Compiler.cpp | 23 +++++++++- src/arkreactor/VM/VM.cpp | 46 +++++++++++++++++++ .../compileTime/at_at_not_enough_args.ark | 2 + .../at_at_not_enough_args.expected | 6 +++ .../runtime/at_at_inner_out_of_range.ark | 2 + .../runtime/at_at_inner_out_of_range.expected | 1 + .../runtime/at_at_out_of_range.ark | 2 + .../runtime/at_at_out_of_range.expected | 1 + .../resources/LangSuite/list-tests.ark | 9 ++++ .../resources/LangSuite/string-tests.ark | 16 +++++++ 14 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b5e72a..ec18773b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - added in place list mutation: `(@= list|string index new_value)`, `(@@= list|list index1 index2 new_value|char)` (bound checked) - compile time argument count check for `and` and `or` - basic dead code elimination in the AST optimizer +- new operator `@@` to get elements in list of lists / list of strings ### Changed - instructions are on 4 bytes: 1 byte for the instruction, 1 byte of padding, 2 bytes for an immediate argument diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index b1978ea0..bade350f 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -132,13 +132,13 @@ namespace Ark::internal // This list is related to include/Ark/Compiler/Instructions.hpp // from FIRST_OPERATOR, to LAST_OPERATOR // The order is very important - constexpr std::array operators = { + constexpr std::array operators = { "+", "-", "*", "/", ">", "<", "<=", ">=", "!=", "=", "len", "empty?", "tail", "head", "nil?", "assert", "toNumber", "toString", - "@", "mod", + "@", "@@", "mod", "type", "hasField", "not" }; diff --git a/include/Ark/Compiler/Compiler.hpp b/include/Ark/Compiler/Compiler.hpp index 2eb7cc2e..6797674f 100644 --- a/include/Ark/Compiler/Compiler.hpp +++ b/include/Ark/Compiler/Compiler.hpp @@ -142,6 +142,15 @@ namespace Ark::internal */ static bool isUnaryInst(Instruction inst) noexcept; + /** + * @brief Check if a given instruction is ternary (takes three arguments) + * + * @param inst + * @return true the instruction is ternary + * @return false + */ + static bool isTernaryInst(Instruction inst) noexcept; + /** * @brief Display a warning message * diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 4c01c809..a7cbb3b3 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -84,23 +84,24 @@ namespace Ark::internal TO_NUM = 0x2e, TO_STR = 0x2f, AT = 0x30, - MOD = 0x31, - TYPE = 0x32, - HASFIELD = 0x33, - NOT = 0x34, + AT_AT = 0x31, + MOD = 0x32, + TYPE = 0x33, + HASFIELD = 0x34, + NOT = 0x35, - LOAD_CONST_LOAD_CONST = 0x35, - LOAD_CONST_STORE = 0x36, - LOAD_CONST_SET_VAL = 0x37, - STORE_FROM = 0x38, - SET_VAL_FROM = 0x39, - INCREMENT = 0x3a, - DECREMENT = 0x3b, - STORE_TAIL = 0x3c, - STORE_HEAD = 0x3d, - SET_VAL_TAIL = 0x3e, - SET_VAL_HEAD = 0x3f, - CALL_BUILTIN = 0x40 + LOAD_CONST_LOAD_CONST = 0x36, + LOAD_CONST_STORE = 0x37, + LOAD_CONST_SET_VAL = 0x38, + STORE_FROM = 0x39, + SET_VAL_FROM = 0x3a, + INCREMENT = 0x3b, + DECREMENT = 0x3c, + STORE_TAIL = 0x3d, + STORE_HEAD = 0x3e, + SET_VAL_TAIL = 0x3f, + SET_VAL_HEAD = 0x40, + CALL_BUILTIN = 0x41 }; constexpr std::array InstructionNames = { @@ -154,6 +155,7 @@ namespace Ark::internal "TO_NUM", "TO_STR", "AT", + "AT_AT", "MOD", "TYPE", "HASFIELD", diff --git a/src/arkreactor/Compiler/Compiler.cpp b/src/arkreactor/Compiler/Compiler.cpp index e057d31c..efb09b39 100644 --- a/src/arkreactor/Compiler/Compiler.cpp +++ b/src/arkreactor/Compiler/Compiler.cpp @@ -107,6 +107,18 @@ namespace Ark::internal } } + bool Compiler::isTernaryInst(const Instruction inst) noexcept + { + switch (inst) + { + case AT_AT: + return true; + + default: + return false; + } + } + void Compiler::compilerWarning(const std::string& message, const Node& node) { fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node)); @@ -562,7 +574,7 @@ namespace Ark::internal // in order to be able to handle things like (op A B C D...) // which should be transformed into A B op C op D op... - if (exp_count >= 2) + if (exp_count >= 2 && !isTernaryInst(op)) page(p).emplace_back(op); } @@ -572,6 +584,12 @@ namespace Ark::internal throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]); page(p).emplace_back(op); } + else if (isTernaryInst(op)) + { + if (exp_count != 3) + throwCompilerError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]); + page(p).emplace_back(op); + } else if (exp_count <= 1) throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]); @@ -585,7 +603,8 @@ namespace Ark::internal case SUB: [[fallthrough]]; case MUL: [[fallthrough]]; case DIV: [[fallthrough]]; - case MOD: + case MOD: [[fallthrough]]; + case AT_AT: break; default: diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index ed117924..88b65fb1 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -404,6 +404,7 @@ namespace Ark &&TARGET_TO_NUM, &&TARGET_TO_STR, &&TARGET_AT, + &&TARGET_AT_AT, &&TARGET_MOD, &&TARGET_TYPE, &&TARGET_HASFIELD, @@ -1169,6 +1170,51 @@ namespace Ark DISPATCH(); } + TARGET(AT_AT) + { + { + Value* x = popAndResolveAsPtr(context); + Value* y = popAndResolveAsPtr(context); + Value list = *popAndResolveAsPtr(context); // be careful, it's not a pointer + + if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number || + list.valueType() != ValueType::List) + types::generateError( + "@@", + { { types::Contract { + { types::Typedef("src", ValueType::List), + types::Typedef("y", ValueType::Number), + types::Typedef("x", ValueType::Number) } } } }, + { list, *y, *x }); + + long idx_y = static_cast(y->number()); + idx_y = idx_y < 0 ? static_cast(list.list().size()) + idx_y : idx_y; + if (std::cmp_greater_equal(idx_y, list.list().size())) + throwVMError( + ErrorKind::Index, + fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size())); + + const bool is_list = list.list()[static_cast(idx_y)].valueType() == ValueType::List; + const std::size_t size = + is_list + ? list.list()[static_cast(idx_y)].list().size() + : list.list()[static_cast(idx_y)].stringRef().size(); + + long idx_x = static_cast(x->number()); + idx_x = idx_x < 0 ? static_cast(size) + idx_x : idx_x; + if (std::cmp_greater_equal(idx_x, size)) + throwVMError( + ErrorKind::Index, + fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size)); + + if (is_list) + push(list.list()[static_cast(idx_y)].list()[static_cast(idx_x)], context); + else + push(Value(std::string(1, list.list()[static_cast(idx_y)].stringRef()[static_cast(idx_x)])), context); + } + DISPATCH(); + } + TARGET(MOD) { Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.ark b/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.ark new file mode 100644 index 00000000..53d77631 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.ark @@ -0,0 +1,2 @@ +(let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) +(print (@@ lst 1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected new file mode 100644 index 00000000..6be93b4f --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected @@ -0,0 +1,6 @@ +At @@ @ 2:9 + 1 | (let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) + 2 | (print (@@ lst 1)) + | ^~ + 3 | + Operator needs three arguments, but was called with 2 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.ark new file mode 100644 index 00000000..2b976f4c --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.ark @@ -0,0 +1,2 @@ +(let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) +(print (@@ lst 0 6)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected new file mode 100644 index 00000000..4fa05718 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected @@ -0,0 +1 @@ +IndexError: @@ index (x: 6) out of range (inner indexable size: 4) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.ark new file mode 100644 index 00000000..417d5ed4 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.ark @@ -0,0 +1,2 @@ +(let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) +(print (@@ lst 3 1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected new file mode 100644 index 00000000..5a21d49e --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected @@ -0,0 +1 @@ +IndexError: @@ index (3) out of range (list size: 3) diff --git a/tests/unittests/resources/LangSuite/list-tests.ark b/tests/unittests/resources/LangSuite/list-tests.ark index fdc404d6..2ca237c8 100644 --- a/tests/unittests/resources/LangSuite/list-tests.ark +++ b/tests/unittests/resources/LangSuite/list-tests.ark @@ -75,6 +75,15 @@ (test:eq (list:setAt [0 1 2 3] 3 9) [0 1 2 9]) (test:eq (list:setAt [0 1 2 9] -1 9) [0 1 2 9]) }) + (test:case "get element in 2D list" { + (let nested_list [[0 1 2] [3 4 5] [6 7 8]]) + (test:eq 0 (@@ nested_list 0 0)) + (test:eq 2 (@@ nested_list 0 -1)) + (test:eq 1 (@@ nested_list 0 -2)) + (test:eq 8 (@@ nested_list -1 -1)) + (test:eq 4 (@@ nested_list 1 1)) + (test:eq 4 (@@ nested_list -2 1)) }) + (test:case "in place mutation with @=" { (mut numbers [0 1 2 3 4]) (@= numbers 2 5) diff --git a/tests/unittests/resources/LangSuite/string-tests.ark b/tests/unittests/resources/LangSuite/string-tests.ark index c520e782..2838b07f 100644 --- a/tests/unittests/resources/LangSuite/string-tests.ark +++ b/tests/unittests/resources/LangSuite/string-tests.ark @@ -23,6 +23,22 @@ (test:eq (string:find "abcdefghijkl" "defghijkl") 3) (test:eq (string:find "abcdabcdabcd" "abcd" 2) 4) }) + (test:case "get char in string" { + (test:eq "a" (@ "abc" 0)) + (test:eq "c" (@ "abc" -1)) + (test:eq "b" (@ "abc" -2)) + (test:eq "b" (@ "abc" 1)) }) + + (test:case "get char in list of strings" { + (let nested_strings ["abc" "def" "ijk"]) + (test:eq "a" (@@ nested_strings 0 0)) + (test:eq "b" (@@ nested_strings -3 1)) + (test:eq "c" (@@ nested_strings 0 -1)) + (test:eq "k" (@@ nested_strings -1 -1)) + (test:eq "i" (@@ nested_strings 2 0)) + (test:eq "e" (@@ nested_strings 1 1)) + (test:eq "e" (@@ nested_strings 1 -2)) }) + (test:case "update string" { (test:eq (string:setAt "hello" 0 "a") "aello") (test:eq (string:setAt "hello" -1 "a") "hella") From 3ad2cf0c762ebd9e9235cdb584a62d0055a6e462 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Tue, 17 Dec 2024 12:46:50 +0100 Subject: [PATCH 3/3] chore: format examples/macros.ark --- examples/macros.ark | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/macros.ark b/examples/macros.ark index 8e169514..4d345be0 100644 --- a/examples/macros.ark +++ b/examples/macros.ark @@ -45,23 +45,23 @@ (last 1 5 6 7 8) { -(print "Testing macros in scopes and macro shadowing") + (print "Testing macros in scopes and macro shadowing") -($ test (+ 1 2 3)) -(print "(global) Reading macro 'test', expected 6, " test) + ($ test (+ 1 2 3)) + (print "(global) Reading macro 'test', expected 6, " test) -((fun () { - ($ test (- 1 2 3)) - (print "(sub scope) Reading macro 'test', expected -4, " test) })) + ((fun () { + ($ test (- 1 2 3)) + (print "(sub scope) Reading macro 'test', expected -4, " test) })) -(print "(global) Reading macro 'test', expected 6, " test) + (print "(global) Reading macro 'test', expected 6, " test) -{ - ($ test 555) - (print "(subscope) Reading macro 'test', expected 555, " test) - ($ undef test) - (print "(subscope, undef test) Reading macro 'test', expected 6, " test) - ($ undef a) } } + { + ($ test 555) + (print "(subscope) Reading macro 'test', expected 555, " test) + ($ undef test) + (print "(subscope, undef test) Reading macro 'test', expected 6, " test) + ($ undef a) } } (print "Demonstrating a threading macro") ($ -> (arg fn1 ...fn) {