From 32134cfd2ff0085ffb90f64a72f967d80064d223 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 5 Jan 2026 18:21:21 +0100 Subject: [PATCH 01/16] feat(instructions, vm, compiler)!: adding BREAKPOINT instruction and turning ASSERT into a builtin --- CHANGELOG.md | 7 ++++ CMakeLists.txt | 3 +- include/Ark/Builtins/Builtins.hpp | 1 + include/Ark/Compiler/Common.hpp | 5 +-- include/Ark/Compiler/Instructions.hpp | 40 +++++++++---------- src/arkreactor/Builtins/Builtins.cpp | 1 + src/arkreactor/Builtins/System.cpp | 15 +++++++ .../Compiler/Lowerer/ASTLowerer.cpp | 6 +-- src/arkreactor/VM/VM.cpp | 28 +++++-------- tests/unittests/Suites/CompilerSuite.cpp | 4 ++ .../CompilerSuite/ir/99bottles.expected | 4 +- .../CompilerSuite/ir/plugin.expected | 11 +++-- .../optimized_ir/99bottles.expected | 4 +- .../optimized_ir/builtins.expected | 2 +- .../compileTime/assert_too_many_args.expected | 5 --- .../assert_too_many_args.ark | 0 .../runtime/assert_too_many_args.expected | 14 +++++++ 17 files changed, 91 insertions(+), 59 deletions(-) delete mode 100644 tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.expected rename tests/unittests/resources/DiagnosticsSuite/{compileTime => runtime}/assert_too_many_args.ark (100%) create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.expected diff --git a/CHANGELOG.md b/CHANGELOG.md index b26c27c6f..9899b5615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [Unreleased changes] - 20XX-XX-XX +### Breaking changes +- `assert` is no longer an instruction but a builtin + +### Added +- added `BREAKPOINT` instruction + ## [4.1.2] - 2026-01-09 ### Added - the repl prints the output of the last expression it ran diff --git a/CMakeLists.txt b/CMakeLists.txt index bf55d4219..4e5c121c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ if (NOT ARK_EMSCRIPTEN) endif () include(cmake/CPM.cmake) +message(STATUS "ArkScript version ${ARK_VERSION_MAJOR}.${ARK_VERSION_MINOR}.${ARK_VERSION_PATCH}-${ARK_COMMIT}") ##################################################### # Configure files @@ -55,9 +56,7 @@ include(cmake/CPM.cmake) configure_file( ${ark_SOURCE_DIR}/Installer.iss.in ${ark_SOURCE_DIR}/Installer.iss) - ## Ark/Constants.hpp -message(STATUS "ArkScript version ${ARK_VERSION_MAJOR}.${ARK_VERSION_MINOR}.${ARK_VERSION_PATCH}-${ARK_COMMIT}") configure_file( ${ark_SOURCE_DIR}/include/Ark/Constants.hpp.in ${ark_SOURCE_DIR}/include/Ark/Constants.hpp) diff --git a/include/Ark/Builtins/Builtins.hpp b/include/Ark/Builtins/Builtins.hpp index c2fa961dd..ba49bf270 100644 --- a/include/Ark/Builtins/Builtins.hpp +++ b/include/Ark/Builtins/Builtins.hpp @@ -70,6 +70,7 @@ namespace Ark::internal::Builtins ARK_BUILTIN(system_); ARK_BUILTIN(sleep); ARK_BUILTIN(exit_); + ARK_BUILTIN(assert_); } namespace String diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index c7cb98ae5..7a7c8eddc 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -153,11 +153,10 @@ 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", + "len", "empty?", "tail", "head", "nil?", "toNumber", "toString", "@", "@@", "mod", "type", "hasField", diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 94c460b7c..8028ee1f3 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -171,55 +171,55 @@ namespace Ark::internal // @role Push the current page address as a value on the stack GET_CURRENT_PAGE_ADDR = 0x25, - FIRST_OPERATOR = 0x26, + // @role Pop the top of the stack, if it's true, trigger the debugger + BREAKPOINT = 0x26, + + FIRST_OPERATOR = 0x27, // @role Push `TS1 + TS` - ADD = 0x26, + ADD = 0x27, // @role Push `TS1 - TS` - SUB = 0x27, + SUB = 0x28, // @role Push `TS1 * TS` - MUL = 0x28, + MUL = 0x29, // @role Push `TS1 / TS` - DIV = 0x29, + DIV = 0x2a, // @role Push `TS1 > TS` - GT = 0x2a, + GT = 0x2b, // @role Push `TS1 < TS` - LT = 0x2b, + LT = 0x2c, // @role Push `TS1 <= TS` - LE = 0x2c, + LE = 0x2d, // @role Push `TS1 >= TS` - GE = 0x2d, + GE = 0x2e, // @role Push `TS1 != TS` - NEQ = 0x2e, + NEQ = 0x2f, // @role Push `TS1 == TS` - EQ = 0x2f, + EQ = 0x30, // @role Push `len(TS)`, TS must be a list - LEN = 0x30, + LEN = 0x31, // @role Push `empty?(TS)`, TS must be a list or string - IS_EMPTY = 0x31, + IS_EMPTY = 0x32, // @role Push `tail(TS)`, all the elements of TS except the first one. TS must be a list or string - TAIL = 0x32, + TAIL = 0x33, // @role Push `head(TS)`, the first element of TS or nil if empty. TS must be a list or string - HEAD = 0x33, + HEAD = 0x34, // @role Push true if TS is nil, false otherwise - IS_NIL = 0x34, - - // @role Throw an exception if TS1 is false, and display TS (must be a string). Do not push anything on the stack - ASSERT = 0x35, + IS_NIL = 0x35, // @role Convert TS to number (must be a string) TO_NUM = 0x36, @@ -491,6 +491,7 @@ namespace Ark::internal "RESET_SCOPE_JUMP", "POP_SCOPE", "GET_CURRENT_PAGE_ADDR", + "BREAKPOINT", // operators "ADD", "SUB", @@ -507,7 +508,6 @@ namespace Ark::internal "TAIL", "HEAD", "IS_NIL", - "ASSERT", "TO_NUM", "TO_STR", "AT", diff --git a/src/arkreactor/Builtins/Builtins.cpp b/src/arkreactor/Builtins/Builtins.cpp index d689f83e6..4738f8960 100644 --- a/src/arkreactor/Builtins/Builtins.cpp +++ b/src/arkreactor/Builtins/Builtins.cpp @@ -57,6 +57,7 @@ namespace Ark::internal::Builtins { "builtin__sys:exec", Value(System::system_) }, { "builtin__sys:sleep", Value(System::sleep) }, { "builtin__sys:exit", Value(System::exit_) }, + { "assert", Value(System::assert_) }, // String { "format", Value(String::format) }, diff --git a/src/arkreactor/Builtins/System.cpp b/src/arkreactor/Builtins/System.cpp index 6580ee83f..37446ab40 100644 --- a/src/arkreactor/Builtins/System.cpp +++ b/src/arkreactor/Builtins/System.cpp @@ -80,4 +80,19 @@ namespace Ark::internal::Builtins::System vm->exit(static_cast(n[0].number())); return nil; } + + // cppcheck-suppress constParameterReference + Value assert_(std::vector& n, VM* vm [[maybe_unused]]) + { + if (!types::check(n, ValueType::Any, ValueType::String)) + throw types::TypeCheckingError( + "assert", + { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } }, + n); + + if (n[0] == Builtins::falseSym) + throw AssertionFailed(n[1].stringRef()); + + return nil; + } } diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 6df66aa4d..057a15e0d 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -86,10 +86,10 @@ namespace Ark::internal (node.constList()[0].keyword() == Keyword::If && nodeProducesOutput(node.constList()[2]) && (node.constList().size() == 3 || nodeProducesOutput(node.constList()[3]))); - // in place list instruction, as well as assert, do not produce values + // in place list instruction, as well as breakpoint, do not produce values if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol) return std::ranges::find(Language::UpdateRef, node.constList().front().string()) == Language::UpdateRef.end() && - node.constList().front().string() != "assert"; + node.constList().front().string() != "breakpoint"; return true; // any other node, function call, symbol, number... } @@ -686,7 +686,7 @@ namespace Ark::internal // retrieve operator auto op = maybe_operator.value(); - if (op == ASSERT) + if (op == BREAKPOINT) is_result_unused = false; // push arguments on current page diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index d79958542..430fbfe80 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -606,6 +606,7 @@ namespace Ark &&TARGET_RESET_SCOPE_JUMP, &&TARGET_POP_SCOPE, &&TARGET_GET_CURRENT_PAGE_ADDR, + &&TARGET_BREAKPOINT, &&TARGET_ADD, &&TARGET_SUB, &&TARGET_MUL, @@ -621,7 +622,6 @@ namespace Ark &&TARGET_TAIL, &&TARGET_HEAD, &&TARGET_IS_NIL, - &&TARGET_ASSERT, &&TARGET_TO_NUM, &&TARGET_TO_STR, &&TARGET_AT, @@ -1180,6 +1180,16 @@ namespace Ark DISPATCH(); } + TARGET(BREAKPOINT) + { + { + const Value cond = *popAndResolveAsPtr(context); + if (cond == Builtins::trueSym) // todo: trigger debugger + {} + } + DISPATCH(); + } + #pragma endregion #pragma region "Operators" @@ -1344,22 +1354,6 @@ namespace Ark DISPATCH(); } - TARGET(ASSERT) - { - Value* const b = popAndResolveAsPtr(context); - Value* const a = popAndResolveAsPtr(context); - - if (b->valueType() != ValueType::String) - throw types::TypeCheckingError( - "assert", - { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } }, - { *a, *b }); - - if (*a == Builtins::falseSym) - throw AssertionFailed(b->stringRef()); - DISPATCH(); - } - TARGET(TO_NUM) { const Value* a = popAndResolveAsPtr(context); diff --git a/tests/unittests/Suites/CompilerSuite.cpp b/tests/unittests/Suites/CompilerSuite.cpp index 464e79492..e167fc778 100644 --- a/tests/unittests/Suites/CompilerSuite.cpp +++ b/tests/unittests/Suites/CompilerSuite.cpp @@ -94,6 +94,8 @@ ut::suite<"Compiler"> compiler_suite = [] { Ark::Utils::ltrim(Ark::Utils::rtrim(ir)); expectOrDiff(data.expected, ir); + if (shouldWriteNewDiffsTofile() && data.expected != ir) + updateExpectedFile(data, ir); }; }); }; @@ -116,6 +118,8 @@ ut::suite<"Compiler"> compiler_suite = [] { Ark::Utils::ltrim(Ark::Utils::rtrim(ir)); expectOrDiff(data.expected, ir); + if (shouldWriteNewDiffsTofile() && data.expected != ir) + updateExpectedFile(data, ir); }; }); }; diff --git a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected index 1ff09c922..e5132d226 100644 --- a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected @@ -37,7 +37,7 @@ page_0 LOAD_FAST 4 LOAD_FAST 4 LOAD_CONST 3 - BUILTIN 26 + BUILTIN 27 CALL 3 .L7: BUILTIN 9 @@ -52,7 +52,7 @@ page_0 PUSH_RETURN_ADDRESS L9 LOAD_FAST 4 LOAD_CONST 4 - BUILTIN 26 + BUILTIN 27 CALL 2 .L9: BUILTIN 9 diff --git a/tests/unittests/resources/CompilerSuite/ir/plugin.expected b/tests/unittests/resources/CompilerSuite/ir/plugin.expected index abfa2f787..4932688a2 100644 --- a/tests/unittests/resources/CompilerSuite/ir/plugin.expected +++ b/tests/unittests/resources/CompilerSuite/ir/plugin.expected @@ -1,12 +1,15 @@ page_0 PLUGIN 0 - LOAD_CONST 1 PUSH_RETURN_ADDRESS L0 + LOAD_CONST 1 LOAD_CONST 2 + PUSH_RETURN_ADDRESS L1 + LOAD_CONST 3 LOAD_FAST 0 CALL 1 -.L0: +.L1: EQ 0 - LOAD_CONST 3 - ASSERT 0 + BUILTIN 26 + CALL 2 +.L0: HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected index 485b5b7b1..4386e4110 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected @@ -37,7 +37,7 @@ page_0 LOAD_FAST 13 LOAD_FAST 13 LOAD_CONST 6 - CALL_BUILTIN 26, 3 + CALL_BUILTIN 27, 3 .L10: CALL_BUILTIN 9, 1 .L9: @@ -47,7 +47,7 @@ page_0 PUSH_RETURN_ADDRESS L12 LOAD_FAST 13 LOAD_CONST 7 - CALL_BUILTIN 26, 2 + CALL_BUILTIN 27, 2 .L12: CALL_BUILTIN 9, 1 .L11: diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected index 8453e41f4..a8c4a2277 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected @@ -5,7 +5,7 @@ page_0 HALT 0 page_1 - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 54, 1 + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 55, 1 .L0: RET 0 HALT 0 diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.expected deleted file mode 100644 index 80d04e7b2..000000000 --- a/tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.expected +++ /dev/null @@ -1,5 +0,0 @@ -In file tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.ark:1 - 1 | (assert true 1 2 3) - | ^~~~~~~~~~~~~~~~~~ - 2 | - `assert' requires 2 arguments, but got 4. diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.ark similarity index 100% rename from tests/unittests/resources/DiagnosticsSuite/compileTime/assert_too_many_args.ark rename to tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.ark diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.expected new file mode 100644 index 000000000..504cfae39 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.expected @@ -0,0 +1,14 @@ +Function assert expected 2 arguments but got 4 +Call + ↳ (assert true 1 2 3) +Signature + ↳ (assert expr message) +Arguments + → `expr' (expected any) ✓ + → `message' (expected String), got 1 (Number) + → unexpected additional args: 2 (Number), 3 (Number) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/assert_too_many_args.ark:1 + 1 | (assert true 1 2 3) + | ^~~~~~~~~~~~~~~~~~ + 2 | From 236239e9cd3132a5dead7fc6383eddddb9de907e Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 5 Jan 2026 18:40:00 +0100 Subject: [PATCH 02/16] feat(error messages): enhance operator arity error messages by adding the operator name in the message --- include/Ark/Compiler/Common.hpp | 3 ++- include/Ark/Compiler/Instructions.hpp | 6 +++--- src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp | 17 +++++++---------- .../compileTime/at_at_not_enough_args.expected | 2 +- .../compileTime/no_args.expected | 2 +- .../compileTime/ope_not_enough_args.expected | 2 +- .../compileTime/type_no_args.expected | 2 +- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index 7a7c8eddc..1cfa0a72c 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -153,7 +153,8 @@ 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 = { + "breakpoint", "+", "-", "*", "/", ">", "<", "<=", ">=", "!=", "=", "len", "empty?", "tail", "head", "nil?", diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 8028ee1f3..99e636091 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -171,11 +171,11 @@ namespace Ark::internal // @role Push the current page address as a value on the stack GET_CURRENT_PAGE_ADDR = 0x25, + FIRST_OPERATOR = 0x26, + // @role Pop the top of the stack, if it's true, trigger the debugger BREAKPOINT = 0x26, - FIRST_OPERATOR = 0x27, - // @role Push `TS1 + TS` ADD = 0x27, @@ -491,8 +491,8 @@ namespace Ark::internal "RESET_SCOPE_JUMP", "POP_SCOPE", "GET_CURRENT_PAGE_ADDR", - "BREAKPOINT", // operators + "BREAKPOINT", "ADD", "SUB", "MUL", diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 057a15e0d..d5d81ed80 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -97,6 +97,7 @@ namespace Ark::internal { switch (inst) { + case BREAKPOINT: [[fallthrough]]; case NOT: [[fallthrough]]; case LEN: [[fallthrough]]; case IS_EMPTY: [[fallthrough]]; @@ -684,7 +685,8 @@ namespace Ark::internal else // operator { // retrieve operator - auto op = maybe_operator.value(); + const auto op = maybe_operator.value(); + const auto op_name = Language::operators[static_cast(op - FIRST_OPERATOR)]; if (op == BREAKPOINT) is_result_unused = false; @@ -710,17 +712,17 @@ namespace Ark::internal if (isUnaryInst(op)) { if (exp_count != 1) - buildAndThrowError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]); + buildAndThrowError(fmt::format("`{}' expected one argument, but was called with {}", op_name, exp_count), x.constList()[0]); page(p).emplace_back(op); } else if (isTernaryInst(op)) { if (exp_count != 3) - buildAndThrowError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]); + buildAndThrowError(fmt::format("`{}' expected three arguments, but was called with {}", op_name, exp_count), x.constList()[0]); page(p).emplace_back(op); } else if (exp_count <= 1) - buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]); + buildAndThrowError(fmt::format("`{}' expected two arguments, but was called with {}", op_name, exp_count), x.constList()[0]); page(p).back().setSourceLocation(x.filename(), x.position().start.line); @@ -739,12 +741,7 @@ namespace Ark::internal break; default: - buildAndThrowError( - fmt::format( - "`{}' requires 2 arguments, but got {}.", - Language::operators[static_cast(op - FIRST_OPERATOR)], - exp_count), - x); + buildAndThrowError(fmt::format("`{}' requires 2 arguments, but got {}.", op_name, exp_count), x); } } } 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 index 6abcfcced..ddfdc6ef8 100644 --- a/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_args.expected @@ -3,4 +3,4 @@ In file tests/unittests/resources/DiagnosticsSuite/compileTime/at_at_not_enough_ 2 | (print (@@ lst 1)) | ^~ 3 | - Operator needs three arguments, but was called with 2 + `@@' expected three arguments, but was called with 2 diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/no_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/no_args.expected index 24b790486..e588657f8 100644 --- a/tests/unittests/resources/DiagnosticsSuite/compileTime/no_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/no_args.expected @@ -2,4 +2,4 @@ In file tests/unittests/resources/DiagnosticsSuite/compileTime/no_args.ark:1 1 | ((+)) | ^ 2 | - Operator needs two arguments, but was called with 0 + `+' expected two arguments, but was called with 0 diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/ope_not_enough_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/ope_not_enough_args.expected index 3d6b3c8f3..0b0483f49 100644 --- a/tests/unittests/resources/DiagnosticsSuite/compileTime/ope_not_enough_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/ope_not_enough_args.expected @@ -2,4 +2,4 @@ In file tests/unittests/resources/DiagnosticsSuite/compileTime/ope_not_enough_ar 1 | (print (!= 1)) | ^~ 2 | - Operator needs two arguments, but was called with 1 + `!=' expected two arguments, but was called with 1 diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/type_no_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/type_no_args.expected index b8f7607ea..b5e865ba4 100644 --- a/tests/unittests/resources/DiagnosticsSuite/compileTime/type_no_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/type_no_args.expected @@ -2,4 +2,4 @@ In file tests/unittests/resources/DiagnosticsSuite/compileTime/type_no_args.ark: 1 | (print (type)) | ^~~~ 2 | - Operator needs one argument, but was called with 0 + `type' expected one argument, but was called with 0 From 1ff32eec47e79d0833ef588021923317aadcb760 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 5 Jan 2026 19:05:40 +0100 Subject: [PATCH 03/16] feat(ast lowerer): breakpoints can be placed anywhere without disturbing the argument counts for functions and operators, as they produce nothing --- include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 8 ++ .../Compiler/Lowerer/ASTLowerer.cpp | 19 +++- .../CompilerSuite/ir/breakpoints.ark | 19 ++++ .../CompilerSuite/ir/breakpoints.expected | 105 ++++++++++++++++++ 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/unittests/resources/CompilerSuite/ir/breakpoints.ark create mode 100644 tests/unittests/resources/CompilerSuite/ir/breakpoints.expected diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index de9971fb2..36ece2193 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -128,6 +128,14 @@ namespace Ark::internal */ static std::optional getListInstruction(const std::string& name) noexcept; + /** + * Checks if a node is a list and is a call to 'breakpoint' + * @param node node to check + * @return true if the node is a 'breakpoint' call: (breakpoint ) + * @return false otherwise + */ + static bool isBreakpoint(const Node& node); + /** * Checks if a node is a list and has a keyboard as its first node, indicating if it's producing a value on the stack or not * @param node node to check diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index d5d81ed80..e47c39e15 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -75,6 +75,13 @@ namespace Ark::internal return std::nullopt; } + bool ASTLowerer::isBreakpoint(const Node& node) + { + if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol) + return node.constList().front().string() == "breakpoint"; + return false; + } + bool ASTLowerer::nodeProducesOutput(const Node& node) { if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword) @@ -557,7 +564,8 @@ namespace Ark::internal // value for argument b, but loaded it as a reference. for (Node& value : std::ranges::drop_view(call.list(), 1) | std::views::reverse) { - if (nodeProducesOutput(value)) + // FIXME: in (foo a b (breakpoint (< c 0)) c), we will push c before the breakpoint + if (nodeProducesOutput(value) || isBreakpoint(value)) { // we have to disallow usage of references in tail calls, because if we shuffle arguments around while using refs, they will end up with the same value if (value.nodeType() == NodeType::Symbol && is_tail_call) @@ -671,7 +679,7 @@ namespace Ark::internal std::size_t args_count = 0; for (auto it = x.constList().begin() + start_index, it_end = x.constList().end(); it != it_end; ++it) { - if (it->nodeType() != NodeType::Capture) + if (it->nodeType() != NodeType::Capture && !isBreakpoint(*it)) args_count++; } // call the procedure @@ -695,17 +703,18 @@ namespace Ark::internal std::size_t exp_count = 0; for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index) { - if (nodeProducesOutput(x.constList()[index])) + const bool is_breakpoint = isBreakpoint(x.constList()[index]); + if (nodeProducesOutput(x.constList()[index]) || is_breakpoint) compileExpression(x.list()[index], p, false, false); else buildAndThrowError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x.constList()[index]); - if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size) + if (!is_breakpoint && ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)) exp_count++; // 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 && !isTernaryInst(op)) + if (exp_count >= 2 && !isTernaryInst(op) && !is_breakpoint) page(p).emplace_back(op); } diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.ark b/tests/unittests/resources/CompilerSuite/ir/breakpoints.ark new file mode 100644 index 000000000..c19d793f3 --- /dev/null +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.ark @@ -0,0 +1,19 @@ +(breakpoint true) + +(let foo (fun (a b c) { + (breakpoint (> a 5)) + (print (format "a={}, b={}, c={}" a b c)) + (let res (* a b (breakpoint (< c 0)) c 5)) + (print (breakpoint true) res (breakpoint false)) + res })) + +(foo 1 2 3) + +(if (> (breakpoint true) 2 1) + { + (print "before call") + (foo 1 (breakpoint false) 2 3) + (print "after call") }) + +(let bar (fun () ())) +(bar (breakpoint true)) diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected new file mode 100644 index 000000000..01f448939 --- /dev/null +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected @@ -0,0 +1,105 @@ +page_0 + BUILTIN 1 + BREAKPOINT 0 + LOAD_CONST 0 + STORE 0 + PUSH_RETURN_ADDRESS L3 + LOAD_CONST 4 + LOAD_CONST 5 + LOAD_CONST 6 + LOAD_SYMBOL_BY_INDEX 0 + CALL 3 +.L3: + POP 0 + BUILTIN 1 + BREAKPOINT 0 + LOAD_CONST 5 + LOAD_CONST 6 + GT 0 + POP_JUMP_IF_TRUE L4 + JUMP L5 +.L4: + PUSH_RETURN_ADDRESS L6 + LOAD_CONST 7 + BUILTIN 9 + CALL 1 +.L6: + POP 0 + PUSH_RETURN_ADDRESS L7 + LOAD_CONST 4 + LOAD_CONST 5 + BUILTIN 0 + BREAKPOINT 0 + LOAD_CONST 6 + LOAD_SYMBOL_BY_INDEX 0 + CALL 3 +.L7: + POP 0 + PUSH_RETURN_ADDRESS L8 + LOAD_CONST 8 + BUILTIN 9 + CALL 1 +.L8: + POP 0 +.L5: + LOAD_CONST 9 + STORE 5 + PUSH_RETURN_ADDRESS L9 + BUILTIN 1 + BREAKPOINT 0 + LOAD_SYMBOL_BY_INDEX 0 + CALL 0 +.L9: + HALT 0 + +page_1 + STORE 1 + STORE 2 + STORE 3 + LOAD_SYMBOL_BY_INDEX 2 + LOAD_CONST 1 + GT 0 + BREAKPOINT 0 + PUSH_RETURN_ADDRESS L0 + PUSH_RETURN_ADDRESS L1 + LOAD_SYMBOL_BY_INDEX 0 + LOAD_SYMBOL_BY_INDEX 1 + LOAD_SYMBOL_BY_INDEX 2 + LOAD_CONST 2 + BUILTIN 27 + CALL 4 +.L1: + BUILTIN 9 + CALL 1 +.L0: + POP 0 + LOAD_SYMBOL_BY_INDEX 2 + LOAD_SYMBOL_BY_INDEX 1 + MUL 0 + LOAD_SYMBOL_BY_INDEX 0 + LOAD_CONST 3 + LT 0 + BREAKPOINT 0 + LOAD_SYMBOL_BY_INDEX 0 + MUL 0 + LOAD_CONST 1 + MUL 0 + STORE 4 + PUSH_RETURN_ADDRESS L2 + BUILTIN 0 + BREAKPOINT 0 + LOAD_SYMBOL_BY_INDEX 0 + BUILTIN 1 + BREAKPOINT 0 + BUILTIN 9 + CALL 1 +.L2: + POP 0 + LOAD_SYMBOL_BY_INDEX 0 + RET 0 + HALT 0 + +page_2 + BUILTIN 2 + RET 0 + HALT 0 \ No newline at end of file From 8cdae3e64a4684601aaf06136f44746caee1069c Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 5 Jan 2026 19:43:23 +0100 Subject: [PATCH 04/16] feat(cli): add -fdebugger to toggle the debugger in the VM when an error is caught --- include/Ark/Constants.hpp.in | 1 + include/Ark/VM/State.hpp | 6 +- src/arkreactor/VM/State.cpp | 35 ++-- src/arkreactor/VM/VM.cpp | 15 +- src/arkscript/main.cpp | 5 +- .../CompilerSuite/ir/breakpoints.expected | 26 +-- .../optimized_ir/fused_math_ops.expected | 162 +++++++++--------- 7 files changed, 132 insertions(+), 118 deletions(-) diff --git a/include/Ark/Constants.hpp.in b/include/Ark/Constants.hpp.in index 30a15d915..4b7a626b5 100644 --- a/include/Ark/Constants.hpp.in +++ b/include/Ark/Constants.hpp.in @@ -56,6 +56,7 @@ namespace Ark constexpr uint16_t FeatureASTOptimizer = 1 << 2; ///< This is disabled so that embedding ArkScript does not prune nodes from the AST ; it is active in the `arkscript` executable constexpr uint16_t FeatureIROptimizer = 1 << 3; constexpr uint16_t FeatureNameResolver = 1 << 4; + constexpr uint16_t FeatureVMDebugger = 1 << 5; ///< This is disabled so that embedding ArkScript does not launch the debugger on every error when running code constexpr uint16_t FeatureDumpIR = 1 << 14; /// This feature should only be used in tests, to disable diagnostics generation and enable exceptions to be thrown diff --git a/include/Ark/VM/State.hpp b/include/Ark/VM/State.hpp index 7c11c696e..c80543448 100644 --- a/include/Ark/VM/State.hpp +++ b/include/Ark/VM/State.hpp @@ -131,11 +131,10 @@ namespace Ark * * @param file the path of file code to compile * @param output set path of .arkc file - * @param features compiler features to enable/disable * @return true on success * @return false on failure and raise an exception */ - [[nodiscard]] bool compile(const std::string& file, const std::string& output, uint16_t features) const; + [[nodiscard]] bool compile(const std::string& file, const std::string& output) const; static void throwStateError(const std::string& message) { @@ -143,6 +142,7 @@ namespace Ark } unsigned m_debug_level; + uint16_t m_features; bytecode_t m_bytecode; std::vector m_libenv; @@ -157,7 +157,7 @@ namespace Ark bytecode_t m_code; // related to the execution - std::unordered_map m_binded; ///< Values binded to the State, to be used by the VM + std::unordered_map m_bound; ///< Values bound to the State, to be used by the VM /** * @brief Get an instruction in a given page, with a given instruction pointer diff --git a/src/arkreactor/VM/State.cpp b/src/arkreactor/VM/State.cpp index 6782b1e0a..eef26f57d 100644 --- a/src/arkreactor/VM/State.cpp +++ b/src/arkreactor/VM/State.cpp @@ -18,15 +18,16 @@ namespace Ark { State::State(const std::vector& libenv) noexcept : m_debug_level(0), + m_features(0), m_libenv(libenv), m_filename(ARK_NO_NAME_FILE), m_max_page_size(0) { // default value for builtin__sys:args is empty list const Value val(ValueType::List); - m_binded[std::string(internal::Language::SysArgs)] = val; + m_bound[std::string(internal::Language::SysArgs)] = val; - m_binded[std::string(internal::Language::SysProgramName)] = Value(""); + m_bound[std::string(internal::Language::SysProgramName)] = Value(""); } bool State::feed(const std::string& bytecode_filename, const bool fail_with_exception) @@ -61,11 +62,11 @@ namespace Ark } } - bool State::compile(const std::string& file, const std::string& output, const uint16_t features) const + bool State::compile(const std::string& file, const std::string& output) const { - Welder welder(m_debug_level, m_libenv, features); - for (const auto& p : m_binded) - welder.registerSymbol(p.first); + Welder welder(m_debug_level, m_libenv, m_features); + for (const auto& key : m_bound | std::views::keys) + welder.registerSymbol(key); if (!welder.computeASTFromFile(file)) return false; @@ -81,13 +82,15 @@ namespace Ark bool State::doFile(const std::string& file_path, const uint16_t features) { + m_features = features; + if (!Utils::fileExists(file_path)) { fmt::print(fmt::fg(fmt::color::red), "Can not find file '{}'\n", file_path); return false; } m_filename = file_path; - m_binded[std::string(internal::Language::SysProgramName)] = Value(std::filesystem::path(m_filename).filename().string()); + m_bound[std::string(internal::Language::SysProgramName)] = Value(std::filesystem::path(m_filename).filename().string()); const bytecode_t bytecode = Utils::readFileAsBytes(file_path); BytecodeReader bcr; @@ -102,7 +105,7 @@ namespace Ark if (!exists(cache_directory)) create_directory(cache_directory); - if (compile(file_path, bytecode_path, features) && feed(bytecode_path)) + if (compile(file_path, bytecode_path) && feed(bytecode_path)) return true; } else if (feed(bytecode)) // it's a bytecode file @@ -112,8 +115,10 @@ namespace Ark bool State::doString(const std::string& code, const uint16_t features) { - Welder welder(m_debug_level, m_libenv, features); - for (const auto& p : m_binded) + m_features = features; + + Welder welder(m_debug_level, m_libenv, m_features); + for (const auto& p : m_bound) welder.registerSymbol(p.first); if (!welder.computeASTFromString(code)) @@ -125,7 +130,7 @@ namespace Ark void State::loadFunction(const std::string& name, Procedure::CallbackType&& function) noexcept { - m_binded[name] = Value(std::move(function)); + m_bound[name] = Value(std::move(function)); } void State::setArgs(const std::vector& args) noexcept @@ -135,7 +140,7 @@ namespace Ark return Value(arg); }); - m_binded[std::string(internal::Language::SysArgs)] = val; + m_bound[std::string(internal::Language::SysArgs)] = val; } void State::setDebug(const unsigned level) noexcept @@ -210,13 +215,13 @@ namespace Ark m_inst_locations.clear(); m_max_page_size = 0; m_code.clear(); - m_binded.clear(); + m_bound.clear(); // default value for builtin__sys:args is empty list const Value val(ValueType::List); - m_binded[std::string(internal::Language::SysArgs)] = val; + m_bound[std::string(internal::Language::SysArgs)] = val; - m_binded[std::string(internal::Language::SysProgramName)] = Value(""); + m_bound[std::string(internal::Language::SysProgramName)] = Value(""); } } diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 430fbfe80..89e811cbb 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -173,7 +173,7 @@ namespace Ark // loading bound stuff // put them in the global frame if we can, aka the first one - for (const auto& [sym_id, value] : m_state.m_binded) + for (const auto& [sym_id, value] : m_state.m_bound) { auto it = std::ranges::find(m_state.m_symbols, sym_id); if (it != m_state.m_symbols.end()) @@ -1180,6 +1180,10 @@ namespace Ark DISPATCH(); } +#pragma endregion + +#pragma region "Operators" + TARGET(BREAKPOINT) { { @@ -1190,10 +1194,6 @@ namespace Ark DISPATCH(); } -#pragma endregion - -#pragma region "Operators" - TARGET(ADD) { Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); @@ -2260,6 +2260,11 @@ namespace Ark text += '\n'; fmt::println("{}", text); backtrace(context); + + if (m_state.m_features & FeatureVMDebugger) + { + // TODO: launch debugger + } #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes m_exit_code = 0; diff --git a/src/arkscript/main.cpp b/src/arkscript/main.cpp index 4cf124543..16ae4e84b 100644 --- a/src/arkscript/main.cpp +++ b/src/arkscript/main.cpp @@ -80,12 +80,15 @@ int main(int argc, char** argv) option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimizer; }) | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimizer; }) ).doc("Toggle on and off the IR optimizer pass"); + auto vm_debugger_flag = ( + option("-fdebugger").call([&] { passes |= Ark::FeatureVMDebugger; }) + ).doc("Turn on the debugger"); auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; }) .doc("Dump IR to file.ark.ir"); const auto run_flags = ( // cppcheck-suppress constStatement - debug_flag, lib_dir_flag, import_solver_pass_flag, macro_proc_pass_flag, optimizer_pass_flag, ir_optimizer_pass_flag, ir_dump + debug_flag, lib_dir_flag, import_solver_pass_flag, macro_proc_pass_flag, optimizer_pass_flag, ir_optimizer_pass_flag, vm_debugger_flag, ir_dump ); auto cli = ( diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected index 01f448939..d4b95686c 100644 --- a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected @@ -7,7 +7,7 @@ page_0 LOAD_CONST 4 LOAD_CONST 5 LOAD_CONST 6 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 CALL 3 .L3: POP 0 @@ -31,7 +31,7 @@ page_0 BUILTIN 0 BREAKPOINT 0 LOAD_CONST 6 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 CALL 3 .L7: POP 0 @@ -47,7 +47,7 @@ page_0 PUSH_RETURN_ADDRESS L9 BUILTIN 1 BREAKPOINT 0 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 CALL 0 .L9: HALT 0 @@ -56,15 +56,15 @@ page_1 STORE 1 STORE 2 STORE 3 - LOAD_SYMBOL_BY_INDEX 2 + LOAD_FAST_BY_INDEX 2 LOAD_CONST 1 GT 0 BREAKPOINT 0 PUSH_RETURN_ADDRESS L0 PUSH_RETURN_ADDRESS L1 - LOAD_SYMBOL_BY_INDEX 0 - LOAD_SYMBOL_BY_INDEX 1 - LOAD_SYMBOL_BY_INDEX 2 + LOAD_FAST_BY_INDEX 0 + LOAD_FAST_BY_INDEX 1 + LOAD_FAST_BY_INDEX 2 LOAD_CONST 2 BUILTIN 27 CALL 4 @@ -73,14 +73,14 @@ page_1 CALL 1 .L0: POP 0 - LOAD_SYMBOL_BY_INDEX 2 - LOAD_SYMBOL_BY_INDEX 1 + LOAD_FAST_BY_INDEX 2 + LOAD_FAST_BY_INDEX 1 MUL 0 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 LOAD_CONST 3 LT 0 BREAKPOINT 0 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 MUL 0 LOAD_CONST 1 MUL 0 @@ -88,14 +88,14 @@ page_1 PUSH_RETURN_ADDRESS L2 BUILTIN 0 BREAKPOINT 0 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 BUILTIN 1 BREAKPOINT 0 BUILTIN 9 CALL 1 .L2: POP 0 - LOAD_SYMBOL_BY_INDEX 0 + LOAD_FAST_BY_INDEX 0 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected index 7d911eb0f..d1926012f 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected @@ -8,387 +8,387 @@ page_0 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 41, 41 + FUSED_MATH 42, 42, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 39, 41 + FUSED_MATH 42, 40, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 40, 41 + FUSED_MATH 42, 41, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 38, 41 + FUSED_MATH 42, 39, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 41, 40 + FUSED_MATH 42, 42, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 39, 40 + FUSED_MATH 42, 40, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 40, 40 + FUSED_MATH 42, 41, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 38, 40 + FUSED_MATH 42, 39, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 41, 39 + FUSED_MATH 42, 42, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 39, 39 + FUSED_MATH 42, 40, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 40, 39 + FUSED_MATH 42, 41, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 38, 39 + FUSED_MATH 42, 39, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 41, 38 + FUSED_MATH 42, 42, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 39, 38 + FUSED_MATH 42, 40, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 40, 38 + FUSED_MATH 42, 41, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 41, 38, 38 + FUSED_MATH 42, 39, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 41, 41 + FUSED_MATH 41, 42, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 39, 41 + FUSED_MATH 41, 40, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 40, 41 + FUSED_MATH 41, 41, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 38, 41 + FUSED_MATH 41, 39, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 41, 40 + FUSED_MATH 41, 42, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 39, 40 + FUSED_MATH 41, 40, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 40, 40 + FUSED_MATH 41, 41, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 38, 40 + FUSED_MATH 41, 39, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 41, 39 + FUSED_MATH 41, 42, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 39, 39 + FUSED_MATH 41, 40, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 40, 39 + FUSED_MATH 41, 41, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 38, 39 + FUSED_MATH 41, 39, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 41, 38 + FUSED_MATH 41, 42, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 39, 38 + FUSED_MATH 41, 40, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 40, 38 + FUSED_MATH 41, 41, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 40, 38, 38 + FUSED_MATH 41, 39, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 41, 41 + FUSED_MATH 40, 42, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 39, 41 + FUSED_MATH 40, 40, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 40, 41 + FUSED_MATH 40, 41, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 38, 41 + FUSED_MATH 40, 39, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 41, 40 + FUSED_MATH 40, 42, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 39, 40 + FUSED_MATH 40, 40, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 40, 40 + FUSED_MATH 40, 41, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 38, 40 + FUSED_MATH 40, 39, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 41, 39 + FUSED_MATH 40, 42, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 39, 39 + FUSED_MATH 40, 40, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 40, 39 + FUSED_MATH 40, 41, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 38, 39 + FUSED_MATH 40, 39, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 41, 38 + FUSED_MATH 40, 42, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 39, 38 + FUSED_MATH 40, 40, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 40, 38 + FUSED_MATH 40, 41, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 39, 38, 38 + FUSED_MATH 40, 39, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 41, 41 + FUSED_MATH 39, 42, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 39, 41 + FUSED_MATH 39, 40, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 40, 41 + FUSED_MATH 39, 41, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 38, 41 + FUSED_MATH 39, 39, 42 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 41, 40 + FUSED_MATH 39, 42, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 39, 40 + FUSED_MATH 39, 40, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 40, 40 + FUSED_MATH 39, 41, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 38, 40 + FUSED_MATH 39, 39, 41 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 41, 39 + FUSED_MATH 39, 42, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 39, 39 + FUSED_MATH 39, 40, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 40, 39 + FUSED_MATH 39, 41, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 38, 39 + FUSED_MATH 39, 39, 40 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 41, 38 + FUSED_MATH 39, 42, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 39, 38 + FUSED_MATH 39, 40, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 40, 38 + FUSED_MATH 39, 41, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 LOAD_FAST_BY_INDEX 0 - FUSED_MATH 38, 38, 38 + FUSED_MATH 39, 39, 39 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 41, 41, 0 + FUSED_MATH 42, 42, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 39, 41, 0 + FUSED_MATH 40, 42, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 40, 41, 0 + FUSED_MATH 41, 42, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 38, 41, 0 + FUSED_MATH 39, 42, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 41, 40, 0 + FUSED_MATH 42, 41, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 39, 40, 0 + FUSED_MATH 40, 41, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 40, 40, 0 + FUSED_MATH 41, 41, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 38, 40, 0 + FUSED_MATH 39, 41, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 41, 39, 0 + FUSED_MATH 42, 40, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 39, 39, 0 + FUSED_MATH 40, 40, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 40, 39, 0 + FUSED_MATH 41, 40, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 38, 39, 0 + FUSED_MATH 39, 40, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 41, 38, 0 + FUSED_MATH 42, 39, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 39, 38, 0 + FUSED_MATH 40, 39, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 40, 38, 0 + FUSED_MATH 41, 39, 0 LOAD_FAST_BY_INDEX 3 LOAD_FAST_BY_INDEX 2 LOAD_FAST_BY_INDEX 1 - FUSED_MATH 38, 38, 0 + FUSED_MATH 39, 39, 0 LIST 80 CALL_BUILTIN 9, 1 .L0: - HALT 0 + HALT 0 \ No newline at end of file From cf0ba80c43c3b2c3cd3b468de8909a1925481d59 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 12 Jan 2026 18:06:09 +0100 Subject: [PATCH 05/16] refactor: create a dedicated Ark/VM/Value/ folder for better organization --- include/Ark/Ark.hpp | 3 ++- include/Ark/Builtins/Builtins.hpp | 2 +- include/Ark/Compiler/BytecodeReader.hpp | 2 +- include/Ark/{VM => }/State.hpp | 2 +- include/Ark/TypeChecker.hpp | 2 +- include/Ark/VM/DefaultValues.hpp | 2 +- include/Ark/VM/ExecutionContext.hpp | 2 +- include/Ark/VM/ScopeView.hpp | 4 ++-- include/Ark/VM/VM.hpp | 6 +++--- include/Ark/VM/Value/ClosureScope.hpp | 2 +- include/Ark/VM/Value/Dict.hpp | 2 +- include/Ark/VM/{ => Value}/Future.hpp | 2 +- include/Ark/VM/{ => Value}/Value.hpp | 0 include/CLI/REPL/Repl.hpp | 2 +- src/arkreactor/{VM => }/State.cpp | 2 +- src/arkreactor/VM/{ => Value}/Future.cpp | 2 +- src/arkreactor/VM/Value/Procedure.cpp | 2 +- src/arkreactor/VM/{ => Value}/Value.cpp | 2 +- tests/benchmarks/main.cpp | 2 +- tests/unittests/Suites/TypeCheckerSuite.cpp | 4 ++-- 20 files changed, 24 insertions(+), 23 deletions(-) rename include/Ark/{VM => }/State.hpp (99%) rename include/Ark/VM/{ => Value}/Future.hpp (97%) rename include/Ark/VM/{ => Value}/Value.hpp (100%) rename src/arkreactor/{VM => }/State.cpp (99%) rename src/arkreactor/VM/{ => Value}/Future.cpp (97%) rename src/arkreactor/VM/{ => Value}/Value.cpp (99%) diff --git a/include/Ark/Ark.hpp b/include/Ark/Ark.hpp index 30cb11c32..421b27902 100644 --- a/include/Ark/Ark.hpp +++ b/include/Ark/Ark.hpp @@ -14,9 +14,10 @@ #include #include #include +#include +#include #include #include -#include #include #endif // ARK_ARK_HPP diff --git a/include/Ark/Builtins/Builtins.hpp b/include/Ark/Builtins/Builtins.hpp index ba49bf270..d87b6d1ca 100644 --- a/include/Ark/Builtins/Builtins.hpp +++ b/include/Ark/Builtins/Builtins.hpp @@ -13,7 +13,7 @@ #include -#include +#include namespace Ark { diff --git a/include/Ark/Compiler/BytecodeReader.hpp b/include/Ark/Compiler/BytecodeReader.hpp index d5f3c1c11..ccfe50af0 100644 --- a/include/Ark/Compiler/BytecodeReader.hpp +++ b/include/Ark/Compiler/BytecodeReader.hpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include namespace Ark diff --git a/include/Ark/VM/State.hpp b/include/Ark/State.hpp similarity index 99% rename from include/Ark/VM/State.hpp rename to include/Ark/State.hpp index c80543448..7f22db921 100644 --- a/include/Ark/VM/State.hpp +++ b/include/Ark/State.hpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/Ark/TypeChecker.hpp b/include/Ark/TypeChecker.hpp index e5c7ac5e6..baa73baa5 100644 --- a/include/Ark/TypeChecker.hpp +++ b/include/Ark/TypeChecker.hpp @@ -17,7 +17,7 @@ #include #include -#include +#include namespace Ark { diff --git a/include/Ark/VM/DefaultValues.hpp b/include/Ark/VM/DefaultValues.hpp index 9709537d4..a9eb6eaae 100644 --- a/include/Ark/VM/DefaultValues.hpp +++ b/include/Ark/VM/DefaultValues.hpp @@ -11,7 +11,7 @@ #ifndef ARK_VM_DEFAULTVALUES_HPP #define ARK_VM_DEFAULTVALUES_HPP -#include +#include namespace Ark { diff --git a/include/Ark/VM/ExecutionContext.hpp b/include/Ark/VM/ExecutionContext.hpp index 0e0bbbeb7..afdc15db7 100644 --- a/include/Ark/VM/ExecutionContext.hpp +++ b/include/Ark/VM/ExecutionContext.hpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include diff --git a/include/Ark/VM/ScopeView.hpp b/include/Ark/VM/ScopeView.hpp index db9857c59..11fec5315 100644 --- a/include/Ark/VM/ScopeView.hpp +++ b/include/Ark/VM/ScopeView.hpp @@ -1,5 +1,5 @@ /** - * @file Scope.hpp + * @file ScopeView.hpp * @author Lexy Plateau (lexplt.dev@gmail.com) * @brief The virtual machine scope system * @date 2020-10-27 @@ -15,7 +15,7 @@ #include #include -#include +#include namespace Ark::internal { diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index 63360be93..dda53e460 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -24,8 +24,8 @@ #include #include -#include -#include +#include +#include #include #include #include @@ -33,7 +33,7 @@ #include #include #include -#include +#include namespace Ark { diff --git a/include/Ark/VM/Value/ClosureScope.hpp b/include/Ark/VM/Value/ClosureScope.hpp index 5474eadbe..cdc13bb33 100644 --- a/include/Ark/VM/Value/ClosureScope.hpp +++ b/include/Ark/VM/Value/ClosureScope.hpp @@ -16,7 +16,7 @@ #include #include -#include +#include namespace Ark::internal { diff --git a/include/Ark/VM/Value/Dict.hpp b/include/Ark/VM/Value/Dict.hpp index d37260df8..c4038d457 100644 --- a/include/Ark/VM/Value/Dict.hpp +++ b/include/Ark/VM/Value/Dict.hpp @@ -11,7 +11,7 @@ #ifndef ARK_VM_VALUE_DICT_HPP #define ARK_VM_VALUE_DICT_HPP -#include +#include #include #include diff --git a/include/Ark/VM/Future.hpp b/include/Ark/VM/Value/Future.hpp similarity index 97% rename from include/Ark/VM/Future.hpp rename to include/Ark/VM/Value/Future.hpp index 6a8d0f2bb..23d0f35cc 100644 --- a/include/Ark/VM/Future.hpp +++ b/include/Ark/VM/Value/Future.hpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include namespace Ark diff --git a/include/Ark/VM/Value.hpp b/include/Ark/VM/Value/Value.hpp similarity index 100% rename from include/Ark/VM/Value.hpp rename to include/Ark/VM/Value/Value.hpp diff --git a/include/CLI/REPL/Repl.hpp b/include/CLI/REPL/Repl.hpp index cb59e4bc7..798efe9e3 100644 --- a/include/CLI/REPL/Repl.hpp +++ b/include/CLI/REPL/Repl.hpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include diff --git a/src/arkreactor/VM/State.cpp b/src/arkreactor/State.cpp similarity index 99% rename from src/arkreactor/VM/State.cpp rename to src/arkreactor/State.cpp index eef26f57d..77caaff2b 100644 --- a/src/arkreactor/VM/State.cpp +++ b/src/arkreactor/State.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/arkreactor/VM/Future.cpp b/src/arkreactor/VM/Value/Future.cpp similarity index 97% rename from src/arkreactor/VM/Future.cpp rename to src/arkreactor/VM/Value/Future.cpp index 5f050155e..cd4b039c6 100644 --- a/src/arkreactor/VM/Future.cpp +++ b/src/arkreactor/VM/Value/Future.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/arkreactor/VM/Value/Procedure.cpp b/src/arkreactor/VM/Value/Procedure.cpp index 6c66853a2..f4b23d2e9 100644 --- a/src/arkreactor/VM/Value/Procedure.cpp +++ b/src/arkreactor/VM/Value/Procedure.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/arkreactor/VM/Value.cpp b/src/arkreactor/VM/Value/Value.cpp similarity index 99% rename from src/arkreactor/VM/Value.cpp rename to src/arkreactor/VM/Value/Value.cpp index ded447e44..90f84a38b 100644 --- a/src/arkreactor/VM/Value.cpp +++ b/src/arkreactor/VM/Value/Value.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/tests/benchmarks/main.cpp b/tests/benchmarks/main.cpp index b518a7d6e..5f15b36b3 100644 --- a/tests/benchmarks/main.cpp +++ b/tests/benchmarks/main.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #define ARK_CREATE_RUNTIME_BENCH(name) \ diff --git a/tests/unittests/Suites/TypeCheckerSuite.cpp b/tests/unittests/Suites/TypeCheckerSuite.cpp index 96330e850..d1296a286 100644 --- a/tests/unittests/Suites/TypeCheckerSuite.cpp +++ b/tests/unittests/Suites/TypeCheckerSuite.cpp @@ -5,9 +5,9 @@ #include #include -#include +#include #include -#include +#include #include #include #include From d43ba706710f92c847a2ab42ecd58e03e3083f77 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 12 Jan 2026 18:25:34 +0100 Subject: [PATCH 06/16] refactor: print vm pointers in VM::showBacktraceWithException instead of VM::backtrace, so that we have access to the pointers when creating the debugger --- src/arkreactor/VM/VM.cpp | 24 +++++++++++++----------- tests/unittests/TestsHelper.cpp | 2 -- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 89e811cbb..864bf7ae5 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -2259,12 +2259,25 @@ namespace Ark if (!text.empty() && text.back() != '\n') text += '\n'; fmt::println("{}", text); + + const std::size_t saved_ip = context.ip; + const std::size_t saved_pp = context.pp; + const uint16_t saved_sp = context.sp; + backtrace(context); + fmt::println( + "At IP: {}, PP: {}, SP: {}", + // dividing by 4 because the instructions are actually on 4 bytes + fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)), + fmt::styled(saved_pp, fmt::fg(fmt::color::green)), + fmt::styled(saved_sp, fmt::fg(fmt::color::yellow))); + if (m_state.m_features & FeatureVMDebugger) { // TODO: launch debugger } + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes m_exit_code = 0; @@ -2310,9 +2323,6 @@ namespace Ark void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize) { - const std::size_t saved_ip = context.ip; - const std::size_t saved_pp = context.pp; - const uint16_t saved_sp = context.sp; constexpr std::size_t max_consecutive_traces = 7; const auto maybe_location = findSourceLocation(context.ip, context.pp); @@ -2404,13 +2414,5 @@ namespace Ark old_scope.atPos(i).second.toString(*this)); } } - - fmt::println( - os, - "At IP: {}, PP: {}, SP: {}", - // dividing by 4 because the instructions are actually on 4 bytes - fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), - fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()), - fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style())); } } diff --git a/tests/unittests/TestsHelper.cpp b/tests/unittests/TestsHelper.cpp index 156d029df..c33c8ebc3 100644 --- a/tests/unittests/TestsHelper.cpp +++ b/tests/unittests/TestsHelper.cpp @@ -95,8 +95,6 @@ std::string sanitizeRuntimeError(const std::exception& e) while (diag.find(ARK_TESTS_ROOT) != std::string::npos) diag.erase(diag.find(ARK_TESTS_ROOT), std::size(ARK_TESTS_ROOT) - 1); Ark::Utils::ltrim(Ark::Utils::rtrim(diag)); - // remove last line, At IP:.., PP:.., SP:.. - diag.erase(diag.find_last_of('\n'), diag.size() - 1); // we most likely have a blank line at the end now Ark::Utils::rtrim(diag); From f4650b0f46fb8cb3b77d5a0e55d7297c11702571 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 12 Jan 2026 19:29:06 +0100 Subject: [PATCH 07/16] feat(vm): enhancing the arity error handling and adding a test for when we have 0 argument --- src/arkreactor/VM/VM.cpp | 5 +++-- .../DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark | 2 ++ .../runtime/arity_error_fun_no_arg.expected | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.expected diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 864bf7ae5..569addf42 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -2195,7 +2195,7 @@ namespace Ark return MaxValue16Bits; } - void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context) + void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context) { std::vector arg_names; arg_names.reserve(expected_arg_count + 1); @@ -2215,7 +2215,7 @@ namespace Ark { assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions"); for (std::size_t i = 0; i < expected_arg_count; ++i) - arg_names.push_back(std::string(1, static_cast('a' + i))); + arg_names.emplace_back(1, static_cast('a' + i)); } std::vector arg_vals; @@ -2233,6 +2233,7 @@ namespace Ark { context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr(); context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr(); + context.sp -= 2; returnFromFuncCall(context); } diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark new file mode 100644 index 000000000..4d4cd4a2a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark @@ -0,0 +1,2 @@ +(let foo (fun () ())) +(print (foo 1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.expected new file mode 100644 index 000000000..b90afbb4a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.expected @@ -0,0 +1,7 @@ +ArityError: When calling `(foo 1)', received 1 argument, but expected 0: `(foo)' + +In file tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_fun_no_arg.ark:2 + 1 | (let foo (fun () ())) + 2 | (print (foo 1)) + | ^~~~~~~~~~~~~~ + 3 | From e652ec3d8389bb7e5c7ee7d55a53a6cec4973023 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 12 Jan 2026 19:30:05 +0100 Subject: [PATCH 08/16] feat(debugger): creating a very basic debugger, started by VM::showBacktraceWithException --- include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 35 +++++- include/Ark/Compiler/ValTableElem.hpp | 4 + include/Ark/Compiler/Welder.hpp | 22 ++++ include/Ark/State.hpp | 28 ++++- include/Ark/VM/Debugger.hpp | 100 ++++++++++++++++ include/Ark/VM/VM.hpp | 11 +- .../Compiler/Lowerer/ASTLowerer.cpp | 23 +++- src/arkreactor/Compiler/ValTableElem.cpp | 10 ++ src/arkreactor/Compiler/Welder.cpp | 41 +++++++ src/arkreactor/State.cpp | 42 +++++-- src/arkreactor/VM/Debugger.cpp | 110 ++++++++++++++++++ src/arkreactor/VM/VM.cpp | 35 ++++-- 12 files changed, 430 insertions(+), 31 deletions(-) create mode 100644 include/Ark/VM/Debugger.hpp create mode 100644 src/arkreactor/VM/Debugger.cpp diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index 36ece2193..f0f840081 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -25,11 +25,14 @@ #include #include -namespace Ark::internal +namespace Ark { class State; class Welder; +} +namespace Ark::internal +{ /** * @brief The ArkScript AST to IR compiler * @@ -44,6 +47,21 @@ namespace Ark::internal */ explicit ASTLowerer(unsigned debug); + /** + * @brief Pre-fill tables (used by the debugger) + * + * @param symbols + * @param constants + */ + void addToTables(const std::vector& symbols, const std::vector& constants); + + /** + * @brief Start bytecode pages at a given offset (by default, 0) + * + * @param offset + */ + void offsetPagesBy(std::size_t offset); + /** * @brief Start the compilation * @@ -84,6 +102,7 @@ namespace Ark::internal // tables: symbols, values, plugins and codes std::vector m_symbols; std::vector m_values; + std::size_t m_start_page_at_offset = 0; ///< Used to offset the page numbers when compiling code in the debugger std::vector m_code_pages; std::vector m_temp_pages; ///< we need temporary code pages for some compilations passes IR::label_t m_current_label = 0; @@ -91,6 +110,18 @@ namespace Ark::internal Logger m_logger; + Page createNewCodePage(const bool temp = false) noexcept + { + if (!temp) + { + m_code_pages.emplace_back(); + return Page { .index = m_start_page_at_offset + m_code_pages.size() - 1u, .is_temp = false }; + } + + m_temp_pages.emplace_back(); + return Page { .index = m_temp_pages.size() - 1u, .is_temp = true }; + } + /** * @brief helper functions to get a temp or finalized code page * @@ -100,7 +131,7 @@ namespace Ark::internal IR::Block& page(const Page page) noexcept { if (!page.is_temp) - return m_code_pages[page.index]; + return m_code_pages[page.index - m_start_page_at_offset]; return m_temp_pages[page.index]; } diff --git a/include/Ark/Compiler/ValTableElem.hpp b/include/Ark/Compiler/ValTableElem.hpp index 3170f4738..53f78c849 100644 --- a/include/Ark/Compiler/ValTableElem.hpp +++ b/include/Ark/Compiler/ValTableElem.hpp @@ -40,6 +40,10 @@ namespace Ark::internal // automatic handling (Number/String/Function) explicit ValTableElem(const Node& node) noexcept; + // Numbers + explicit ValTableElem(double n) noexcept; + // Strings + explicit ValTableElem(const std::string& str) noexcept; // Functions explicit ValTableElem(std::size_t page) noexcept; diff --git a/include/Ark/Compiler/Welder.hpp b/include/Ark/Compiler/Welder.hpp index 20ad6d5fa..e3f23b86f 100644 --- a/include/Ark/Compiler/Welder.hpp +++ b/include/Ark/Compiler/Welder.hpp @@ -30,6 +30,8 @@ namespace Ark { + class Value; + /** * @brief The welder joins all the compiler passes */ @@ -65,12 +67,32 @@ namespace Ark */ bool computeASTFromString(const std::string& code); + /** + * @brief Compile code from a string, with a set of known symbols (useful for the debugger) + * + * @param code + * @param symbols + * @return true on success + */ + bool computeASTFromStringWithKnownSymbols(const std::string& code, const std::vector& symbols); + /** * @brief Compile the AST processed by computeASTFromFile / computeASTFromString + * * @return true on success */ bool generateBytecode(); + /** + * @brief Compile the AST processed by computeASTFromFile / computeASTFromString, with prefilled symbols and constants tables (useful for the debugger) + * + * @param symbols + * @param constants + * @param start_page_at_offset + * @return true on success + */ + bool generateBytecodeUsingTables(const std::vector& symbols, const std::vector& constants, std::size_t start_page_at_offset); + /** * @brief Save the generated bytecode to a given file * @param filename diff --git a/include/Ark/State.hpp b/include/Ark/State.hpp index 7f22db921..0d4ff9659 100644 --- a/include/Ark/State.hpp +++ b/include/Ark/State.hpp @@ -25,6 +25,11 @@ namespace Ark { + namespace internal + { + class Debugger; + } + /** * @brief Ark state to handle the dirty job of loading and compiling ArkScript code * @@ -115,8 +120,9 @@ namespace Ark void reset() noexcept; friend class VM; - friend class internal::Closure; friend class Repl; + friend class internal::Closure; + friend class internal::Debugger; private: /** @@ -153,12 +159,32 @@ namespace Ark std::vector m_constants; std::vector m_filenames; std::vector m_inst_locations; + std::vector m_pages; std::size_t m_max_page_size; bytecode_t m_code; // related to the execution std::unordered_map m_bound; ///< Values bound to the State, to be used by the VM + void addPagesToContiguousBytecode(const std::vector& pages, std::size_t start); + + /** + * @brief Compute the maximum length of the given code pages + * + * @param pages + * @return std::size_t + */ + static std::size_t maxPageSize(const std::vector& pages); + + /** + * @brief Used by the debugger to add code to the VM at runtime + * + * @param pages + * @param symbols + * @param constants + */ + void extendBytecode(const std::vector& pages, const std::vector& symbols, const std::vector& constants); + /** * @brief Get an instruction in a given page, with a given instruction pointer * diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp new file mode 100644 index 000000000..ec0a83a6e --- /dev/null +++ b/include/Ark/VM/Debugger.hpp @@ -0,0 +1,100 @@ +/** + * @file Debugger.hpp + * @author Lexy Plateau (lexplt.dev@gmail.com) + * @brief Debugger used by the VM when an error or a breakpoint is reached + * @date 2026-01-12 + * + * @copyright Copyright (c) 2026-01-12 + * + */ + +#ifndef ARK_VM_DEBUGGER_HPP +#define ARK_VM_DEBUGGER_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace Ark +{ + class VM; +} + +namespace Ark::internal +{ + struct SavedState + { + std::size_t ip; + std::size_t pp; + uint16_t sp; + uint16_t fc; + std::vector locals; + std::vector> closure_scopes; + }; + + class Debugger + { + public: + /** + * @brief Create a new Debugger object + * + * @param context context from the VM before displaying a backtrace + * @param libenv + * @param symbols symbols table of the VM + * @param constants constants table of the VM + */ + explicit Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants); + + /** + * @brief Save the current VM state, to get back to it once the debugger is done running + * + * @param context + */ + void saveState(const ExecutionContext& context); + + /** + * @brief Reset a VM context to the last state saved by the debugger + * + * @param context context to reset + */ + void resetContextToErrorState(ExecutionContext& context); + + /** + * @brief Start the debugger shell + * + * @param vm + * @param context + */ + void run(VM& vm, ExecutionContext& context); + + inline bool isRunning() const noexcept + { + return m_running; + } + + private: + std::vector> m_states; + std::vector m_libenv; + std::vector m_symbols; + std::vector m_constants; + bool m_running; + + std::string m_code; ///< Code added while inside the debugger + + /** + * @brief Take care of compiling new code using the existing data tables + * + * @param code + * @param start_page_at_offset offset to start the new pages at + * @return std::optional> optional set of bytecode pages if compilation succeeded + */ + std::optional> compile(const std::string& code, std::size_t start_page_at_offset); + }; +} + +#endif // ARK_VM_DEBUGGER_HPP diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index dda53e460..91cd53332 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace Ark { @@ -164,8 +165,9 @@ namespace Ark } friend class Value; - friend class internal::Closure; friend class Repl; + friend class internal::Closure; + friend class internal::Debugger; private: State& m_state; @@ -175,6 +177,7 @@ namespace Ark std::mutex m_mutex, m_mutex_futures; std::vector> m_shared_lib_objects; std::vector> m_futures; ///< Storing the promises while we are resolving them + std::unique_ptr m_debugger { nullptr }; // a little trick for operator[] and for pop Value m_no_value = internal::Builtins::nil; @@ -352,6 +355,8 @@ namespace Ark [[noreturn]] void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context); + void initDebugger(internal::ExecutionContext& context); + void showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context); /** @@ -361,9 +366,9 @@ namespace Ark * @param pp * @return std::optional */ - std::optional findSourceLocation(std::size_t ip, std::size_t pp) const; + [[nodiscard]] std::optional findSourceLocation(std::size_t ip, std::size_t pp) const; - std::string debugShowSource() const; + [[nodiscard]] std::string debugShowSource() const; /** * @brief Display a backtrace when the VM encounter an exception diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index e47c39e15..8f687ae4c 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -19,15 +19,28 @@ namespace Ark::internal m_logger("ASTLowerer", debug) {} + void ASTLowerer::addToTables(const std::vector& symbols, const std::vector& constants) + { + for (const std::string& sym : symbols) + m_symbols.emplace_back(sym); + for (const ValTableElem& elem : constants) + m_values.emplace_back(elem); + } + + void ASTLowerer::offsetPagesBy(std::size_t offset) + { + m_start_page_at_offset = offset; + } + void ASTLowerer::process(Node& ast) { m_logger.traceStart("process"); - m_code_pages.emplace_back(); // create empty page + const Page global = createNewCodePage(); // gather symbols, values, and start to create code segments compileExpression( ast, - /* current_page */ Page { .index = 0, .is_temp = false }, + /* current_page */ global, /* is_result_unused */ false, /* is_terminal */ false); m_logger.traceEnd(); @@ -418,8 +431,7 @@ namespace Ark::internal : LocalsLocator::ScopeType::Function); // create new page for function body - m_code_pages.emplace_back(); - const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false }; + const auto function_body_page = createNewCodePage(); // save page_id into the constants table as PageAddr and load the const page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x)); @@ -643,8 +655,7 @@ namespace Ark::internal if (!nodeProducesOutput(node)) buildAndThrowError(fmt::format("Can not call `{}', as it doesn't return a value", node.repr()), node); - m_temp_pages.emplace_back(); - const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true }; + const auto proc_page = createNewCodePage(/* temp= */ true); // compile the function resolution to a separate page if (node.nodeType() == NodeType::Symbol && !m_opened_vars.empty() && m_opened_vars.top() == node.string()) diff --git a/src/arkreactor/Compiler/ValTableElem.cpp b/src/arkreactor/Compiler/ValTableElem.cpp index 3a254066a..f925ec2ae 100644 --- a/src/arkreactor/Compiler/ValTableElem.cpp +++ b/src/arkreactor/Compiler/ValTableElem.cpp @@ -16,6 +16,16 @@ namespace Ark::internal } } + ValTableElem::ValTableElem(const double n) noexcept : + value(n), + type(ValTableElemType::Number) + {} + + ValTableElem::ValTableElem(const std::string& str) noexcept : + value(str), + type(ValTableElemType::String) + {} + ValTableElem::ValTableElem(std::size_t page) noexcept : value(page), type(ValTableElemType::PageAddr) diff --git a/src/arkreactor/Compiler/Welder.cpp b/src/arkreactor/Compiler/Welder.cpp index bf097e825..58f5f183d 100644 --- a/src/arkreactor/Compiler/Welder.cpp +++ b/src/arkreactor/Compiler/Welder.cpp @@ -8,7 +8,9 @@ #include #include #include +#include +#include #include #include @@ -48,6 +50,15 @@ namespace Ark return computeAST(ARK_NO_NAME_FILE, code); } + bool Welder::computeASTFromStringWithKnownSymbols(const std::string& code, const std::vector& symbols) + { + m_root_file = std::filesystem::current_path(); // No filename given, take the current working directory + + for (const std::string& sym : symbols) + m_name_resolver.addDefinedSymbol(sym, /* is_mutable= */ false); + return computeAST(ARK_NO_NAME_FILE, code); + } + bool Welder::generateBytecode() { try @@ -79,6 +90,36 @@ namespace Ark } } + bool Welder::generateBytecodeUsingTables(const std::vector& symbols, const std::vector& constants, const std::size_t start_page_at_offset) + { + std::vector values; + for (const Value& constant : constants) + { + switch (constant.valueType()) + { + case ValueType::Number: + values.emplace_back(constant.number()); + break; + + case ValueType::String: + values.emplace_back(constant.string()); + break; + + case ValueType::PageAddr: + values.emplace_back(static_cast(constant.pageAddr())); + break; + + default: + assert(false && "This should not be possible to have a constant that isn't a Number, a String or a PageAddr"); + break; + } + } + + m_lowerer.addToTables(symbols, values); + m_lowerer.offsetPagesBy(start_page_at_offset); + return generateBytecode(); + } + bool Welder::saveBytecodeToFile(const std::string& filename) { m_logger.info("Final bytecode size: {}B", m_bytecode.size() * sizeof(uint8_t)); diff --git a/src/arkreactor/State.cpp b/src/arkreactor/State.cpp index 77caaff2b..56d13c268 100644 --- a/src/arkreactor/State.cpp +++ b/src/arkreactor/State.cpp @@ -187,24 +187,15 @@ namespace Ark m_constants = vals.values; m_filenames = files.filenames; m_inst_locations = inst_locs.locations; - - m_max_page_size = 0; - for (const bytecode_t& page : pages) - { - if (page.size() > m_max_page_size) - m_max_page_size = page.size(); - } + m_pages = pages; + m_max_page_size = maxPageSize(m_pages); // Make m_code as a big contiguous chunk of instructions, // aligned on the biggest page size. // This might have a downside when we have a single big page and // a bunch of smaller ones, though I couldn't measure it while testing. m_code.resize(m_max_page_size * pages.size(), Instruction::NOP); - for (std::size_t i = 0, end = pages.size(); i < end; ++i) - { - for (std::size_t j = 0, end_j = pages[i].size(); j < end_j; ++j) - m_code[i * m_max_page_size + j] = pages[i][j]; - } + addPagesToContiguousBytecode(pages, /* start= */ 0); } void State::reset() noexcept @@ -223,6 +214,33 @@ namespace Ark m_bound[std::string(internal::Language::SysProgramName)] = Value(""); } + + void State::addPagesToContiguousBytecode(const std::vector& pages, const std::size_t start) + { + for (std::size_t i = 0, end = pages.size(); i < end; ++i) + { + for (std::size_t j = 0, end_j = pages[i].size(); j < end_j; ++j) + m_code[(start + i) * m_max_page_size + j] = pages[i][j]; + } + } + + std::size_t State::maxPageSize(const std::vector& pages) + { + return std::ranges::max(pages, {}, &bytecode_t::size).size(); + } + + void State::extendBytecode(const std::vector& pages, const std::vector& symbols, const std::vector& constants) + { + m_symbols = symbols; + m_constants = constants; + + // do not modify m_pages so that we can start over + m_max_page_size = std::max(m_max_page_size, maxPageSize(pages)); + + m_code.resize(m_max_page_size * (m_pages.size() + pages.size()), internal::Instruction::NOP); + addPagesToContiguousBytecode(m_pages, /* start= */ 0); + addPagesToContiguousBytecode(pages, /* start= */ m_pages.size()); + } } #ifdef _MSC_VER diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp new file mode 100644 index 000000000..3888711fe --- /dev/null +++ b/src/arkreactor/VM/Debugger.cpp @@ -0,0 +1,110 @@ +#include + +#include +#include + +#include +#include +#include +#include + +namespace Ark::internal +{ + Debugger::Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants) : + m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false) + { + saveState(context); + } + + void Debugger::saveState(const ExecutionContext& context) + { + m_states.emplace_back( + std::make_unique( + context.ip, + context.pp, + context.sp, + context.fc, + context.locals, + context.stacked_closure_scopes)); + } + + void Debugger::resetContextToErrorState(ExecutionContext& context) + { + const auto& [ip, pp, sp, fc, locals, closure_scopes] = *m_states.back(); + context.locals = locals; + context.stacked_closure_scopes = closure_scopes; + context.ip = ip; + context.pp = pp; + context.sp = sp; + context.fc = fc; + + m_states.pop_back(); + } + + void Debugger::run(VM& vm, ExecutionContext& context) + { + m_running = true; + const bool is_vm_running = vm.m_running; + + // TODO: create a shell + fmt::print("> "); + std::string line; + std::getline(std::cin, line); + + // todo: start writing the article, god damn it! + + if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value()) + { + context.ip = 0; + context.pp = vm.m_state.m_pages.size(); + + // create dedicated scope + context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); + + // todo: test state.extendBytecode + vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants); + + if (vm.safeRun(context) == 0) + { + // executing code worked + m_code += line; + + const Value* maybe_value = vm.peekAndResolveAsPtr(context); + if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr) + fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate))); + } + + context.locals.pop_back(); + } + + m_running = false; + // we do not want to retain code from the past executions + m_code.clear(); + // we hit a HALT instruction that set 'running' to false, ignore that if we were still running! + vm.m_running = is_vm_running; + } + + std::optional> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) + { + Welder welder(0, m_libenv, DefaultFeatures); + if (!welder.computeASTFromStringWithKnownSymbols(code, m_symbols)) + return std::nullopt; + // todo: test that we can generate bytecode using small precomputes tables and that the tables, + // even if unused, are present in the resulting bytecode + if (!welder.generateBytecodeUsingTables(m_symbols, m_constants, start_page_at_offset)) + return std::nullopt; + + BytecodeReader bcr; + bcr.feed(welder.bytecode()); + const auto syms = bcr.symbols(); + const auto vals = bcr.values(syms); + const auto files = bcr.filenames(vals); + const auto inst_locs = bcr.instLocations(files); + const auto [pages, _] = bcr.code(inst_locs); + + m_symbols = syms.symbols; + m_constants = vals.values; + + return pages; + } +} diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 569addf42..bb7183b0b 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -918,7 +918,7 @@ namespace Ark args.push_back(*popAndResolveAsPtr(context)); throw types::TypeCheckingError( "append", - { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } }, + { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } }, args); } @@ -1187,9 +1187,16 @@ namespace Ark TARGET(BREAKPOINT) { { - const Value cond = *popAndResolveAsPtr(context); - if (cond == Builtins::trueSym) // todo: trigger debugger - {} + bool breakpoint_active = true; + if (arg == 1) + breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym; + + if (m_state.m_features & FeatureVMDebugger && breakpoint_active) + { + initDebugger(context); + m_debugger->run(*this, context); + m_debugger->resetContextToErrorState(context); + } } DISPATCH(); } @@ -2254,13 +2261,26 @@ namespace Ark fmt::join(arg_names, " "))); } - void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context) + void VM::initDebugger(ExecutionContext& context) + { + if (!m_debugger) + m_debugger = std::make_unique(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants); + else + m_debugger->saveState(context); + } + + void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context) { std::string text = e.what(); if (!text.empty() && text.back() != '\n') text += '\n'; fmt::println("{}", text); + // If code being run from the debugger crashed, ignore it and don't trigger a debugger inside the VM inside the debugger inside the VM + const bool error_from_debugger = m_debugger && m_debugger->isRunning(); + if (m_state.m_features & FeatureVMDebugger && !error_from_debugger) + initDebugger(context); + const std::size_t saved_ip = context.ip; const std::size_t saved_pp = context.pp; const uint16_t saved_sp = context.sp; @@ -2274,9 +2294,10 @@ namespace Ark fmt::styled(saved_pp, fmt::fg(fmt::color::green)), fmt::styled(saved_sp, fmt::fg(fmt::color::yellow))); - if (m_state.m_features & FeatureVMDebugger) + if (m_debugger && !error_from_debugger) { - // TODO: launch debugger + m_debugger->resetContextToErrorState(context); + m_debugger->run(*this, context); } #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION From bacd46afd6127bf8975128e23328c2746ba6b2fa Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Thu, 15 Jan 2026 18:13:14 +0100 Subject: [PATCH 09/16] refactor(macro processor): compute the arg_name of a function macro only after checking the node type --- src/arkreactor/Compiler/Macros/Executors/Function.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/arkreactor/Compiler/Macros/Executors/Function.cpp b/src/arkreactor/Compiler/Macros/Executors/Function.cpp index dce71ab6e..8b511c275 100644 --- a/src/arkreactor/Compiler/Macros/Executors/Function.cpp +++ b/src/arkreactor/Compiler/Macros/Executors/Function.cpp @@ -38,17 +38,15 @@ namespace Ark::internal if (j >= args_needed) break; - // todo: this fails because we don't have a string because we are defining a (macro ! (call ...args) ...) inside another (macro ! (call ...args) ...) - // most likely the first macro got applied to another macro. We shouldn't apply macro on macros - // assert(args.list()[j].nodeType() == NodeType::String || args.list()[j].nodeType() == NodeType::Spread); // todo: temp - const std::string& arg_name = args.list()[j].string(); if (args.list()[j].nodeType() == NodeType::Symbol) { + const std::string& arg_name = args.list()[j].string(); args_applied[arg_name] = node.constList()[i]; ++j; } else if (args.list()[j].nodeType() == NodeType::Spread) { + const std::string& arg_name = args.list()[j].string(); if (!args_applied.contains(arg_name)) { args_applied[arg_name] = Node(NodeType::List); From 453e86f6869395c82eefd124844cb8161b52c9fc Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Thu, 15 Jan 2026 20:02:59 +0100 Subject: [PATCH 10/16] feat(tests): testing the new functionalities developped for the debugger --- include/Ark/State.hpp | 23 ++-- src/arkreactor/VM/Debugger.cpp | 6 +- src/arkreactor/VM/VM.cpp | 2 +- .../unittests/Suites/BytecodeReaderSuite.cpp | 40 +++---- tests/unittests/Suites/CompilerSuite.cpp | 2 +- tests/unittests/Suites/StateSuite.cpp | 108 ++++++++++++++++++ 6 files changed, 145 insertions(+), 36 deletions(-) diff --git a/include/Ark/State.hpp b/include/Ark/State.hpp index 0d4ff9659..bde19c98c 100644 --- a/include/Ark/State.hpp +++ b/include/Ark/State.hpp @@ -119,6 +119,20 @@ namespace Ark */ void reset() noexcept; + /** + * @brief Used by the debugger to add code to the VM at runtime + * + * @param pages + * @param symbols + * @param constants + */ + void extendBytecode(const std::vector& pages, const std::vector& symbols, const std::vector& constants); + + [[nodiscard]] inline const bytecode_t& bytecode() const noexcept + { + return m_bytecode; + } + friend class VM; friend class Repl; friend class internal::Closure; @@ -176,15 +190,6 @@ namespace Ark */ static std::size_t maxPageSize(const std::vector& pages); - /** - * @brief Used by the debugger to add code to the VM at runtime - * - * @param pages - * @param symbols - * @param constants - */ - void extendBytecode(const std::vector& pages, const std::vector& symbols, const std::vector& constants); - /** * @brief Get an instruction in a given page, with a given instruction pointer * diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 3888711fe..423cfdaaa 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -57,11 +57,9 @@ namespace Ark::internal { context.ip = 0; context.pp = vm.m_state.m_pages.size(); - - // create dedicated scope + // create dedicated scope, so that we won't be overwriting existing variables context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); - // todo: test state.extendBytecode vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants); if (vm.safeRun(context) == 0) @@ -89,8 +87,6 @@ namespace Ark::internal Welder welder(0, m_libenv, DefaultFeatures); if (!welder.computeASTFromStringWithKnownSymbols(code, m_symbols)) return std::nullopt; - // todo: test that we can generate bytecode using small precomputes tables and that the tables, - // even if unused, are present in the resulting bytecode if (!welder.generateBytecodeUsingTables(m_symbols, m_constants, start_page_at_offset)) return std::nullopt; diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index bb7183b0b..0baa295d2 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1196,7 +1196,7 @@ namespace Ark initDebugger(context); m_debugger->run(*this, context); m_debugger->resetContextToErrorState(context); - } + } } DISPATCH(); } diff --git a/tests/unittests/Suites/BytecodeReaderSuite.cpp b/tests/unittests/Suites/BytecodeReaderSuite.cpp index c89c16f89..1d4552f9f 100644 --- a/tests/unittests/Suites/BytecodeReaderSuite.cpp +++ b/tests/unittests/Suites/BytecodeReaderSuite.cpp @@ -16,29 +16,29 @@ using namespace Ark::literals; ut::suite<"BytecodeReader"> bcr_suite = [] { using namespace ut; - Ark::Welder welder(0, { lib_path }); - const std::string script_path = getResourcePath("BytecodeReaderSuite/ackermann.ark"); - - const auto time_start = - static_cast(std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count()); - - should("compile without error") = [&] { - expect(mut(welder).computeASTFromFile(script_path)); - expect(mut(welder).generateBytecode()); - }; + "ackermann.ark"_test = [] { + Ark::Welder welder(0, { lib_path }); + const std::string script_path = getResourcePath("BytecodeReaderSuite/ackermann.ark"); + + const auto time_start = + static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); + + should("compile without error") = [&] { + expect(mut(welder).computeASTFromFile(script_path)); + expect(mut(welder).generateBytecode()); + }; - const auto time_end = - static_cast(std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count()); + const auto time_end = + static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); - Ark::BytecodeReader bcr; - const auto bytecode = welder.bytecode(); - bcr.feed(bytecode); + Ark::BytecodeReader bcr; + const auto bytecode = welder.bytecode(); + bcr.feed(bytecode); - "bytecode"_test = [&] { should("find the version") = [bcr] { auto [major, minor, patch] = bcr.version(); expect(that % major == ARK_VERSION_MAJOR); diff --git a/tests/unittests/Suites/CompilerSuite.cpp b/tests/unittests/Suites/CompilerSuite.cpp index e167fc778..6e47f5a7f 100644 --- a/tests/unittests/Suites/CompilerSuite.cpp +++ b/tests/unittests/Suites/CompilerSuite.cpp @@ -12,7 +12,7 @@ using namespace boost; ut::suite<"Compiler"> compiler_suite = [] { using namespace ut; - const std::vector nums = { 0.11, 0.000000000011, 2, -2, 12, 6, 4, 0, 14657892.35, 3.141592653589, 4092.7984 }; + const std::vector nums = { 0.11, 0.000000000011, 2, -2, 12, 6, 4, 0, 14657892.35, 3.141592653589, 4092.7984, -4092.7984 }; "IEEE754 serialization"_test = [&] { using namespace Ark::internal::ieee754; diff --git a/tests/unittests/Suites/StateSuite.cpp b/tests/unittests/Suites/StateSuite.cpp index 39a97fd43..b760ab8a1 100644 --- a/tests/unittests/Suites/StateSuite.cpp +++ b/tests/unittests/Suites/StateSuite.cpp @@ -2,6 +2,7 @@ #include #include +#include using namespace boost; @@ -62,4 +63,111 @@ ut::suite<"State"> state_suite = [] { } }; }; + + "[compile code with known symbols and constants]"_test = [] { + Ark::Welder welder(0, {}, Ark::DefaultFeatures); + const std::vector additional_symbols = { "bar", "egg", "a", "b", "c" }; + const std::vector additional_constants = { Ark::Value("bar"), Ark::Value("egg") }; + + should("compile the string without any error") = [&] { + expect(mut(welder).computeASTFromStringWithKnownSymbols("(let foo bar)", additional_symbols)); + expect(mut(welder).generateBytecodeUsingTables(additional_symbols, additional_constants, 0)); + }; + + const Ark::bytecode_t bytecode = welder.bytecode(); + Ark::State state; + + should("accept the bytecode") = [&] { + expect(nothrow([&] { + mut(state).feed(bytecode, /* fail_with_exception= */ true); + })); + }; + + Ark::BytecodeReader bcr; + bcr.feed(bytecode); + const auto symbols_block = bcr.symbols(); + const auto values_block = bcr.values(symbols_block); + + should("list all symbols") = [symbols_block, additional_symbols] { + using namespace std::literals::string_literals; + + std::vector expected_symbols = additional_symbols; + expected_symbols.emplace_back("foo"); + + const std::size_t symbols_bytes_count = std::accumulate( + expected_symbols.begin(), + expected_symbols.end(), + expected_symbols.size(), + [](const std::size_t acc, const std::string& sym) { + return acc + sym.size(); + }); + + expect(that % symbols_block.symbols == expected_symbols); + // 'ark\0' + version (2 bytes per number) + timestamp + sha -> first byte of the sym table + expect(that % symbols_block.start == 4 + 6 + 8 + 32ull); + // 50 = 4 + 6 + 8 + 32 + // + 1 for the header + // + 2 because we need to count the size of the table (uint16) + // + 3 because we need to count the \0 + expect(that % symbols_block.end == 50 + 1 + 2 + symbols_bytes_count); + }; + + should("list all values") = [symbols_block, values_block, additional_constants] { + expect(that % values_block.values.size() == additional_constants.size()); + expect(that % values_block.start == symbols_block.end); + expect(values_block.values[0] == additional_constants[0]); + expect(values_block.values[1] == additional_constants[1]); + }; + }; + + "[extend bytecode]"_test = [] { + Ark::Welder welder(0, {}, Ark::DefaultFeatures); + should("compile the string without any error") = [&] { + expect(mut(welder).computeASTFromString("(let foo 5) (let bar (fun (a b c) (+ a b c)))")); + expect(mut(welder).generateBytecode()); + }; + + const Ark::bytecode_t bytecode = welder.bytecode(); + Ark::State state; + should("accept the bytecode") = [&] { + expect(nothrow([&] { + mut(state).feed(bytecode, /* fail_with_exception= */ true); + })); + }; + + Ark::BytecodeReader bcr; + bcr.feed(bytecode); + const auto symbols_block = bcr.symbols(); + const auto values_block = bcr.values(symbols_block); + + { + Ark::Welder welder2(0, {}, Ark::DefaultFeatures); + should("compute additional code") = [&] { + expect(mut(welder2).computeASTFromStringWithKnownSymbols("(let x foo) (let y 10) (print (bar foo foo foo))", symbols_block.symbols)); + }; + + should("generate bytecode using existing bytecode tables") = [&] { + // we have two existing pages in the original code + expect(mut(welder2).generateBytecodeUsingTables(symbols_block.symbols, values_block.values, 2)); + }; + + Ark::BytecodeReader bcr2; + bcr2.feed(welder2.bytecode()); + const auto syms = bcr2.symbols(); + const auto vals = bcr2.values(syms); + const auto files = bcr2.filenames(vals); + const auto inst_locs = bcr2.instLocations(files); + const auto [pages, _] = bcr2.code(inst_locs); + + should("have compiled a single additional page") = [&] { + expect(that % pages.size() == 1u); + }; + + should("extend bytecode without errors") = [&] { + expect(nothrow([&] { + mut(state).extendBytecode(pages, syms.symbols, vals.values); + })); + }; + } + }; }; From e1fd022366df307038091f5f1af7553092c72558 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sun, 18 Jan 2026 16:35:48 +0100 Subject: [PATCH 11/16] feat(breakpoints): allow 'breakpoint' to take 0 or 1 argument (boolean condition to trigger the breakpoint) --- include/Ark/VM/Debugger.hpp | 2 +- src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp | 9 +++++++-- src/arkreactor/VM/Debugger.cpp | 4 +--- src/arkreactor/VM/VM.cpp | 15 ++++++++------- .../CompilerSuite/ir/breakpoints.expected | 18 +++++++++--------- .../compileTime/breakpoint_too_many_args.ark | 1 + .../breakpoint_too_many_args.expected | 5 +++++ .../unittests/resources/LangSuite/vm-tests.ark | 5 +++++ 8 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.expected diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index ec0a83a6e..ff80450c7 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -62,7 +62,7 @@ namespace Ark::internal * * @param context context to reset */ - void resetContextToErrorState(ExecutionContext& context); + void resetContextToSavedState(ExecutionContext& context); /** * @brief Start the debugger shell diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 8f687ae4c..f2403d702 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -117,7 +117,6 @@ namespace Ark::internal { switch (inst) { - case BREAKPOINT: [[fallthrough]]; case NOT: [[fallthrough]]; case LEN: [[fallthrough]]; case IS_EMPTY: [[fallthrough]]; @@ -729,7 +728,13 @@ namespace Ark::internal page(p).emplace_back(op); } - if (isUnaryInst(op)) + if (isBreakpoint(x)) + { + if (exp_count > 1) + buildAndThrowError(fmt::format("`{}' expected at most one argument, but was called with {}", op_name, exp_count), x.constList()[0]); + page(p).emplace_back(op, exp_count); + } + else if (isUnaryInst(op)) { if (exp_count != 1) buildAndThrowError(fmt::format("`{}' expected one argument, but was called with {}", op_name, exp_count), x.constList()[0]); diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 423cfdaaa..13470b659 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -28,7 +28,7 @@ namespace Ark::internal context.stacked_closure_scopes)); } - void Debugger::resetContextToErrorState(ExecutionContext& context) + void Debugger::resetContextToSavedState(ExecutionContext& context) { const auto& [ip, pp, sp, fc, locals, closure_scopes] = *m_states.back(); context.locals = locals; @@ -51,8 +51,6 @@ namespace Ark::internal std::string line; std::getline(std::cin, line); - // todo: start writing the article, god damn it! - if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value()) { context.ip = 0; diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 0baa295d2..b89865a89 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -194,11 +194,12 @@ namespace Ark std::to_string(closure->valueType()), m_state.m_symbols[id])); else - throwVMError(ErrorKind::Type, - fmt::format( - "{} is not a Closure, can not get the field `{}' from it", - std::to_string(closure->valueType()), - m_state.m_symbols[id])); + throwVMError( + ErrorKind::Type, + fmt::format( + "{} is not a Closure, can not get the field `{}' from it", + std::to_string(closure->valueType()), + m_state.m_symbols[id])); } if (Value* field = closure->refClosure().refScope()[id]; field != nullptr) @@ -1195,7 +1196,7 @@ namespace Ark { initDebugger(context); m_debugger->run(*this, context); - m_debugger->resetContextToErrorState(context); + m_debugger->resetContextToSavedState(context); } } DISPATCH(); @@ -2296,7 +2297,7 @@ namespace Ark if (m_debugger && !error_from_debugger) { - m_debugger->resetContextToErrorState(context); + m_debugger->resetContextToSavedState(context); m_debugger->run(*this, context); } diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected index d4b95686c..135bdc001 100644 --- a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected @@ -1,6 +1,6 @@ page_0 BUILTIN 1 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L3 @@ -12,7 +12,7 @@ page_0 .L3: POP 0 BUILTIN 1 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_CONST 5 LOAD_CONST 6 GT 0 @@ -29,7 +29,7 @@ page_0 LOAD_CONST 4 LOAD_CONST 5 BUILTIN 0 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_CONST 6 LOAD_FAST_BY_INDEX 0 CALL 3 @@ -46,7 +46,7 @@ page_0 STORE 5 PUSH_RETURN_ADDRESS L9 BUILTIN 1 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_FAST_BY_INDEX 0 CALL 0 .L9: @@ -59,7 +59,7 @@ page_1 LOAD_FAST_BY_INDEX 2 LOAD_CONST 1 GT 0 - BREAKPOINT 0 + BREAKPOINT 1 PUSH_RETURN_ADDRESS L0 PUSH_RETURN_ADDRESS L1 LOAD_FAST_BY_INDEX 0 @@ -79,7 +79,7 @@ page_1 LOAD_FAST_BY_INDEX 0 LOAD_CONST 3 LT 0 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_FAST_BY_INDEX 0 MUL 0 LOAD_CONST 1 @@ -87,10 +87,10 @@ page_1 STORE 4 PUSH_RETURN_ADDRESS L2 BUILTIN 0 - BREAKPOINT 0 + BREAKPOINT 1 LOAD_FAST_BY_INDEX 0 BUILTIN 1 - BREAKPOINT 0 + BREAKPOINT 1 BUILTIN 9 CALL 1 .L2: @@ -102,4 +102,4 @@ page_1 page_2 BUILTIN 2 RET 0 - HALT 0 \ No newline at end of file + HALT 0 diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.ark b/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.ark new file mode 100644 index 000000000..6344b7e3d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.ark @@ -0,0 +1 @@ +(breakpoint 1 2) diff --git a/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.expected b/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.expected new file mode 100644 index 000000000..6789dc201 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.expected @@ -0,0 +1,5 @@ +In file tests/unittests/resources/DiagnosticsSuite/compileTime/breakpoint_too_many_args.ark:1 + 1 | (breakpoint 1 2) + | ^~~~~~~~~~ + 2 | + `breakpoint' expected at most one argument, but was called with 2 diff --git a/tests/unittests/resources/LangSuite/vm-tests.ark b/tests/unittests/resources/LangSuite/vm-tests.ark index 83882ee18..e67de926e 100644 --- a/tests/unittests/resources/LangSuite/vm-tests.ark +++ b/tests/unittests/resources/LangSuite/vm-tests.ark @@ -202,6 +202,11 @@ (while (> i 5) (set i (- i 1))) (test:eq i 5) }) + (test:case "optimized mul set val" { + (mut val 5) + ((fun () (set val (* val 6)))) + (test:eq val 30) }) + (test:case "set/store head and tail" { (mut data [1 2 3 4]) # STORE_LEN From 31f78d00a9466e30aab14933e05b98cd1192bcaf Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sun, 18 Jan 2026 17:11:33 +0100 Subject: [PATCH 12/16] feat(debugger): create a minimalistic shell for the debugger --- include/Ark/Utils/Utils.hpp | 15 ++++ include/Ark/VM/Debugger.hpp | 11 ++- include/CLI/REPL/Utils.hpp | 15 ---- src/arkreactor/Utils/Utils.cpp | 17 ++++ src/arkreactor/VM/Debugger.cpp | 119 +++++++++++++++++++++----- src/arkreactor/VM/VM.cpp | 3 + src/arkscript/REPL/Repl.cpp | 7 +- src/arkscript/REPL/Utils.cpp | 15 ---- tests/unittests/Suites/ReplSuite.cpp | 29 ------- tests/unittests/Suites/ToolsSuite.cpp | 29 +++++++ 10 files changed, 177 insertions(+), 83 deletions(-) diff --git a/include/Ark/Utils/Utils.hpp b/include/Ark/Utils/Utils.hpp index 2ff4edcd1..08edc0eed 100644 --- a/include/Ark/Utils/Utils.hpp +++ b/include/Ark/Utils/Utils.hpp @@ -99,6 +99,21 @@ namespace Ark::Utils * @return std::size_t */ ARK_API std::size_t levenshteinDistance(const std::string& str1, const std::string& str2); + + /** + * @brief Count the open enclosure and its counterpart: (), {}, [] + * @param line data to operate on + * @param open the open char: (, { or [ + * @param close the closing char: ), } or ] + * @return positive if there are more open enclosures than closed. 0 when both are equal, negative otherwise + */ + ARK_API long countOpenEnclosures(const std::string& line, char open, char close); + + /** + * @brief Remove whitespaces at the start and end of a string + * @param line string modified in place + */ + ARK_API void trimWhitespace(std::string& line); } #endif diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index ff80450c7..52db8a842 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -72,19 +72,28 @@ namespace Ark::internal */ void run(VM& vm, ExecutionContext& context); - inline bool isRunning() const noexcept + [[nodiscard]] inline bool isRunning() const noexcept { return m_running; } + [[nodiscard]] inline bool shouldQuitVM() const noexcept + { + return m_quit_vm; + } + private: std::vector> m_states; std::vector m_libenv; std::vector m_symbols; std::vector m_constants; bool m_running; + bool m_quit_vm; std::string m_code; ///< Code added while inside the debugger + std::size_t m_line_count = 0; + + std::optional prompt(); /** * @brief Take care of compiling new code using the existing data tables diff --git a/include/CLI/REPL/Utils.hpp b/include/CLI/REPL/Utils.hpp index d9f14621b..7e61b47b1 100644 --- a/include/CLI/REPL/Utils.hpp +++ b/include/CLI/REPL/Utils.hpp @@ -18,21 +18,6 @@ namespace Ark::internal { - /** - * @brief Count the open enclosure and its counterpart: (), {}, [] - * @param line data to operate on - * @param open the open char: (, { or [ - * @param close the closing char: ), } or ] - * @return positive if there are more open enclosures than closed. 0 when both are equal, negative otherwise - */ - long countOpenEnclosures(const std::string& line, char open, char close); - - /** - * @brief Remove whitespaces at the start and end of a string - * @param line string modified in place - */ - void trimWhitespace(std::string& line); - /** * @brief Compute a list of all the language keywords and builtins * diff --git a/src/arkreactor/Utils/Utils.cpp b/src/arkreactor/Utils/Utils.cpp index 061e29b00..5a255c086 100644 --- a/src/arkreactor/Utils/Utils.cpp +++ b/src/arkreactor/Utils/Utils.cpp @@ -1,5 +1,7 @@ #include +#include + namespace Ark::Utils { std::size_t levenshteinDistance(const std::string& str1, const std::string& str2) @@ -29,4 +31,19 @@ namespace Ark::Utils return edit_distances[str1_len][str2_len]; } + + long countOpenEnclosures(const std::string& line, const char open, const char close) + { + return std::ranges::count(line, open) - std::ranges::count(line, close); + } + + void trimWhitespace(std::string& line) + { + const std::size_t string_begin = line.find_first_not_of(" \t"); + if (std::string::npos != string_begin) + { + const std::size_t string_end = line.find_last_not_of(" \t"); + line = line.substr(string_begin, string_end - string_begin + 1); + } + } } diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 13470b659..14c6dafb0 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -5,13 +5,15 @@ #include #include +#include #include #include +#include namespace Ark::internal { Debugger::Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants) : - m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false) + m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false), m_quit_vm(false) { saveState(context); } @@ -46,31 +48,59 @@ namespace Ark::internal m_running = true; const bool is_vm_running = vm.m_running; - // TODO: create a shell - fmt::print("> "); - std::string line; - std::getline(std::cin, line); - - if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value()) + // show the line where the breakpoint hit + const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp); + if (maybe_source_loc) { - context.ip = 0; - context.pp = vm.m_state.m_pages.size(); - // create dedicated scope, so that we won't be overwriting existing variables - context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); + const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id]; + + if (Utils::fileExists(filename)) + { + fmt::println(""); + Diagnostics::makeContext( + Diagnostics::ErrorLocation { + .filename = filename, + .start = FilePos { .line = maybe_source_loc->line, .column = 0 }, + .end = std::nullopt }, + std::cout, + /* maybe_context= */ std::nullopt, + /* colorize= */ true); + fmt::println(""); + } + } - vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants); + while (true) + { + std::optional maybe_input = prompt(); - if (vm.safeRun(context) == 0) + if (maybe_input) { - // executing code worked - m_code += line; + const std::string& line = maybe_input.value(); - const Value* maybe_value = vm.peekAndResolveAsPtr(context); - if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr) - fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate))); - } + if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value()) + { + context.ip = 0; + context.pp = vm.m_state.m_pages.size(); + // create dedicated scope, so that we won't be overwriting existing variables + context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); + + vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants); - context.locals.pop_back(); + if (vm.safeRun(context) == 0) + { + // executing code worked + m_code += line; + + const Value* maybe_value = vm.peekAndResolveAsPtr(context); + if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr) + fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate))); + } + + context.locals.pop_back(); + } + } + else + break; } m_running = false; @@ -80,6 +110,55 @@ namespace Ark::internal vm.m_running = is_vm_running; } + std::optional Debugger::prompt() + { + std::string code; + long open_parens = 0; + long open_braces = 0; + + while (true) + { + const bool unfinished_block = open_parens != 0 || open_braces != 0; + fmt::print("dbg:{:0>3}{} ", m_line_count, unfinished_block ? ":" : ">"); + std::string line; + std::getline(std::cin, line); + + Utils::trimWhitespace(line); + + if (line == "c" || line == "continue" || line.empty()) + { + fmt::println("dbg: continue"); + return std::nullopt; + } + else if (line == "q" || line == "quit") + { + fmt::println("dbg: stop"); + m_quit_vm = true; + return std::nullopt; + } + else if (line == "help") + { + fmt::println("Available commands:"); + fmt::println(" help -- display this message"); + fmt::println(" c, continue -- resume execution"); + fmt::println(" q, quit -- quit the debugger, stopping the script execution"); + } + else + { + code += line; + + open_parens += Utils::countOpenEnclosures(line, '(', ')'); + open_braces += Utils::countOpenEnclosures(line, '{', '}'); + + ++m_line_count; + if (open_braces == 0 && open_parens == 0) + break; + } + } + + return code; + } + std::optional> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) { Welder welder(0, m_libenv, DefaultFeatures); diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index b89865a89..9a7b41d16 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1197,6 +1197,9 @@ namespace Ark initDebugger(context); m_debugger->run(*this, context); m_debugger->resetContextToSavedState(context); + + if (m_debugger->shouldQuitVM()) + GOTO_HALT(); } } DISPATCH(); diff --git a/src/arkscript/REPL/Repl.cpp b/src/arkscript/REPL/Repl.cpp index 1c62bb4ef..ec73550ae 100644 --- a/src/arkscript/REPL/Repl.cpp +++ b/src/arkscript/REPL/Repl.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -166,7 +167,7 @@ namespace Ark // line history m_repl.history_add(line); - trimWhitespace(line); + Utils::trimWhitespace(line); // specific commands handling if (line == "quit" || buf == nullptr) @@ -233,8 +234,8 @@ namespace Ark if (maybe_line.has_value() && !maybe_line.value().empty()) { code_block += maybe_line.value() + "\n"; - open_parentheses += countOpenEnclosures(maybe_line.value(), '(', ')'); - open_braces += countOpenEnclosures(maybe_line.value(), '{', '}'); + open_parentheses += Utils::countOpenEnclosures(maybe_line.value(), '(', ')'); + open_braces += Utils::countOpenEnclosures(maybe_line.value(), '{', '}'); // lines number incrementation ++m_line_count; diff --git a/src/arkscript/REPL/Utils.cpp b/src/arkscript/REPL/Utils.cpp index ece3040ae..bbe62d29b 100644 --- a/src/arkscript/REPL/Utils.cpp +++ b/src/arkscript/REPL/Utils.cpp @@ -10,21 +10,6 @@ namespace Ark::internal { - long countOpenEnclosures(const std::string& line, const char open, const char close) - { - return std::ranges::count(line, open) - std::ranges::count(line, close); - } - - void trimWhitespace(std::string& line) - { - const std::size_t string_begin = line.find_first_not_of(" \t"); - if (std::string::npos != string_begin) - { - const std::size_t string_end = line.find_last_not_of(" \t"); - line = line.substr(string_begin, string_end - string_begin + 1); - } - } - std::vector getAllKeywords() { std::vector output; diff --git a/tests/unittests/Suites/ReplSuite.cpp b/tests/unittests/Suites/ReplSuite.cpp index 237b8b97e..119460df8 100644 --- a/tests/unittests/Suites/ReplSuite.cpp +++ b/tests/unittests/Suites/ReplSuite.cpp @@ -9,35 +9,6 @@ using namespace boost; ut::suite<"Repl"> repl_suite = [] { using namespace ut; - "countOpenEnclosures"_test = [] { - expect(that % Ark::internal::countOpenEnclosures("", '(', ')') == 0); - expect(that % Ark::internal::countOpenEnclosures("(", '(', ')') == 1); - expect(that % Ark::internal::countOpenEnclosures(")", '(', ')') == -1); - expect(that % Ark::internal::countOpenEnclosures("{}", '(', ')') == 0); - expect(that % Ark::internal::countOpenEnclosures("{)(()}", '(', ')') == 0); - expect(that % Ark::internal::countOpenEnclosures("{)(()}", '{', '}') == 0); - }; - - "trimWhitespace"_test = [] { - const std::string expected = "hello world"; - std::string line = expected; - - Ark::internal::trimWhitespace(line); - expect(that % line == expected); - - line = " \thello world"; - Ark::internal::trimWhitespace(line); - expect(that % line == expected); - - line = "hello world \t"; - Ark::internal::trimWhitespace(line); - expect(that % line == expected); - - line = " \thello world \t"; - Ark::internal::trimWhitespace(line); - expect(that % line == expected); - }; - const auto kws = Ark::internal::getAllKeywords(); const auto colors = Ark::internal::getColorPerKeyword(); const auto append_color = std::ranges::find_if(colors, [](const auto& pair) { diff --git a/tests/unittests/Suites/ToolsSuite.cpp b/tests/unittests/Suites/ToolsSuite.cpp index fe64ed907..ddf79458e 100644 --- a/tests/unittests/Suites/ToolsSuite.cpp +++ b/tests/unittests/Suites/ToolsSuite.cpp @@ -39,6 +39,35 @@ ut::suite<"Tools"> tools_suite = [] { expect(that % Ark::Utils::levenshteinDistance("arkscript", "OrC") == 8_z); }; + "countOpenEnclosures"_test = [] { + expect(that % Ark::Utils::countOpenEnclosures("", '(', ')') == 0); + expect(that % Ark::Utils::countOpenEnclosures("(", '(', ')') == 1); + expect(that % Ark::Utils::countOpenEnclosures(")", '(', ')') == -1); + expect(that % Ark::Utils::countOpenEnclosures("{}", '(', ')') == 0); + expect(that % Ark::Utils::countOpenEnclosures("{)(()}", '(', ')') == 0); + expect(that % Ark::Utils::countOpenEnclosures("{)(()}", '{', '}') == 0); + }; + + "trimWhitespace"_test = [] { + const std::string expected = "hello world"; + std::string line = expected; + + Ark::Utils::trimWhitespace(line); + expect(that % line == expected); + + line = " \thello world"; + Ark::Utils::trimWhitespace(line); + expect(that % line == expected); + + line = "hello world \t"; + Ark::Utils::trimWhitespace(line); + expect(that % line == expected); + + line = " \thello world \t"; + Ark::Utils::trimWhitespace(line); + expect(that % line == expected); + }; + "Utils::fileExists"_test = [] { expect(Ark::Utils::fileExists(".gitignore")); expect(!Ark::Utils::fileExists("")); From b204126c3f89ce3318d0e67b7dfc28d4dba9a59e Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 19 Jan 2026 17:36:48 +0100 Subject: [PATCH 13/16] feat(debugger): adding a way to use a prompt file for the debugger, instead of requiring the user to provide an input --- include/Ark/Constants.hpp.in | 4 +- include/Ark/VM/Debugger.hpp | 37 ++++- include/Ark/VM/VM.hpp | 8 ++ src/arkreactor/Compiler/Welder.cpp | 2 +- src/arkreactor/VM/Debugger.cpp | 128 +++++++++++------- src/arkreactor/VM/VM.cpp | 5 + tests/unittests/Suites/DebuggerSuite.cpp | 52 +++++++ tests/unittests/TestsHelper.cpp | 11 +- tests/unittests/TestsHelper.hpp | 2 + .../resources/DebuggerSuite/basic.ark | 12 ++ .../resources/DebuggerSuite/basic.expected | 38 ++++++ .../resources/DebuggerSuite/basic.prompt | 6 + .../resources/DebuggerSuite/debugger_quit.ark | 12 ++ .../DebuggerSuite/debugger_quit.expected | 14 ++ .../DebuggerSuite/debugger_quit.prompt | 3 + .../resources/DebuggerSuite/loop.ark | 6 + .../resources/DebuggerSuite/loop.expected | 67 +++++++++ .../resources/DebuggerSuite/loop.prompt | 8 ++ .../DebuggerSuite/modify_program_state.ark | 6 + .../modify_program_state.expected | 34 +++++ .../DebuggerSuite/modify_program_state.prompt | 4 + 21 files changed, 399 insertions(+), 60 deletions(-) create mode 100644 tests/unittests/Suites/DebuggerSuite.cpp create mode 100644 tests/unittests/resources/DebuggerSuite/basic.ark create mode 100644 tests/unittests/resources/DebuggerSuite/basic.expected create mode 100644 tests/unittests/resources/DebuggerSuite/basic.prompt create mode 100644 tests/unittests/resources/DebuggerSuite/debugger_quit.ark create mode 100644 tests/unittests/resources/DebuggerSuite/debugger_quit.expected create mode 100644 tests/unittests/resources/DebuggerSuite/debugger_quit.prompt create mode 100644 tests/unittests/resources/DebuggerSuite/loop.ark create mode 100644 tests/unittests/resources/DebuggerSuite/loop.expected create mode 100644 tests/unittests/resources/DebuggerSuite/loop.prompt create mode 100644 tests/unittests/resources/DebuggerSuite/modify_program_state.ark create mode 100644 tests/unittests/resources/DebuggerSuite/modify_program_state.expected create mode 100644 tests/unittests/resources/DebuggerSuite/modify_program_state.prompt diff --git a/include/Ark/Constants.hpp.in b/include/Ark/Constants.hpp.in index 4b7a626b5..475a94ae2 100644 --- a/include/Ark/Constants.hpp.in +++ b/include/Ark/Constants.hpp.in @@ -53,10 +53,10 @@ namespace Ark // Compiler options constexpr uint16_t FeatureImportSolver = 1 << 0; constexpr uint16_t FeatureMacroProcessor = 1 << 1; - constexpr uint16_t FeatureASTOptimizer = 1 << 2; ///< This is disabled so that embedding ArkScript does not prune nodes from the AST ; it is active in the `arkscript` executable + constexpr uint16_t FeatureASTOptimizer = 1 << 2; ///< Disabled by default because embedding ArkScript should not prune nodes from the AST ; it is active in the `arkscript` executable constexpr uint16_t FeatureIROptimizer = 1 << 3; constexpr uint16_t FeatureNameResolver = 1 << 4; - constexpr uint16_t FeatureVMDebugger = 1 << 5; ///< This is disabled so that embedding ArkScript does not launch the debugger on every error when running code + constexpr uint16_t FeatureVMDebugger = 1 << 5; ///< Disabled by default because embedding ArkScript should not launch the debugger on every error when running code constexpr uint16_t FeatureDumpIR = 1 << 14; /// This feature should only be used in tests, to disable diagnostics generation and enable exceptions to be thrown diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index 52db8a842..d29eb886a 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -37,6 +37,13 @@ namespace Ark::internal std::vector> closure_scopes; }; + struct CompiledPrompt + { + std::vector pages; + std::vector symbols; + std::vector constants; + }; + class Debugger { public: @@ -48,7 +55,18 @@ namespace Ark::internal * @param symbols symbols table of the VM * @param constants constants table of the VM */ - explicit Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants); + Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants); + + /** + * @brief Create a new Debugger object that will use lines from a file as prompts, instead of waiting for user inputs + * + * @param libenv + * @param path_to_prompt_file + * @param os output stream + * @param symbols symbols table of the VM + * @param constants constants table of the VM + */ + Debugger(const std::vector& libenv, const std::string& path_to_prompt_file, std::ostream& os, const std::vector& symbols, const std::vector& constants); /** * @brief Save the current VM state, to get back to it once the debugger is done running @@ -87,22 +105,27 @@ namespace Ark::internal std::vector m_libenv; std::vector m_symbols; std::vector m_constants; - bool m_running; - bool m_quit_vm; + bool m_running { false }; + bool m_quit_vm { false }; + std::ostream& m_os; + bool m_colorize; + std::unique_ptr m_prompt_stream; std::string m_code; ///< Code added while inside the debugger - std::size_t m_line_count = 0; + std::size_t m_line_count { 0 }; + + void showContext(const VM& vm, const ExecutionContext& context) const; - std::optional prompt(); + std::optional prompt(std::size_t ip, std::size_t pp); /** * @brief Take care of compiling new code using the existing data tables * * @param code * @param start_page_at_offset offset to start the new pages at - * @return std::optional> optional set of bytecode pages if compilation succeeded + * @return std::optional optional set of bytecode pages, symbols and constants if compilation succeeded */ - std::optional> compile(const std::string& code, std::size_t start_page_at_offset); + [[nodiscard]] std::optional compile(const std::string& code, std::size_t start_page_at_offset) const; }; } diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index 91cd53332..b779cb916 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -151,6 +151,14 @@ namespace Ark */ [[nodiscard]] bool forceReloadPlugins() const; + /** + * @brief Configure the debugger to use a prompt file instead of asking the user for an input + * + * @param path path to prompt file (one prompt per line) + * @param os output stream + */ + void usePromptFileForDebugger(const std::string& path, std::ostream& os = std::cout); + /** * @brief Throw a VM error message * diff --git a/src/arkreactor/Compiler/Welder.cpp b/src/arkreactor/Compiler/Welder.cpp index 58f5f183d..e595efcb4 100644 --- a/src/arkreactor/Compiler/Welder.cpp +++ b/src/arkreactor/Compiler/Welder.cpp @@ -55,7 +55,7 @@ namespace Ark m_root_file = std::filesystem::current_path(); // No filename given, take the current working directory for (const std::string& sym : symbols) - m_name_resolver.addDefinedSymbol(sym, /* is_mutable= */ false); + m_name_resolver.addDefinedSymbol(sym, /* is_mutable= */ true); return computeAST(ARK_NO_NAME_FILE, code); } diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 14c6dafb0..985804b52 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -13,11 +14,17 @@ namespace Ark::internal { Debugger::Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants) : - m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false), m_quit_vm(false) + m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(std::cout), m_colorize(true) { saveState(context); } + Debugger::Debugger(const std::vector& libenv, const std::string& path_to_prompt_file, std::ostream& os, const std::vector& symbols, const std::vector& constants) : + m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(os), m_colorize(false) + { + m_prompt_stream = std::make_unique(path_to_prompt_file); + } + void Debugger::saveState(const ExecutionContext& context) { m_states.emplace_back( @@ -45,72 +52,89 @@ namespace Ark::internal void Debugger::run(VM& vm, ExecutionContext& context) { + showContext(vm, context); + m_running = true; const bool is_vm_running = vm.m_running; - - // show the line where the breakpoint hit - const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp); - if (maybe_source_loc) - { - const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id]; - - if (Utils::fileExists(filename)) - { - fmt::println(""); - Diagnostics::makeContext( - Diagnostics::ErrorLocation { - .filename = filename, - .start = FilePos { .line = maybe_source_loc->line, .column = 0 }, - .end = std::nullopt }, - std::cout, - /* maybe_context= */ std::nullopt, - /* colorize= */ true); - fmt::println(""); - } - } + const std::size_t ip_at_breakpoint = context.ip, + pp_at_breakpoint = context.pp; + // create dedicated scope, so that we won't be overwriting existing variables + context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); + std::size_t last_ip = 0; while (true) { - std::optional maybe_input = prompt(); + std::optional maybe_input = prompt(ip_at_breakpoint, pp_at_breakpoint); if (maybe_input) { const std::string& line = maybe_input.value(); - if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value()) + if (const auto compiled = compile(m_code + line, vm.m_state.m_pages.size()); compiled.has_value()) { - context.ip = 0; + context.ip = last_ip; context.pp = vm.m_state.m_pages.size(); - // create dedicated scope, so that we won't be overwriting existing variables - context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); - vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants); + vm.m_state.extendBytecode(compiled->pages, compiled->symbols, compiled->constants); if (vm.safeRun(context) == 0) { // executing code worked - m_code += line; + m_code += line + "\n"; + // place ip to end of bytecode instruction (HALT) + last_ip = context.ip - 4; const Value* maybe_value = vm.peekAndResolveAsPtr(context); if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr) - fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate))); + fmt::println( + m_os, + "{}", + fmt::styled( + maybe_value->toString(vm), + m_colorize ? fmt::fg(fmt::color::chocolate) : fmt::text_style())); } - - context.locals.pop_back(); } } else break; } - m_running = false; + context.locals.pop_back(); + // we do not want to retain code from the past executions m_code.clear(); + m_line_count = 0; + // we hit a HALT instruction that set 'running' to false, ignore that if we were still running! vm.m_running = is_vm_running; + m_running = false; } - std::optional Debugger::prompt() + void Debugger::showContext(const VM& vm, const ExecutionContext& context) const + { + // show the line where the breakpoint hit + const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp); + if (maybe_source_loc) + { + const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id]; + + if (Utils::fileExists(filename)) + { + fmt::println(m_os, ""); + Diagnostics::makeContext( + Diagnostics::ErrorLocation { + .filename = filename, + .start = FilePos { .line = maybe_source_loc->line, .column = 0 }, + .end = std::nullopt }, + m_os, + /* maybe_context= */ std::nullopt, + /* colorize= */ m_colorize); + fmt::println(m_os, ""); + } + } + } + + std::optional Debugger::prompt(const std::size_t ip, const std::size_t pp) { std::string code; long open_parens = 0; @@ -119,29 +143,42 @@ namespace Ark::internal while (true) { const bool unfinished_block = open_parens != 0 || open_braces != 0; - fmt::print("dbg:{:0>3}{} ", m_line_count, unfinished_block ? ":" : ">"); + fmt::print( + m_os, + "dbg[{},{}]:{:0>3}{} ", + fmt::format("pp:{}", fmt::styled(pp, m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style())), + fmt::format("ip:{}", fmt::styled(ip, m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())), + m_line_count, + unfinished_block ? ":" : ">"); + std::string line; - std::getline(std::cin, line); + if (m_prompt_stream) + { + std::getline(*m_prompt_stream, line); + fmt::println(m_os, "{}", line); // because nothing is printed otherwise, and prompts get printed on the same line + } + else + std::getline(std::cin, line); Utils::trimWhitespace(line); if (line == "c" || line == "continue" || line.empty()) { - fmt::println("dbg: continue"); + fmt::println(m_os, "dbg: continue"); return std::nullopt; } else if (line == "q" || line == "quit") { - fmt::println("dbg: stop"); + fmt::println(m_os, "dbg: stop"); m_quit_vm = true; return std::nullopt; } else if (line == "help") { - fmt::println("Available commands:"); - fmt::println(" help -- display this message"); - fmt::println(" c, continue -- resume execution"); - fmt::println(" q, quit -- quit the debugger, stopping the script execution"); + fmt::println(m_os, "Available commands:"); + fmt::println(m_os, " help -- display this message"); + fmt::println(m_os, " c, continue -- resume execution"); + fmt::println(m_os, " q, quit -- quit the debugger, stopping the script execution"); } else { @@ -159,7 +196,7 @@ namespace Ark::internal return code; } - std::optional> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) + std::optional Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) const { Welder welder(0, m_libenv, DefaultFeatures); if (!welder.computeASTFromStringWithKnownSymbols(code, m_symbols)) @@ -175,9 +212,6 @@ namespace Ark::internal const auto inst_locs = bcr.instLocations(files); const auto [pages, _] = bcr.code(inst_locs); - m_symbols = syms.symbols; - m_constants = vals.values; - - return pages; + return std::optional(CompiledPrompt(pages, syms.symbols, vals.values)); } } diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 9a7b41d16..cf6b0bda3 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -510,6 +510,11 @@ namespace Ark } } + void VM::usePromptFileForDebugger(const std::string& path, std::ostream& os) + { + m_debugger = std::make_unique(m_state.m_libenv, path, os, m_state.m_symbols, m_state.m_constants); + } + void VM::throwVMError(ErrorKind kind, const std::string& message) { throw std::runtime_error(std::string(errorKinds[static_cast(kind)]) + ": " + message + "\n"); diff --git a/tests/unittests/Suites/DebuggerSuite.cpp b/tests/unittests/Suites/DebuggerSuite.cpp new file mode 100644 index 000000000..551c1eaff --- /dev/null +++ b/tests/unittests/Suites/DebuggerSuite.cpp @@ -0,0 +1,52 @@ +#include + +#include + +#include +#include + +using namespace boost; + +ut::suite<"Debugger"> debugger_suite = [] { + using namespace ut; + + constexpr uint16_t features = Ark::DefaultFeatures | Ark::FeatureVMDebugger; + + iterTestFiles( + "DebuggerSuite", + [](TestData&& data) { + std::stringstream os; + Ark::State state({ lib_path }); + + // cppcheck-suppress constParameterReference + state.loadFunction("prn", [&os](std::vector& args, Ark::VM* vm) -> Ark::Value { + for (const auto& value : args) + fmt::print(os, "{}", value.toString(*vm)); + fmt::println(os, ""); + return Ark::Nil; + }); + + should("compile without error for " + data.stem) = [&] { + expect(mut(state).doFile(data.path, features)); + }; + + should("launch the debugger and compute expressions for " + data.stem) = [&] { + std::filesystem::path prompt_path(data.path); + prompt_path.replace_extension("prompt"); + + try + { + Ark::VM vm(state); + vm.usePromptFileForDebugger(prompt_path.generic_string(), os); + vm.run(/* fail_with_exception= */ true); + + const std::string output = sanitizeOutput(os.str()); + expectOrDiff(data.expected, output); + } + catch (const std::exception&) + { + expect(false); + } + }; + }); +}; diff --git a/tests/unittests/TestsHelper.cpp b/tests/unittests/TestsHelper.cpp index c33c8ebc3..972c9b367 100644 --- a/tests/unittests/TestsHelper.cpp +++ b/tests/unittests/TestsHelper.cpp @@ -82,10 +82,9 @@ std::string sanitizeCodeError(const Ark::CodeError& e) return diag; } -std::string sanitizeRuntimeError(const std::exception& e) +std::string sanitizeOutput(const std::string& output) { - // std::replace(s.begin(), s.end(), '\\', '/'); - std::string diag = e.what(); + std::string diag = output; // because of windows diag.erase(std::ranges::remove(diag, '\r').begin(), diag.end()); @@ -101,6 +100,12 @@ std::string sanitizeRuntimeError(const std::exception& e) return diag; } +std::string sanitizeRuntimeError(const std::exception& e) +{ + const std::string diag = e.what(); + return sanitizeOutput(diag); +} + void expectOrDiff(const std::string& expected, const std::string& received) { const bool comparison = expected == received; diff --git a/tests/unittests/TestsHelper.hpp b/tests/unittests/TestsHelper.hpp index 726dd9fde..d405a1460 100644 --- a/tests/unittests/TestsHelper.hpp +++ b/tests/unittests/TestsHelper.hpp @@ -63,6 +63,8 @@ std::string getResourcePath(const std::string& folder); std::string sanitizeCodeError(const Ark::CodeError& e); +std::string sanitizeOutput(const std::string& output); + std::string sanitizeRuntimeError(const std::exception& e); void expectOrDiff(const std::string& expected, const std::string& received); diff --git a/tests/unittests/resources/DebuggerSuite/basic.ark b/tests/unittests/resources/DebuggerSuite/basic.ark new file mode 100644 index 000000000..63f662ef8 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/basic.ark @@ -0,0 +1,12 @@ +(let a 5) +(let b 6) +(breakpoint) +(prn (format "ark: after first breakpoint, a={}, b={}" a b)) + +(let foo (fun (x y z) { + (breakpoint true) + (prn "ark: in (foo x y z), after second breakpoint") + (+ x y z) })) + +(prn (foo a b 7)) +(prn "ark: end of script") diff --git a/tests/unittests/resources/DebuggerSuite/basic.expected b/tests/unittests/resources/DebuggerSuite/basic.expected new file mode 100644 index 000000000..3406a46dc --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/basic.expected @@ -0,0 +1,38 @@ +In file tests/unittests/resources/DebuggerSuite/basic.ark:3 + 1 | (let a 5) + 2 | (let b 6) + 3 | (breakpoint) + | ^~~~~~~~~~~ + 4 | (prn (format "ark: after first breakpoint, a={}, b={}" a b)) + 5 | + +dbg[pp:0,ip:12]:000> (let c (+ a b)) +dbg[pp:0,ip:12]:001> (prn c) +11 +nil +dbg[pp:0,ip:12]:002> c +dbg: continue +ark: after first breakpoint, a=5, b=6 + +In file tests/unittests/resources/DebuggerSuite/basic.ark:7 + 4 | (prn (format "ark: after first breakpoint, a={}, b={}" a b)) + 5 | + 6 | (let foo (fun (x y z) { + 7 | (breakpoint true) + | ^~~~~~~~~~~~~~~~ + 8 | (prn "ark: in (foo x y z), after second breakpoint") + 9 | (+ x y z) })) + +dbg[pp:1,ip:20]:000> (prn x y z) +567 +nil +dbg[pp:1,ip:20]:001> help +Available commands: + help -- display this message + c, continue -- resume execution + q, quit -- quit the debugger, stopping the script execution +dbg[pp:1,ip:20]:001> continue +dbg: continue +ark: in (foo x y z), after second breakpoint +18 +ark: end of script diff --git a/tests/unittests/resources/DebuggerSuite/basic.prompt b/tests/unittests/resources/DebuggerSuite/basic.prompt new file mode 100644 index 000000000..0f5648781 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/basic.prompt @@ -0,0 +1,6 @@ +(let c (+ a b)) +(prn c) +c +(prn x y z) +help +continue diff --git a/tests/unittests/resources/DebuggerSuite/debugger_quit.ark b/tests/unittests/resources/DebuggerSuite/debugger_quit.ark new file mode 100644 index 000000000..63f662ef8 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/debugger_quit.ark @@ -0,0 +1,12 @@ +(let a 5) +(let b 6) +(breakpoint) +(prn (format "ark: after first breakpoint, a={}, b={}" a b)) + +(let foo (fun (x y z) { + (breakpoint true) + (prn "ark: in (foo x y z), after second breakpoint") + (+ x y z) })) + +(prn (foo a b 7)) +(prn "ark: end of script") diff --git a/tests/unittests/resources/DebuggerSuite/debugger_quit.expected b/tests/unittests/resources/DebuggerSuite/debugger_quit.expected new file mode 100644 index 000000000..0adb73182 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/debugger_quit.expected @@ -0,0 +1,14 @@ +In file tests/unittests/resources/DebuggerSuite/debugger_quit.ark:3 + 1 | (let a 5) + 2 | (let b 6) + 3 | (breakpoint) + | ^~~~~~~~~~~ + 4 | (prn (format "ark: after first breakpoint, a={}, b={}" a b)) + 5 | + +dbg[pp:0,ip:12]:000> (let c (+ a b)) +dbg[pp:0,ip:12]:001> (prn c) +11 +nil +dbg[pp:0,ip:12]:002> q +dbg: stop diff --git a/tests/unittests/resources/DebuggerSuite/debugger_quit.prompt b/tests/unittests/resources/DebuggerSuite/debugger_quit.prompt new file mode 100644 index 000000000..5af7037ac --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/debugger_quit.prompt @@ -0,0 +1,3 @@ +(let c (+ a b)) +(prn c) +q diff --git a/tests/unittests/resources/DebuggerSuite/loop.ark b/tests/unittests/resources/DebuggerSuite/loop.ark new file mode 100644 index 000000000..8a4b59db9 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/loop.ark @@ -0,0 +1,6 @@ +(mut i 0) +(while (< i 10) { + (breakpoint (> i 5)) + (prn (format "ark: i={}" i)) + (set i (+ 1 i)) }) +(prn "ark: end") diff --git a/tests/unittests/resources/DebuggerSuite/loop.expected b/tests/unittests/resources/DebuggerSuite/loop.expected new file mode 100644 index 000000000..424086953 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/loop.expected @@ -0,0 +1,67 @@ +ark: i=0 +ark: i=1 +ark: i=2 +ark: i=3 +ark: i=4 +ark: i=5 + +In file tests/unittests/resources/DebuggerSuite/loop.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (prn i) +6 +nil +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=6 + +In file tests/unittests/resources/DebuggerSuite/loop.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (prn i) +7 +nil +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=7 + +In file tests/unittests/resources/DebuggerSuite/loop.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (prn i) +8 +nil +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=8 + +In file tests/unittests/resources/DebuggerSuite/loop.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (prn i) +9 +nil +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=9 +ark: end diff --git a/tests/unittests/resources/DebuggerSuite/loop.prompt b/tests/unittests/resources/DebuggerSuite/loop.prompt new file mode 100644 index 000000000..e8e0cfcbf --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/loop.prompt @@ -0,0 +1,8 @@ +(prn i) +c +(prn i) +c +(prn i) +c +(prn i) +c diff --git a/tests/unittests/resources/DebuggerSuite/modify_program_state.ark b/tests/unittests/resources/DebuggerSuite/modify_program_state.ark new file mode 100644 index 000000000..8a4b59db9 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/modify_program_state.ark @@ -0,0 +1,6 @@ +(mut i 0) +(while (< i 10) { + (breakpoint (> i 5)) + (prn (format "ark: i={}" i)) + (set i (+ 1 i)) }) +(prn "ark: end") diff --git a/tests/unittests/resources/DebuggerSuite/modify_program_state.expected b/tests/unittests/resources/DebuggerSuite/modify_program_state.expected new file mode 100644 index 000000000..4155a42cd --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/modify_program_state.expected @@ -0,0 +1,34 @@ +ark: i=0 +ark: i=1 +ark: i=2 +ark: i=3 +ark: i=4 +ark: i=5 + +In file tests/unittests/resources/DebuggerSuite/modify_program_state.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (print i) +nil +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=6 + +In file tests/unittests/resources/DebuggerSuite/modify_program_state.ark:3 + 1 | (mut i 0) + 2 | (while (< i 10) { + 3 | (breakpoint (> i 5)) + | ^~~~~~~~~~~~~~~~~~~ + 4 | (prn (format "ark: i={}" i)) + 5 | (set i (+ 1 i)) }) + +dbg[pp:0,ip:32]:000> (set i 20) +dbg[pp:0,ip:32]:001> c +dbg: continue +ark: i=20 +ark: end diff --git a/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt b/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt new file mode 100644 index 000000000..4e6a91c7c --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt @@ -0,0 +1,4 @@ +(print i) +c +(set i 20) +c From da2690d0603331e8fd6b57776b82cead0cb36780 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 19 Jan 2026 17:40:34 +0100 Subject: [PATCH 14/16] chore: use const vector& when loading lambdas into Ark::State --- .github/workflows/static_analysis.yml | 2 +- src/arkscript/REPL/Repl.cpp | 6 +++--- tests/unittests/Suites/DebuggerSuite.cpp | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index c144d1d7f..1c28710c3 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -42,7 +42,7 @@ jobs: cppcheck --platform=unix64 --template="{file}:{line}: {severity}: {message}" \ --output-file=cppcheck.txt \ -I include src \ - --enable=all --inline-suppr \ + --enable=all --inline-suppr --check-level=exhaustive \ --suppressions-list=cppcheck-suppressions.txt content=$(python .github/generate_cppcheck_report.py $(echo $(git diff --name-only -r HEAD^1 HEAD))) echo "CPPCHECK_REPORT<> $GITHUB_ENV diff --git a/src/arkscript/REPL/Repl.cpp b/src/arkscript/REPL/Repl.cpp index ec73550ae..50dc14c77 100644 --- a/src/arkscript/REPL/Repl.cpp +++ b/src/arkscript/REPL/Repl.cpp @@ -121,11 +121,11 @@ namespace Ark void Repl::registerBuiltins() { - m_state.loadFunction("repl:history", [this]([[maybe_unused]] std::vector&, [[maybe_unused]] VM*) { + m_state.loadFunction("repl:history", [this]([[maybe_unused]] const std::vector&, [[maybe_unused]] VM*) { return Value(m_code); }); - m_state.loadFunction("repl:save", [this](std::vector& n, [[maybe_unused]] VM*) { + m_state.loadFunction("repl:save", [this](const std::vector& n, [[maybe_unused]] VM*) { if (!types::check(n, ValueType::String)) throw types::TypeCheckingError( "repl:save", @@ -137,7 +137,7 @@ namespace Ark return Nil; }); - m_state.loadFunction("repl:load", [this](std::vector& n, [[maybe_unused]] VM*) { + m_state.loadFunction("repl:load", [this](const std::vector& n, [[maybe_unused]] VM*) { if (!types::check(n, ValueType::String)) throw types::TypeCheckingError( "repl:load", diff --git a/tests/unittests/Suites/DebuggerSuite.cpp b/tests/unittests/Suites/DebuggerSuite.cpp index 551c1eaff..261a115b1 100644 --- a/tests/unittests/Suites/DebuggerSuite.cpp +++ b/tests/unittests/Suites/DebuggerSuite.cpp @@ -18,8 +18,7 @@ ut::suite<"Debugger"> debugger_suite = [] { std::stringstream os; Ark::State state({ lib_path }); - // cppcheck-suppress constParameterReference - state.loadFunction("prn", [&os](std::vector& args, Ark::VM* vm) -> Ark::Value { + state.loadFunction("prn", [&os](const std::vector& args, Ark::VM* vm) -> Ark::Value { for (const auto& value : args) fmt::print(os, "{}", value.toString(*vm)); fmt::println(os, ""); From 8dfa1d6c7b7d08096a443b2cecb98cce4994b580 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 19 Jan 2026 17:46:45 +0100 Subject: [PATCH 15/16] chore: addressing cppcheck recommandations: more ranges, less variable shadowing --- include/Ark/State.hpp | 5 ----- src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp | 8 +++----- src/arkreactor/VM/Debugger.cpp | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/Ark/State.hpp b/include/Ark/State.hpp index bde19c98c..ffe554dc2 100644 --- a/include/Ark/State.hpp +++ b/include/Ark/State.hpp @@ -128,11 +128,6 @@ namespace Ark */ void extendBytecode(const std::vector& pages, const std::vector& symbols, const std::vector& constants); - [[nodiscard]] inline const bytecode_t& bytecode() const noexcept - { - return m_bytecode; - } - friend class VM; friend class Repl; friend class internal::Closure; diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index f2403d702..1a40f29a3 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -21,13 +21,11 @@ namespace Ark::internal void ASTLowerer::addToTables(const std::vector& symbols, const std::vector& constants) { - for (const std::string& sym : symbols) - m_symbols.emplace_back(sym); - for (const ValTableElem& elem : constants) - m_values.emplace_back(elem); + std::ranges::copy(symbols, std::back_inserter(m_symbols)); + std::ranges::copy(constants, std::back_inserter(m_values)); } - void ASTLowerer::offsetPagesBy(std::size_t offset) + void ASTLowerer::offsetPagesBy(const std::size_t offset) { m_start_page_at_offset = offset; } diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 985804b52..d5af18795 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -14,16 +14,23 @@ namespace Ark::internal { Debugger::Debugger(const ExecutionContext& context, const std::vector& libenv, const std::vector& symbols, const std::vector& constants) : - m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(std::cout), m_colorize(true) + m_libenv(libenv), + m_symbols(symbols), + m_constants(constants), + m_os(std::cout), + m_colorize(true) { saveState(context); } Debugger::Debugger(const std::vector& libenv, const std::string& path_to_prompt_file, std::ostream& os, const std::vector& symbols, const std::vector& constants) : - m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(os), m_colorize(false) - { - m_prompt_stream = std::make_unique(path_to_prompt_file); - } + m_libenv(libenv), + m_symbols(symbols), + m_constants(constants), + m_os(os), + m_colorize(false), + m_prompt_stream(std::make_unique(path_to_prompt_file)) + {} void Debugger::saveState(const ExecutionContext& context) { From d599a701d1b8622fb4236cd7ee5c1b118ee0b97a Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Mon, 19 Jan 2026 18:00:36 +0100 Subject: [PATCH 16/16] feat(tests, debugger): testing the debugger triggering on errors --- include/Ark/VM/Debugger.hpp | 3 ++- src/arkreactor/VM/Debugger.cpp | 5 +++-- src/arkreactor/VM/VM.cpp | 4 ++-- tests/unittests/Suites/DebuggerSuite.cpp | 2 +- .../resources/DebuggerSuite/modify_program_state.expected | 3 ++- .../resources/DebuggerSuite/modify_program_state.prompt | 2 +- tests/unittests/resources/DebuggerSuite/on_error.ark | 2 ++ tests/unittests/resources/DebuggerSuite/on_error.expected | 8 ++++++++ tests/unittests/resources/DebuggerSuite/on_error.prompt | 3 +++ 9 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 tests/unittests/resources/DebuggerSuite/on_error.ark create mode 100644 tests/unittests/resources/DebuggerSuite/on_error.expected create mode 100644 tests/unittests/resources/DebuggerSuite/on_error.prompt diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index d29eb886a..68473df9e 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -87,8 +87,9 @@ namespace Ark::internal * * @param vm * @param context + * @param from_breakpoint true if the debugger is being invoked from a breakpoint */ - void run(VM& vm, ExecutionContext& context); + void run(VM& vm, ExecutionContext& context, bool from_breakpoint); [[nodiscard]] inline bool isRunning() const noexcept { diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index d5af18795..06fde1ef2 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -57,9 +57,10 @@ namespace Ark::internal m_states.pop_back(); } - void Debugger::run(VM& vm, ExecutionContext& context) + void Debugger::run(VM& vm, ExecutionContext& context, const bool from_breakpoint) { - showContext(vm, context); + if (from_breakpoint) + showContext(vm, context); m_running = true; const bool is_vm_running = vm.m_running; diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index cf6b0bda3..130f45fc5 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1200,7 +1200,7 @@ namespace Ark if (m_state.m_features & FeatureVMDebugger && breakpoint_active) { initDebugger(context); - m_debugger->run(*this, context); + m_debugger->run(*this, context, /* from_breakpoint= */ true); m_debugger->resetContextToSavedState(context); if (m_debugger->shouldQuitVM()) @@ -2306,7 +2306,7 @@ namespace Ark if (m_debugger && !error_from_debugger) { m_debugger->resetContextToSavedState(context); - m_debugger->run(*this, context); + m_debugger->run(*this, context, /* from_breakpoint= */ false); } #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION diff --git a/tests/unittests/Suites/DebuggerSuite.cpp b/tests/unittests/Suites/DebuggerSuite.cpp index 261a115b1..57a9369bf 100644 --- a/tests/unittests/Suites/DebuggerSuite.cpp +++ b/tests/unittests/Suites/DebuggerSuite.cpp @@ -37,7 +37,7 @@ ut::suite<"Debugger"> debugger_suite = [] { { Ark::VM vm(state); vm.usePromptFileForDebugger(prompt_path.generic_string(), os); - vm.run(/* fail_with_exception= */ true); + vm.run(/* fail_with_exception= */ false); const std::string output = sanitizeOutput(os.str()); expectOrDiff(data.expected, output); diff --git a/tests/unittests/resources/DebuggerSuite/modify_program_state.expected b/tests/unittests/resources/DebuggerSuite/modify_program_state.expected index 4155a42cd..75dc4b45d 100644 --- a/tests/unittests/resources/DebuggerSuite/modify_program_state.expected +++ b/tests/unittests/resources/DebuggerSuite/modify_program_state.expected @@ -13,7 +13,8 @@ In file tests/unittests/resources/DebuggerSuite/modify_program_state.ark:3 4 | (prn (format "ark: i={}" i)) 5 | (set i (+ 1 i)) }) -dbg[pp:0,ip:32]:000> (print i) +dbg[pp:0,ip:32]:000> (prn i) +6 nil dbg[pp:0,ip:32]:001> c dbg: continue diff --git a/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt b/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt index 4e6a91c7c..940856dc2 100644 --- a/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt +++ b/tests/unittests/resources/DebuggerSuite/modify_program_state.prompt @@ -1,4 +1,4 @@ -(print i) +(prn i) c (set i 20) c diff --git a/tests/unittests/resources/DebuggerSuite/on_error.ark b/tests/unittests/resources/DebuggerSuite/on_error.ark new file mode 100644 index 000000000..b73b9c20e --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/on_error.ark @@ -0,0 +1,2 @@ +(let a 5) +(prn (@ a 0)) diff --git a/tests/unittests/resources/DebuggerSuite/on_error.expected b/tests/unittests/resources/DebuggerSuite/on_error.expected new file mode 100644 index 000000000..e70e1a9c0 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/on_error.expected @@ -0,0 +1,8 @@ +dbg[pp:0,ip:12]:000> (prn a) +5 +nil +dbg[pp:0,ip:12]:001> (prn (type a)) +Number +nil +dbg[pp:0,ip:12]:002> c +dbg: continue diff --git a/tests/unittests/resources/DebuggerSuite/on_error.prompt b/tests/unittests/resources/DebuggerSuite/on_error.prompt new file mode 100644 index 000000000..bda2fb2ef --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/on_error.prompt @@ -0,0 +1,3 @@ +(prn a) +(prn (type a)) +c