diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index 1e4a64b0..b1978ea0 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -87,21 +87,26 @@ namespace Ark::internal constexpr std::string_view AppendInPlace = "append!"; constexpr std::string_view ConcatInPlace = "concat!"; constexpr std::string_view PopInPlace = "pop!"; + constexpr std::string_view SetAtInPlace = "@="; + constexpr std::string_view SetAt2InPlace = "@@="; /// All the builtins that modify in place a variable constexpr std::array UpdateRef = { - AppendInPlace, ConcatInPlace, PopInPlace + AppendInPlace, ConcatInPlace, PopInPlace, + SetAtInPlace, SetAt2InPlace }; // This list is related to include/Ark/Compiler/Instructions.hpp // The order is very important - constexpr std::array listInstructions = { + constexpr std::array listInstructions = { "list", "append", "concat", AppendInPlace, ConcatInPlace, "pop", - PopInPlace + PopInPlace, + SetAtInPlace, + SetAt2InPlace }; constexpr std::string_view SysArgs = "sys:args"; diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 64d2905c..fb7a1150 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -57,46 +57,48 @@ namespace Ark::internal CONCAT_IN_PLACE = 0x15, POP_LIST = 0x16, POP_LIST_IN_PLACE = 0x17, - POP = 0x18, - DUP = 0x19, + SET_AT_INDEX = 0x18, + SET_AT_2_INDEX = 0x19, + POP = 0x1a, + DUP = 0x1b, - FIRST_OPERATOR = 0x1a, - ADD = 0x1a, - SUB = 0x1b, - MUL = 0x1c, - DIV = 0x1d, - GT = 0x1e, - LT = 0x1f, - LE = 0x20, - GE = 0x21, - NEQ = 0x22, - EQ = 0x23, - LEN = 0x24, - EMPTY = 0x25, - TAIL = 0x26, - HEAD = 0x27, - ISNIL = 0x28, - ASSERT = 0x29, - TO_NUM = 0x2a, - TO_STR = 0x2b, - AT = 0x2c, - MOD = 0x2d, - TYPE = 0x2e, - HASFIELD = 0x2f, - NOT = 0x30, + 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, - LOAD_CONST_LOAD_CONST = 0x31, - LOAD_CONST_STORE = 0x32, - LOAD_CONST_SET_VAL = 0x33, - STORE_FROM = 0x34, - SET_VAL_FROM = 0x35, - INCREMENT = 0x36, - DECREMENT = 0x37, - STORE_TAIL = 0x38, - STORE_HEAD = 0x39, - SET_VAL_TAIL = 0x3a, - SET_VAL_HEAD = 0x3b, - CALL_BUILTIN = 0x3c + 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 }; constexpr std::array InstructionNames = { @@ -124,6 +126,8 @@ namespace Ark::internal "CONCAT_IN_PLACE", "POP_LIST", "POP_LIST_IN_PLACE", + "SET_AT_INDEX", + "SET_AT_2_INDEX", "POP", "DUP", // operators diff --git a/include/Ark/VM/Value.hpp b/include/Ark/VM/Value.hpp index a173f869..91515c20 100644 --- a/include/Ark/VM/Value.hpp +++ b/include/Ark/VM/Value.hpp @@ -114,9 +114,12 @@ namespace Ark [[nodiscard]] ValueType valueType() const noexcept { return m_type; } [[nodiscard]] bool isFunction() const noexcept { - const auto type = valueType(); - return type == ValueType::PageAddr || type == ValueType::Closure || type == ValueType::CProc || - (type == ValueType::Reference && reference()->isFunction()); + return m_type == ValueType::PageAddr || m_type == ValueType::Closure || m_type == ValueType::CProc || + (m_type == ValueType::Reference && reference()->isFunction()); + } + [[nodiscard]] bool isIndexable() const noexcept + { + return m_type == ValueType::List || m_type == ValueType::String; } [[nodiscard]] double number() const { return std::get(m_value); } diff --git a/src/arkreactor/Compiler/Compiler.cpp b/src/arkreactor/Compiler/Compiler.cpp index 455ceda9..14e3dc43 100644 --- a/src/arkreactor/Compiler/Compiler.cpp +++ b/src/arkreactor/Compiler/Compiler.cpp @@ -243,10 +243,14 @@ namespace Ark::internal // length of at least 1 since we got a symbol name const auto argc = x.constList().size() - 1u; // error, can not use append/concat/pop (and their in place versions) with a <2 length argument list - if (argc < 2 && inst != LIST) + if (argc < 2 && APPEND <= inst && inst <= POP) throwCompilerError(fmt::format("Can not use {} with less than 2 arguments", name), c0); - if (std::cmp_greater(argc, std::numeric_limits::max())) + if (inst <= POP && std::cmp_greater(argc, std::numeric_limits::max())) throwCompilerError(fmt::format("Too many arguments ({}), exceeds 65'535", argc), x); + if (argc != 3 && inst == SET_AT_INDEX) + throwCompilerError(fmt::format("Expected 3 arguments for {}, got {}", name, argc), c0); + if (argc != 4 && inst == SET_AT_2_INDEX) + throwCompilerError(fmt::format("Expected 4 arguments for {}, got {}", name, argc), c0); // compile arguments in reverse order for (std::size_t i = x.constList().size() - 1u; i > 0; --i) @@ -259,7 +263,7 @@ namespace Ark::internal } // put inst and number of arguments - std::size_t inst_argc; + std::size_t inst_argc = 0; switch (inst) { case LIST: @@ -273,13 +277,17 @@ namespace Ark::internal inst_argc = argc - 1; break; - default: + case POP_LIST: + case POP_LIST_IN_PLACE: inst_argc = 0; break; + + default: + break; } page(p).emplace_back(inst, static_cast(inst_argc)); - if (is_result_unused && name.back() != '!') // in-place functions never push a value + if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE) // in-place functions never push a value { compilerWarning("Ignoring return value of function", x); page(p).emplace_back(POP); diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index d3d11a48..47832d6a 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -379,6 +379,8 @@ namespace Ark &&TARGET_CONCAT_IN_PLACE, &&TARGET_POP_LIST, &&TARGET_POP_LIST_IN_PLACE, + &&TARGET_SET_AT_INDEX, + &&TARGET_SET_AT_2_INDEX, &&TARGET_POP, &&TARGET_DUP, &&TARGET_ADD, @@ -798,6 +800,104 @@ namespace Ark DISPATCH(); } + TARGET(SET_AT_INDEX) + { + { + Value* list = popAndResolveAsPtr(context); + Value number = *popAndResolveAsPtr(context); + Value new_value = *popAndResolveAsPtr(context); + + if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String)) + types::generateError( + "@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("index", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } }, + { types::Contract { + { types::Typedef("string", ValueType::String), + types::Typedef("index", ValueType::Number), + types::Typedef("char", ValueType::String) } } } }, + { *list, number }); + + const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size(); + long idx = static_cast(number.number()); + idx = idx < 0 ? static_cast(size) + idx : idx; + if (std::cmp_greater_equal(idx, size)) + throwVMError( + ErrorKind::Index, + fmt::format("@= index ({}) out of range (indexable size: {})", idx, size)); + + if (list->valueType() == ValueType::List) + list->list()[static_cast(idx)] = new_value; + else + list->stringRef()[static_cast(idx)] = new_value.string()[0]; + } + DISPATCH(); + } + + TARGET(SET_AT_2_INDEX) + { + { + Value* list = popAndResolveAsPtr(context); + Value x = *popAndResolveAsPtr(context); + Value y = *popAndResolveAsPtr(context); + Value new_value = *popAndResolveAsPtr(context); + + if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number) + types::generateError( + "@@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } } }, + { *list, x, y }); + + long idx_x = static_cast(x.number()); + idx_x = idx_x < 0 ? static_cast(list->list().size()) + idx_x : idx_x; + if (std::cmp_greater_equal(idx_x, list->list().size())) + throwVMError( + ErrorKind::Index, + fmt::format("@@= index (x: {}) out of range (list size: {})", idx_x, list->list().size())); + + if (!list->list()[static_cast(idx_x)].isIndexable() || + (list->list()[static_cast(idx_x)].valueType() == ValueType::String && new_value.valueType() != ValueType::String)) + types::generateError( + "@@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } }, + { types::Contract { + { types::Typedef("string", ValueType::String), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("char", ValueType::String) } } } }, + { *list, x, y }); + + const bool is_list = list->list()[static_cast(idx_x)].valueType() == ValueType::List; + const std::size_t size = + is_list + ? list->list()[static_cast(idx_x)].list().size() + : list->list()[static_cast(idx_x)].stringRef().size(); + + long idx_y = static_cast(y.number()); + idx_y = idx_y < 0 ? static_cast(size) + idx_y : idx_y; + if (std::cmp_greater_equal(idx_y, size)) + throwVMError( + ErrorKind::Index, + fmt::format("@@= index (y: {}) out of range (inner indexable size: {})", idx_x, size)); + + if (is_list) + list->list()[static_cast(idx_x)].list()[static_cast(idx_y)] = new_value; + else + list->list()[static_cast(idx_x)].stringRef()[static_cast(idx_y)] = new_value.string()[0]; + } + DISPATCH(); + } + TARGET(POP) { pop(context); diff --git a/tests/unittests/resources/LangSuite/builtins-tests.ark b/tests/unittests/resources/LangSuite/builtins-tests.ark index b7f6f4dd..92ec2191 100644 --- a/tests/unittests/resources/LangSuite/builtins-tests.ark +++ b/tests/unittests/resources/LangSuite/builtins-tests.ark @@ -31,6 +31,19 @@ (test:eq (@ (list:setAt short_list 5 "a") 5) "a") (del short_list) + (mut numbers [0 1 2 3 4]) + (@= numbers 2 5) + (@= numbers -1 9) + (@= numbers -2 8) + (test:eq numbers [0 1 5 8 9] "@=") + + (set numbers [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) + (@@= numbers 0 0 9) + (@@= numbers 1 1 "a") + (@@= numbers -1 -1 -1) + (@@= numbers -2 -2 -2) + (test:eq numbers [[9 1 2 3] [4 "a" -2 7] [8 9 0 -1]]) + (test:expect (not (io:fileExists? "test.txt"))) (io:writeFile "test.txt" "hello, world!") (test:expect (io:fileExists? "test.txt"))