From f433f9e95f39f16de86550790c061254eb704b91 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 21 Apr 2025 18:36:37 +0200 Subject: [PATCH 1/7] feat(compiler, vm): adding instruction source location tracking to the generated bytecode This will allow the VM to show source location information when an error is thrown, as well as display nicer error messages to the user. --- include/Ark/Compiler/BytecodeReader.hpp | 29 +++++- include/Ark/Compiler/Instructions.hpp | 6 +- .../IntermediateRepresentation/Entity.hpp | 12 ++- .../IntermediateRepresentation/IRCompiler.hpp | 16 +-- .../IntermediateRepresentation/InstLoc.hpp | 18 ++++ .../Serialization/IntegerSerializer.hpp | 8 +- include/Ark/VM/State.hpp | 7 +- src/arkreactor/Compiler/BytecodeReader.cpp | 80 ++++++++++++++- .../IntermediateRepresentation/Entity.cpp | 6 ++ .../IntermediateRepresentation/IRCompiler.cpp | 99 +++++++++++++++++-- .../Compiler/Lowerer/ASTLowerer.cpp | 17 +++- src/arkreactor/VM/State.cpp | 8 +- .../unittests/Suites/BytecodeReaderSuite.cpp | 27 ++++- 13 files changed, 299 insertions(+), 34 deletions(-) create mode 100644 include/Ark/Compiler/IntermediateRepresentation/InstLoc.hpp diff --git a/include/Ark/Compiler/BytecodeReader.hpp b/include/Ark/Compiler/BytecodeReader.hpp index 48d4b288f..bb9479aaa 100644 --- a/include/Ark/Compiler/BytecodeReader.hpp +++ b/include/Ark/Compiler/BytecodeReader.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace Ark { @@ -55,6 +56,20 @@ namespace Ark std::size_t end {}; ///< Point to the byte following the last byte of the table in the bytecode }; + struct Filenames + { + std::vector filenames {}; + std::size_t start {}; ///< Point to the FILENAMES_TABLE_START byte in the bytecode + std::size_t end {}; ///< Point to the byte following the last byte of the table in the bytecode + }; + + struct InstLocations + { + std::vector locations {}; + std::size_t start {}; ///< Point to the INST_LOC_TABLE_START byte in the bytecode + std::size_t end {}; ///< Point to the byte following the last byte of the table in the bytecode + }; + struct Code { std::vector pages {}; @@ -129,9 +144,21 @@ namespace Ark /** * @param values + * @return Filenames + */ + [[nodiscard]] Filenames filenames(const Values& values) const; + + /** + * @param filenames + * @return InstLocations + */ + [[nodiscard]] InstLocations instLocations(const Filenames& filenames) const; + + /** + * @param instLocations * @return Code */ - [[nodiscard]] Code code(const Values& values) const; + [[nodiscard]] Code code(const InstLocations& instLocations) const; /** * @brief Display the bytecode opcode in a human friendly way. diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 9ed415891..ae771888e 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -2,10 +2,10 @@ * @file Instructions.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief The different instructions used by the compiler and virtual machine - * @version 0.1 + * @version 1.0 * @date 2020-10-27 * - * @copyright Copyright (c) 2020-2024 + * @copyright Copyright (c) 2020-2025 * */ @@ -34,6 +34,8 @@ namespace Ark::internal NUMBER_TYPE = 0xF1, STRING_TYPE = 0xF2, FUNC_TYPE = 0xF3, + FILENAMES_TABLE_START = 0xA4, + INST_LOC_TABLE_START = 0xA5, // @args symbol id // @role Load a symbol from its ID onto the stack diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index b27b5bcf4..90a5d60e8 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -5,7 +5,7 @@ * @version 1.0 * @date 2024-10-05 * - * @copyright Copyright (c) 2024 + * @copyright Copyright (c) 2024-2025 * */ @@ -62,12 +62,22 @@ namespace Ark::internal::IR [[nodiscard]] inline uint16_t secondaryArg() const { return m_secondary_arg; } + void setSourceLocation(const std::string& filename, std::size_t line); + + [[nodiscard]] inline bool hasValidSourceLocation() const { return !m_source_file.empty(); } + + [[nodiscard]] inline const std::string& filename() const { return m_source_file; } + + [[nodiscard]] inline std::size_t sourceLine() const { return m_source_line; } + private: Kind m_kind; label_t m_label { 0 }; Instruction m_inst { NOP }; uint16_t m_primary_arg { 0 }; uint16_t m_secondary_arg { 0 }; + std::string m_source_file; + std::size_t m_source_line { 0 }; }; using Block = std::vector; diff --git a/include/Ark/Compiler/IntermediateRepresentation/IRCompiler.hpp b/include/Ark/Compiler/IntermediateRepresentation/IRCompiler.hpp index 235f69d00..2a6d47e4e 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/IRCompiler.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/IRCompiler.hpp @@ -2,10 +2,10 @@ * @file IRCompiler.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief Compile the intermediate representation to bytecode - * @version 0.1 + * @version 0.2 * @date 2024-10-05 * - * @copyright Copyright (c) 2024 + * @copyright Copyright (c) 2024-2025 * */ @@ -60,11 +60,12 @@ namespace Ark::internal Logger m_logger; bytecode_t m_bytecode; std::vector m_ir; + std::vector m_filenames; void compile(); /** - * @brief Push a word to the m_bytecode + * @brief Push a word (4 bytes) to the m_bytecode * @param word */ void pushWord(const Word& word); @@ -75,11 +76,10 @@ namespace Ark::internal */ void pushFileHeader() noexcept; - /** - * @brief Push the symbols and values tables - * - */ - void pushSymAndValTables(const std::vector& symbols, const std::vector& values); + void pushSymbolTable(const std::vector& symbols); + void pushValueTable(const std::vector& values); + void pushFilenameTable(); + void pushInstLocTable(const std::vector& pages); }; } diff --git a/include/Ark/Compiler/IntermediateRepresentation/InstLoc.hpp b/include/Ark/Compiler/IntermediateRepresentation/InstLoc.hpp new file mode 100644 index 000000000..801f6a463 --- /dev/null +++ b/include/Ark/Compiler/IntermediateRepresentation/InstLoc.hpp @@ -0,0 +1,18 @@ +#ifndef ARK_COMPILER_INTERMEDIATEREPRESENTATION_INSTLOC_HPP +#define ARK_COMPILER_INTERMEDIATEREPRESENTATION_INSTLOC_HPP + +#include + +namespace Ark::internal +{ + // pp (2 bytes), ip (2 bytes), filename id (2 bytes), line (4 bytes) -> 10 bytes per record + struct InstLoc + { + uint16_t page_pointer; + uint16_t inst_pointer; + uint16_t filename_id; + uint32_t line; + }; +} + +#endif // ARK_COMPILER_INTERMEDIATEREPRESENTATION_INSTLOC_HPP diff --git a/include/Ark/Compiler/Serialization/IntegerSerializer.hpp b/include/Ark/Compiler/Serialization/IntegerSerializer.hpp index 84a1fa851..d229c4ffc 100644 --- a/include/Ark/Compiler/Serialization/IntegerSerializer.hpp +++ b/include/Ark/Compiler/Serialization/IntegerSerializer.hpp @@ -30,15 +30,15 @@ namespace Ark::internal void serializeOn2BytesToVecLE(std::integral auto number, std::vector& out) { constexpr auto mask = static_cast(0xff); - for (std::size_t i = 0; i < 2; ++i) - out.push_back(static_cast((number & (mask << (8 * i))) >> (8 * i))); + out.push_back(static_cast(number & mask)); + out.push_back(static_cast((number & (mask << 8)) >> 8)); } void serializeOn2BytesToVecBE(std::integral auto number, std::vector& out) { constexpr auto mask = static_cast(0xff); - for (std::size_t i = 0; i < 2; ++i) - out.push_back(static_cast((number & (mask << (8 * (1 - i)))) >> (8 * (1 - i)))); + out.push_back(static_cast((number & (mask << 8)) >> 8)); + out.push_back(static_cast(number & mask)); } template diff --git a/include/Ark/VM/State.hpp b/include/Ark/VM/State.hpp index 48cf4480d..07e1d5cdc 100644 --- a/include/Ark/VM/State.hpp +++ b/include/Ark/VM/State.hpp @@ -2,10 +2,10 @@ * @file State.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief State used by the virtual machine: it loads the bytecode, can compile it if needed, load C++ functions... - * @version 0.4 + * @version 1.0 * @date 2020-10-27 * - * @copyright Copyright (c) 2020-2024 + * @copyright Copyright (c) 2020-2025 * */ @@ -21,6 +21,7 @@ #include #include #include +#include namespace Ark { @@ -148,6 +149,8 @@ namespace Ark // related to the bytecode std::vector m_symbols; std::vector m_constants; + std::vector m_filenames; + std::vector m_inst_locations; std::vector m_pages; // related to the execution diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index c263e4793..867328b6f 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -171,12 +171,85 @@ namespace Ark return block; } - Code BytecodeReader::code(const Values& values) const + Filenames BytecodeReader::filenames(const Ark::Values& values) const { if (!checkMagic()) return {}; std::size_t i = values.end; + if (m_bytecode[i] != FILENAMES_TABLE_START) + return {}; + i++; + + const uint16_t size = readNumber(i); + i++; + + Filenames block; + block.start = values.end; + block.filenames.reserve(size); + + for (uint16_t j = 0; j < size; ++j) + { + std::string val; + while (m_bytecode[i] != 0) + val.push_back(static_cast(m_bytecode[i++])); + block.filenames.emplace_back(val); + i++; + } + + block.end = i; + return block; + } + + InstLocations BytecodeReader::instLocations(const Ark::Filenames& filenames) const + { + if (!checkMagic()) + return {}; + + std::size_t i = filenames.end; + if (m_bytecode[i] != INST_LOC_TABLE_START) + return {}; + i++; + + const uint16_t size = readNumber(i); + i++; + + InstLocations block; + block.start = filenames.end; + block.locations.reserve(size); + + for (uint16_t j = 0; j < size; ++j) + { + auto pp = readNumber(i); + i++; + + auto ip = readNumber(i); + i++; + + auto file_id = readNumber(i); + i++; + + auto line = deserializeBE( + m_bytecode.begin() + static_cast::difference_type>(i), m_bytecode.end()); + i += 4; + + block.locations.push_back( + { .page_pointer = pp, + .inst_pointer = ip, + .filename_id = file_id, + .line = line }); + } + + block.end = i; + return block; + } + + Code BytecodeReader::code(const InstLocations& instLocations) const + { + if (!checkMagic()) + return {}; + + std::size_t i = instLocations.end; Code block; block.start = i; @@ -232,7 +305,10 @@ namespace Ark const auto syms = symbols(); const auto vals = values(syms); - const auto code_block = code(vals); + // todo display files and inst_locs tables + const auto files = filenames(vals); + const auto inst_locs = instLocations(files); + const auto code_block = code(inst_locs); // symbols table { diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp index 9e4ae521c..7c03a8656 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp @@ -49,4 +49,10 @@ namespace Ark::internal::IR return Word(m_inst, m_primary_arg, m_secondary_arg); return Word(0, 0); } + + void Entity::setSourceLocation(const std::string& filename, std::size_t line) + { + m_source_file = filename; + m_source_line = line; + } } diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp index 3d8d95a93..277b2c052 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -23,7 +24,21 @@ namespace Ark::internal { m_logger.traceStart("process"); pushFileHeader(); - pushSymAndValTables(symbols, values); + pushSymbolTable(symbols); + pushValueTable(values); + + // compute a list of unique filenames + for (const auto& page : pages) + { + for (const auto& inst : page) + { + if (std::ranges::find(m_filenames, inst.filename()) == m_filenames.end() && inst.hasValidSourceLocation()) + m_filenames.push_back(inst.filename()); + } + } + + pushFilenameTable(); + pushInstLocTable(pages); m_ir = pages; compile(); @@ -55,7 +70,7 @@ namespace Ark::internal for (const auto& block : m_ir) { fmt::println(stream, "page_{}", index); - for (const auto entity : block) + for (const auto& entity : block) { switch (entity.kind()) { @@ -113,13 +128,12 @@ namespace Ark::internal throw std::overflow_error(fmt::format("Size of page {} exceeds the maximum size of 2^16 - 1", i)); m_bytecode.push_back(CODE_SEGMENT_START); - m_bytecode.push_back(static_cast((page_size & 0xff00) >> 8)); - m_bytecode.push_back(static_cast(page_size & 0x00ff)); + serializeOn2BytesToVecBE(page_size, m_bytecode); // register labels position uint16_t pos = 0; std::unordered_map label_to_position; - for (auto inst : page) + for (auto& inst : page) { switch (inst.kind()) { @@ -132,7 +146,7 @@ namespace Ark::internal } } - for (auto inst : page) + for (auto& inst : page) { switch (inst.kind()) { @@ -200,7 +214,7 @@ namespace Ark::internal } } - void IRCompiler::pushSymAndValTables(const std::vector& symbols, const std::vector& values) + void IRCompiler::pushSymbolTable(const std::vector& symbols) { const std::size_t symbol_size = symbols.size(); if (symbol_size > std::numeric_limits::max()) @@ -217,7 +231,10 @@ namespace Ark::internal }); m_bytecode.push_back(0_u8); } + } + void IRCompiler::pushValueTable(const std::vector& values) + { const std::size_t value_size = values.size(); if (value_size > std::numeric_limits::max()) throw std::overflow_error(fmt::format("Too many values: {}, exceeds the maximum size of 2^16 - 1", value_size)); @@ -261,4 +278,72 @@ namespace Ark::internal m_bytecode.push_back(0_u8); } } + + void IRCompiler::pushFilenameTable() + { + if (m_filenames.size() > std::numeric_limits::max()) + throw std::overflow_error(fmt::format("Too many filenames: {}, exceeds the maximum size of 2^16 - 1", m_filenames.size())); + + m_bytecode.push_back(FILENAMES_TABLE_START); + // push number of elements + serializeOn2BytesToVecBE(m_filenames.size(), m_bytecode); + + for (const auto& name : m_filenames) + { + std::ranges::transform(name, std::back_inserter(m_bytecode), [](const char i) { + return static_cast(i); + }); + m_bytecode.push_back(0_u8); + } + } + + void IRCompiler::pushInstLocTable(const std::vector& pages) + { + std::vector locations; + for (std::size_t i = 0, end = pages.size(); i < end; ++i) + { + const auto& page = pages[i]; + uint16_t ip = 0; + + for (const auto& inst : page) + { + if (inst.hasValidSourceLocation()) + { + // we are guaranteed to have a value since we listed all existing filenames in IRCompiler::process before, + // thus we do not have to check if std::ranges::find returned a valid iterator. + auto file_id = static_cast(std::distance(m_filenames.begin(), std::ranges::find(m_filenames, inst.filename()))); + + std::optional prev = std::nullopt; + if (!locations.empty()) + prev = locations.back(); + + // skip redundant instruction location + if (!(prev.has_value() && prev->filename_id == file_id && prev->line == inst.sourceLine() && prev->page_pointer == i)) + locations.push_back( + { .page_pointer = static_cast(i), + .inst_pointer = ip, + .filename_id = file_id, + .line = static_cast(inst.sourceLine()) }); + } + + if (inst.kind() != IR::Kind::Label) + ++ip; + } + } + + m_bytecode.push_back(INST_LOC_TABLE_START); + serializeOn2BytesToVecBE(locations.size(), m_bytecode); + + std::optional prev = std::nullopt; + + for (const auto& loc : locations) + { + serializeOn2BytesToVecBE(loc.page_pointer, m_bytecode); + serializeOn2BytesToVecBE(loc.inst_pointer, m_bytecode); + serializeOn2BytesToVecBE(loc.filename_id, m_bytecode); + serializeToVecBE(loc.line, m_bytecode); + + prev = loc; + } + } } diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 17c4ee2e3..3ba39e898 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -137,6 +137,7 @@ namespace Ark::internal { uint16_t i = addSymbol(*it); page(p).emplace_back(GET_FIELD, i); + page(p).back().setSourceLocation(it->filename(), it->line()); } } // register values @@ -303,6 +304,7 @@ namespace Ark::internal break; } page(p).emplace_back(inst, static_cast(inst_argc)); + page(p).back().setSourceLocation(c0.filename(), c0.line()); if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE) // in-place functions never push a value { @@ -315,16 +317,18 @@ namespace Ark::internal { // compile condition compileExpression(x.constList()[1], p, false, false); + page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line()); - // jump only if needed to the if + // jump only if needed to the "true" branch const auto label_then = IR::Entity::Label(m_current_label++); page(p).emplace_back(IR::Entity::GotoIf(label_then, true)); - // else code + // "false" branch code if (x.constList().size() == 4) // we have an else clause { m_locals_locator.saveScopeLengthForBranch(); compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name); + page(p).back().setSourceLocation(x.constList()[3].filename(), x.constList()[3].line()); m_locals_locator.dropVarsForBranch(); } @@ -337,6 +341,7 @@ namespace Ark::internal // if code m_locals_locator.saveScopeLengthForBranch(); compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name); + page(p).back().setSourceLocation(x.constList()[2].filename(), x.constList()[2].line()); m_locals_locator.dropVarsForBranch(); // set jump to end pos page(p).emplace_back(label_end); @@ -419,6 +424,8 @@ namespace Ark::internal } else page(p).emplace_back(SET_VAL, i); + + page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line()); } void ASTLowerer::compileWhile(const Node& x, const Page p) @@ -428,6 +435,7 @@ namespace Ark::internal m_locals_locator.createScope(); page(p).emplace_back(CREATE_SCOPE); + page(p).back().setSourceLocation(x.filename(), x.line()); // save current position to jump there at the end of the loop const auto label_loop = IR::Entity::Label(m_current_label++); @@ -470,6 +478,7 @@ namespace Ark::internal uint16_t id = addValue(Node(NodeType::String, path)); // add plugin instruction + id of the constant referring to the plugin path page(p).emplace_back(PLUGIN, id); + page(p).back().setSourceLocation(x.filename(), x.line()); } void ASTLowerer::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name) @@ -543,6 +552,7 @@ namespace Ark::internal // jump to the top of the function page(p).emplace_back(JUMP, 0_u16); + page(p).back().setSourceLocation(node.filename(), node.line()); return; // skip the potential Instruction::POP at the end } else @@ -576,6 +586,7 @@ namespace Ark::internal } // call the procedure page(p).emplace_back(CALL, args_count); + page(p).back().setSourceLocation(node.filename(), node.line()); } } else // operator @@ -619,6 +630,8 @@ namespace Ark::internal else if (exp_count <= 1) buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]); + page(p).back().setSourceLocation(node.filename(), node.line()); + // need to check we didn't push the (op A B C D...) things for operators not supporting it if (exp_count > 2) { diff --git a/src/arkreactor/VM/State.cpp b/src/arkreactor/VM/State.cpp index dd677b248..555e9934d 100644 --- a/src/arkreactor/VM/State.cpp +++ b/src/arkreactor/VM/State.cpp @@ -167,10 +167,14 @@ namespace Ark const auto syms = bcr.symbols(); const auto vals = bcr.values(syms); - const auto [pages, _] = bcr.code(vals); + 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; + m_filenames = files.filenames; + m_inst_locations = inst_locs.locations; m_pages = pages; } @@ -178,6 +182,8 @@ namespace Ark { m_symbols.clear(); m_constants.clear(); + m_filenames.clear(); + m_inst_locations.clear(); m_pages.clear(); m_binded.clear(); } diff --git a/tests/unittests/Suites/BytecodeReaderSuite.cpp b/tests/unittests/Suites/BytecodeReaderSuite.cpp index 2655b652c..278aa9ca4 100644 --- a/tests/unittests/Suites/BytecodeReaderSuite.cpp +++ b/tests/unittests/Suites/BytecodeReaderSuite.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,11 +11,13 @@ #include using namespace boost; +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( @@ -22,7 +25,7 @@ ut::suite<"BytecodeReader"> bcr_suite = [] { .count()); should("compile without error") = [&] { - expect(mut(welder).computeASTFromFile(getResourcePath("BytecodeReaderSuite/ackermann.ark"))); + expect(mut(welder).computeASTFromFile(script_path)); expect(mut(welder).generateBytecode()); }; @@ -60,7 +63,10 @@ ut::suite<"BytecodeReader"> bcr_suite = [] { const auto symbols_block = bcr.symbols(); const auto values_block = bcr.values(symbols_block); - const auto [pages, start_code] = bcr.code(values_block); + // todo test the filenames and inst_locations block + const auto filenames_block = bcr.filenames(values_block); + const auto inst_locations_block = bcr.instLocations(filenames_block); + const auto [pages, start_code] = bcr.code(inst_locations_block); should("list all symbols") = [symbols_block] { using namespace std::literals::string_literals; @@ -100,8 +106,21 @@ ut::suite<"BytecodeReader"> bcr_suite = [] { ); }; - should("list all code page") = [values_block, pages, start_code] { - expect(that % start_code == values_block.end); + should("list all filenames") = [values_block, filenames_block, script_path] { + expect(that % values_block.end == filenames_block.start); + expect(that % filenames_block.filenames.size() == 1_z); + expect(that % filenames_block.filenames.front() == script_path); + }; + + should("have registered some instruction locations") = [filenames_block, inst_locations_block] { + expect(that % filenames_block.end == inst_locations_block.start); + expect(that % inst_locations_block.locations.size() > 1_z); + expect(that % inst_locations_block.locations.front().page_pointer == 0_z); + expect(that % inst_locations_block.locations.back().page_pointer == 1_z); + }; + + should("list all code page") = [inst_locations_block, pages, start_code] { + expect(that % start_code == inst_locations_block.end); expect(that % pages.size() == 2ull); // 7 instructions on 4 bytes expect(that % pages[0].size() == 7 * 4ull); From d6c110bf73168853f0b32ae26a2957047a94892f Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Mon, 21 Apr 2025 19:26:57 +0200 Subject: [PATCH 2/7] feat(vm): using the instruction source locations to display better error messages at runtime --- CHANGELOG.md | 1 + include/Ark/Compiler/BytecodeReader.hpp | 3 +- .../Serialization/IntegerSerializer.hpp | 4 +- include/Ark/VM/VM.hpp | 9 +++ src/arkreactor/Compiler/BytecodeReader.cpp | 28 +++++++++- .../Compiler/Lowerer/ASTLowerer.cpp | 2 +- src/arkreactor/Exceptions.cpp | 55 +++++++++++++------ src/arkreactor/VM/VM.cpp | 48 +++++++++++++++- 8 files changed, 125 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb78c3fd5..7471e8bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - `INCREMENT_BY_INDEX` and `DECREMENT_BY_INDEX` instructions for parity with the super instructions not using load by index - `STORE_TAIL_BY_INDEX`, `STORE_HEAD_BY_INDEX`, `SET_VAL_TAIL_BY_INDEX`, `SET_VAL_HEAD_BY_INDEX` super instructions added for parity with the super instructions not using load by index - `RESET_SCOPE` instruction emitted at the end of a while loop to reset a scope so that we can create multiple variables and use `LOAD_SYMBOL_BY_INDEX` +- instruction source location ; two new bytecode tables were added: one for filenames, another for (page pointer, instruction pointer, file id, line), allowing the VM to display better error messages when the source is available ### Changed - instructions are on 4 bytes: 1 byte for the instruction, 1 byte of padding, 2 bytes for an immediate argument diff --git a/include/Ark/Compiler/BytecodeReader.hpp b/include/Ark/Compiler/BytecodeReader.hpp index bb9479aaa..6afe0d5bd 100644 --- a/include/Ark/Compiler/BytecodeReader.hpp +++ b/include/Ark/Compiler/BytecodeReader.hpp @@ -32,7 +32,8 @@ namespace Ark Symbols, Values, Code, - HeadersOnly + HeadersOnly, + InstructionLocation }; struct Version diff --git a/include/Ark/Compiler/Serialization/IntegerSerializer.hpp b/include/Ark/Compiler/Serialization/IntegerSerializer.hpp index d229c4ffc..199872753 100644 --- a/include/Ark/Compiler/Serialization/IntegerSerializer.hpp +++ b/include/Ark/Compiler/Serialization/IntegerSerializer.hpp @@ -55,10 +55,10 @@ namespace Ark::internal template T deserializeBE(std::vector::const_iterator begin, std::vector::const_iterator end) { - constexpr std::size_t length = sizeof(T) - 1; + constexpr std::size_t length = sizeof(T); T result {}; for (std::size_t i = 0; i < length && begin != end; ++i, ++begin) - result += static_cast(*begin) << (8 * (length - i)); + result += static_cast(*begin) << (8 * (length - i - 1)); return result; } diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index df885d820..0f2bd5532 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -338,6 +338,15 @@ namespace Ark */ static void throwVMError(internal::ErrorKind kind, const std::string& message); + /** + * @brief Find the nearest source location information given instruction and page pointers + * + * @param ip + * @param pp + * @return std::optional + */ + std::optional findSourceLocation(std::size_t ip, std::size_t pp); + /** * @brief Display a backtrace when the VM encounter an exception * diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index 867328b6f..68fea6eeb 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -305,7 +305,6 @@ namespace Ark const auto syms = symbols(); const auto vals = values(syms); - // todo display files and inst_locs tables const auto files = filenames(vals); const auto inst_locs = instLocations(files); const auto code_block = code(inst_locs); @@ -384,6 +383,33 @@ namespace Ark return; } + // inst locs + file + { + std::size_t size = inst_locs.locations.size(); + std::size_t sliceSize = size; + + bool showVal = (segment == BytecodeSegment::All || segment == BytecodeSegment::InstructionLocation); + if (showVal && sStart.has_value() && sEnd.has_value() && (sStart.value() > size || sEnd.value() > size)) + fmt::print(fmt::fg(fmt::color::red), "Slice start or end can't be greater than the segment size: {}\n", size); + else if (showVal && sStart.has_value() && sEnd.has_value()) + sliceSize = sEnd.value() - sStart.value() + 1; + + if (showVal || segment == BytecodeSegment::HeadersOnly) + fmt::println("{} (length: {})", fmt::styled("Instruction locations table", fmt::fg(fmt::color::cyan)), sliceSize); + if (showVal) + fmt::println(" PP, IP"); + + for (std::size_t j = 0; j < size; ++j) + { + if (auto start = sStart; auto end = sEnd) + showVal = showVal && (j >= start.value() && j <= end.value()); + + const auto& location = inst_locs.locations[j]; + if (showVal) + fmt::println("{:>3},{:>3} -> {}:{}", location.page_pointer, location.inst_pointer, files.filenames[location.filename_id], location.line); + } + } + const auto stringify_value = [](const Value& val) -> std::string { switch (val.valueType()) { diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 3ba39e898..00ac379a5 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -630,7 +630,7 @@ namespace Ark::internal else if (exp_count <= 1) buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]); - page(p).back().setSourceLocation(node.filename(), node.line()); + page(p).back().setSourceLocation(x.filename(), x.line()); // need to check we didn't push the (op A B C D...) things for operators not supporting it if (exp_count > 2) diff --git a/src/arkreactor/Exceptions.cpp b/src/arkreactor/Exceptions.cpp index 781d560b6..3aab09905 100644 --- a/src/arkreactor/Exceptions.cpp +++ b/src/arkreactor/Exceptions.cpp @@ -108,23 +108,44 @@ namespace Ark::Diagnostics if (i == target_line || (i > target_line && overflow > 0)) { fmt::print(os, " |"); - // if we have an overflow then we start at the beginning of the line - const std::size_t curr_col_start = (overflow == 0) ? col_start : 0; - // if we have an overflow, it is used as the end of the line - const std::size_t col_end = (i == target_line) ? std::min(col_start + sym_size, ctx[target_line].size()) - : std::min(overflow, ctx[i].size()); - // update the overflow to avoid going here again if not needed - overflow = (overflow > ctx[i].size()) ? overflow - ctx[i].size() : 0; - - fmt::print( - os, - "{: <{}}{:~<{}}\n", - // padding of spaces - " ", - std::max(1_z, curr_col_start), // fixing padding when the error is on the first character - // underline the error in red - fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()), - col_end - curr_col_start); + + if (sym_size > 0) + { + // if we have an overflow then we start at the beginning of the line + const std::size_t curr_col_start = (overflow == 0) ? col_start : 0; + // if we have an overflow, it is used as the end of the line + const std::size_t col_end = (i == target_line) ? std::min(col_start + sym_size, ctx[target_line].size()) + : std::min(overflow, ctx[i].size()); + // update the overflow to avoid going here again if not needed + overflow = (overflow > ctx[i].size()) ? overflow - ctx[i].size() : 0; + + fmt::print( + os, + "{: <{}}{:~<{}}\n", + // padding of spaces + " ", + std::max(1_z, curr_col_start), // fixing padding when the error is on the first character + // underline the error in red + fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()), + col_end - curr_col_start); + } + else + { + // first non-whitespace character of the line + // +1 for the leading whitespace after ` |` before the code + const std::size_t curr_col_start = ctx[i].find_first_not_of(" \t\v") + 1; + + // highlight the current line but skip any leading whitespace + fmt::print( + os, + "{: <{}}{:~<{}}\n", + // padding of spaces + " ", + curr_col_start, + // underline the whole line in red + fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()), + ctx[target_line].size() - curr_col_start); + } } } } diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 5ece28f65..2111073f4 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1587,12 +1587,51 @@ namespace Ark throw std::runtime_error(std::string(errorKinds[static_cast(kind)]) + ": " + message + "\n"); } + std::optional VM::findSourceLocation(const std::size_t ip, const std::size_t pp) + { + std::optional match = std::nullopt; + + for (const auto location : m_state.m_inst_locations) + { + if (location.page_pointer == pp && !match) + match = location; + + // select the best match: we want to find the location that's nearest our instruction pointer + if (location.page_pointer == pp && match && location.inst_pointer <= ip / 4) + match = location; + + // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip) + if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer > ip / 4)) + break; + } + + return match; + } + void VM::backtrace(ExecutionContext& context) noexcept { const std::size_t saved_ip = context.ip; const std::size_t saved_pp = context.pp; const uint16_t saved_sp = context.sp; + const auto maybe_location = findSourceLocation(context.ip, context.pp); + if (maybe_location) + { + const auto filename = m_state.m_filenames[maybe_location->filename_id]; + + fmt::println("In file {}", filename, maybe_location->line + 1); + + if (Utils::fileExists(filename)) + Diagnostics::makeContext( + std::cout, + Utils::readFile(filename), + maybe_location->line, + /* col_start= */ 0, + /* sym_size= */ 0, + /* colorize= */ true); + fmt::println(""); + } + if (const uint16_t original_frame_count = context.fc; original_frame_count > 1) { // display call stack trace @@ -1600,6 +1639,9 @@ namespace Ark while (context.fc != 0) { + const auto maybe_call_loc = findSourceLocation(context.ip, context.pp); + const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : ""; + fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan))); if (context.pp != 0) { @@ -1608,9 +1650,9 @@ namespace Ark context); if (id < m_state.m_symbols.size()) - fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green))); + fmt::println("In function `{}'{}", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)), loc_as_text); else // should never happen - fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold))); + fmt::println("In function `{}'{}", fmt::styled("???", fmt::fg(fmt::color::gold)), loc_as_text); Value* ip; do @@ -1624,7 +1666,7 @@ namespace Ark } else { - fmt::println("In global scope"); + fmt::println("In global scope{}", loc_as_text); break; } From ae6ab6b15f42aad9f5ece690ddfa9db1c3fbbdf5 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Tue, 22 Apr 2025 13:37:04 +0200 Subject: [PATCH 3/7] feat(tests, vm): update the diagnostic tests and fixes a bug inside findSourceLocation (was refering to the next instruction instead of the last executed one) --- CMakeLists.txt | 1 + include/Ark/Exceptions.hpp | 60 +++++-- include/Ark/TypeChecker.hpp | 27 +++- include/Ark/VM/VM.hpp | 6 +- src/arkreactor/Builtins/Async.cpp | 4 +- src/arkreactor/Builtins/IO.cpp | 20 +-- src/arkreactor/Builtins/List.cpp | 12 +- src/arkreactor/Builtins/Mathematics.cpp | 40 ++--- src/arkreactor/Builtins/String.cpp | 12 +- src/arkreactor/Builtins/System.cpp | 6 +- .../IROptimizer.cpp | 13 +- .../Compiler/Lowerer/ASTLowerer.cpp | 5 +- src/arkreactor/Exceptions.cpp | 6 +- src/arkreactor/TypeChecker.cpp | 6 +- src/arkreactor/VM/VM.cpp | 146 +++++++++++------- tests/unittests/Suites/DiagnosticsSuite.cpp | 11 +- tests/unittests/Suites/TypeCheckerSuite.cpp | 26 ++-- .../runtime/arity_error_async.expected | 8 + .../runtime/at_at_eq_out_of_range_x.expected | 6 + .../runtime/at_at_eq_out_of_range_y.expected | 6 + .../runtime/at_at_inner_out_of_range.expected | 6 + .../runtime/at_at_out_of_range.expected | 6 + .../runtime/at_eq_out_of_range.expected | 6 + .../runtime/at_out_of_range.expected | 6 + .../runtime/at_str_out_of_range.expected | 6 + .../runtime/closure_field_wrong_fqn.expected | 8 + .../DiagnosticsSuite/runtime/db0.expected | 5 + .../runtime/del_unbound.expected | 6 + .../runtime/fmt_arg_not_found.expected | 4 + .../runtime/list_set_at.expected | 4 + .../runtime/list_slice_end_start.expected | 4 + .../runtime/list_slice_past_end.expected | 4 + .../runtime/list_slice_start_less_0.expected | 4 + .../runtime/list_slice_step_null.expected | 4 + .../runtime/nil_not_a_function.expected | 6 + .../runtime/not_a_closure.expected | 7 + .../runtime/not_callable.expected | 5 + .../runtime/not_enough_args.expected | 12 ++ .../runtime/out_of_range_in_place.expected | 6 + .../runtime/pop_out_of_range.expected | 6 + .../runtime/recursion_depth.expected | 22 +++ .../runtime/set_unbound.expected | 6 + .../runtime/str_remove_out_of_bound.expected | 4 + .../runtime/string_set_at.expected | 4 + .../runtime/too_many_args.expected | 12 ++ .../runtime/unbound_var.expected | 6 + .../runtime/unknown_field.expected | 8 + 47 files changed, 443 insertions(+), 155 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cabb1269..034114657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,6 +228,7 @@ if (ARK_TESTS) --exclude 'tests/*' --exclude 'lib/*' --exclude '/usr/*' + --exclude 'build/*' --gcov-tool ${ark_SOURCE_DIR}/tests/llvm-gcov.sh --output-file coverage.info # generate report diff --git a/include/Ark/Exceptions.hpp b/include/Ark/Exceptions.hpp index 60d0896fe..8fcea3cc2 100644 --- a/include/Ark/Exceptions.hpp +++ b/include/Ark/Exceptions.hpp @@ -5,7 +5,7 @@ * @version 1.3 * @date 2020-10-27 * - * @copyright Copyright (c) 2020-2024 + * @copyright Copyright (c) 2020-2025 * */ @@ -35,6 +35,25 @@ namespace Ark explicit Error(const std::string& message) : std::runtime_error(message) {} + + [[nodiscard]] virtual std::string details() const + { + return what(); + } + + Error& colorize(const bool toggle) noexcept + { + m_colorize = toggle; + return *this; + } + + [[nodiscard]] bool shouldColorize() const noexcept + { + return m_colorize; + } + + private: + bool m_colorize = true; }; /** @@ -50,31 +69,37 @@ namespace Ark }; /** - * @brief A special zero division error triggered when a number is divided by 0 + * @brief An assertion error, only triggered from ArkScript code through (assert expr error-message) * */ - class ARK_API ZeroDivisionError final : public Error + class ARK_API AssertionFailed final : public Error { public: - ZeroDivisionError() : - Error( - "ZeroDivisionError: In ordinary arithmetic, the expression has no meaning, " - "as there is no number which, when multiplied by 0, gives a (assuming a != 0), " - "and so division by zero is undefined. Since any number multiplied by 0 is 0, " - "the expression 0/0 is also undefined.") + explicit AssertionFailed(const std::string& message) : + Error("AssertionFailed: " + message) {} }; - /** - * @brief An assertion error, only triggered from ArkScript code through (assert expr error-message) - * - */ - class ARK_API AssertionFailed final : public Error + class ARK_API NestedError final : public Error { public: - explicit AssertionFailed(const std::string& message) : - Error("AssertionFailed: " + message) + NestedError(const Error& e, const std::string& details) : + Error("NestedError"), + m_details(Error(e).colorize(false).details() + "\n" + details) {} + + NestedError(const std::exception& e, const std::string& details) : + Error("NestedError"), + m_details(e.what() + ("\n" + details)) + {} + + [[nodiscard]] const char* what() const noexcept override + { + return m_details.c_str(); + } + + private: + std::string m_details; }; /** @@ -111,9 +136,10 @@ namespace Ark * @param target_line line where the error is * @param col_start where the error starts on the given line * @param sym_size bad expression that triggered the error + * @param whole_line when true, underline the whole line, disregarding col_start and sym_size * @param colorize generate colors or not */ - ARK_API void makeContext(std::ostream& os, const std::string& code, std::size_t target_line, std::size_t col_start, std::size_t sym_size, bool colorize); + ARK_API void makeContext(std::ostream& os, const std::string& code, std::size_t target_line, std::size_t col_start, std::size_t sym_size, bool whole_line, bool colorize); /** * @brief Helper used by the compiler to generate a colorized context from a node diff --git a/include/Ark/TypeChecker.hpp b/include/Ark/TypeChecker.hpp index 5b59f1cb4..2ca2fc9c6 100644 --- a/include/Ark/TypeChecker.hpp +++ b/include/Ark/TypeChecker.hpp @@ -5,7 +5,7 @@ * @version 1.1 * @date 2022-01-16 * - * @copyright Copyright (c) 2022-2024 + * @copyright Copyright (c) 2022-2025 * */ @@ -94,12 +94,35 @@ namespace Ark::types * @param os output stream, default to cout * @param colorize enable output colorizing */ - ARK_API void generateError [[noreturn]] ( + ARK_API void generateError( const std::string_view& funcname, const std::vector& contracts, const std::vector& args, std::ostream& os = std::cout, bool colorize = true); + + class ARK_API TypeCheckingError : public Error + { + public: + TypeCheckingError(std::string&& funcname, const std::vector& contracts, const std::vector& args) : + Error("TypeCheckingError"), + m_funcname(std::move(funcname)), + m_contracts(contracts), + m_passed_args(args) + {} + + [[nodiscard]] std::string details() const override + { + std::stringstream stream; + generateError(m_funcname, m_contracts, m_passed_args, stream, shouldColorize()); + return stream.str(); + } + + private: + std::string m_funcname; + std::vector m_contracts; + std::vector m_passed_args; + }; } #endif diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index 0f2bd5532..56a01ccc0 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -338,6 +338,8 @@ namespace Ark */ static void throwVMError(internal::ErrorKind kind, const std::string& message); + void showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context); + /** * @brief Find the nearest source location information given instruction and page pointers * @@ -351,8 +353,10 @@ namespace Ark * @brief Display a backtrace when the VM encounter an exception * * @param context + * @param os + * @param colorize */ - void backtrace(internal::ExecutionContext& context) noexcept; + void backtrace(internal::ExecutionContext& context, std::ostream& os = std::cout, bool colorize = true); /** * @brief Function called when the CALL instruction is met in the bytecode diff --git a/src/arkreactor/Builtins/Async.cpp b/src/arkreactor/Builtins/Async.cpp index 031e0dcfc..9c3446368 100644 --- a/src/arkreactor/Builtins/Async.cpp +++ b/src/arkreactor/Builtins/Async.cpp @@ -24,7 +24,7 @@ namespace Ark::internal::Builtins::Async Value async(std::vector& n, VM* vm) { if (n.empty() || (n[0].valueType() != ValueType::PageAddr && n[0].valueType() != ValueType::CProc && n[0].valueType() != ValueType::Closure)) - types::generateError( + throw types::TypeCheckingError( "async", { { types::Contract { { types::Typedef("function", { ValueType::PageAddr, ValueType::CProc, ValueType::Closure }), types::Typedef("args", ValueType::Any, /* is_variadic= */ true) } }, types::Contract { { types::Typedef("function", { ValueType::PageAddr, ValueType::CProc, ValueType::Closure }) } } } }, n); Future* future = vm->createFuture(n); @@ -46,7 +46,7 @@ namespace Ark::internal::Builtins::Async Value await(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::User) || !n[0].usertypeRef().is()) - types::generateError("await", { { types::Contract { { types::Typedef("future", ValueType::User) } } } }, n); + throw types::TypeCheckingError("await", { { types::Contract { { types::Typedef("future", ValueType::User) } } } }, n); auto& f = n[0].usertypeRef().as(); Value res = f.resolve(); diff --git a/src/arkreactor/Builtins/IO.cpp b/src/arkreactor/Builtins/IO.cpp index 38bdb3392..a2c847724 100644 --- a/src/arkreactor/Builtins/IO.cpp +++ b/src/arkreactor/Builtins/IO.cpp @@ -63,7 +63,7 @@ namespace Ark::internal::Builtins::IO if (types::check(n, ValueType::String)) fmt::print("{}", n[0].string()); else if (!n.empty()) - types::generateError("input", { { types::Contract {}, types::Contract { { types::Typedef("prompt", ValueType::String) } } } }, n); + throw types::TypeCheckingError("input", { { types::Contract {}, types::Contract { { types::Typedef("prompt", ValueType::String) } } } }, n); std::string line; std::getline(std::cin, line); @@ -95,7 +95,7 @@ namespace Ark::internal::Builtins::IO throw std::runtime_error(fmt::format("io:writeFile: couldn't write to file \"{}\"", n[0].stringRef())); } else - types::generateError( + throw types::TypeCheckingError( "io:writeFile", { { types::Contract { { types::Typedef("filename", ValueType::String), types::Typedef("content", ValueType::Any) } } } }, n); @@ -127,7 +127,7 @@ namespace Ark::internal::Builtins::IO throw std::runtime_error(fmt::format("io:appendToFile: couldn't write to file \"{}\"", n[0].stringRef())); } else - types::generateError( + throw types::TypeCheckingError( "io:appendToFile", { { types::Contract { { types::Typedef("filename", ValueType::String), types::Typedef("content", ValueType::Any) } } } }, n); @@ -147,7 +147,7 @@ namespace Ark::internal::Builtins::IO Value readFile(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "io:readFile", { { types::Contract { { types::Typedef("filename", ValueType::String) } } } }, n); @@ -172,7 +172,7 @@ namespace Ark::internal::Builtins::IO Value fileExists(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "io:fileExists?", { { types::Contract { { types::Typedef("filename", ValueType::String) } } } }, n); @@ -192,7 +192,7 @@ namespace Ark::internal::Builtins::IO Value listFiles(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "io:listFiles", { { types::Contract { { types::Typedef("path", ValueType::String) } } } }, n); @@ -218,7 +218,7 @@ namespace Ark::internal::Builtins::IO Value isDirectory(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "io:dir?", { { types::Contract { { types::Typedef("path", ValueType::String) } } } }, n); @@ -238,7 +238,7 @@ namespace Ark::internal::Builtins::IO Value makeDir(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "io:makeDir", { { types::Contract { { types::Typedef("path", ValueType::String) } } } }, n); @@ -260,7 +260,7 @@ namespace Ark::internal::Builtins::IO Value removeFiles(std::vector& n, VM* vm [[maybe_unused]]) { if (n.empty() || n[0].valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "io:removeFiles", { { types::Contract { { types::Typedef("filename", ValueType::String), types::Typedef("filenames", ValueType::String, /* variadic */ true) } } } }, n); @@ -268,7 +268,7 @@ namespace Ark::internal::Builtins::IO for (auto it = n.begin(), it_end = n.end(); it != it_end; ++it) { if (it->valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "io:removeFiles", { { types::Contract { { types::Typedef("filename", ValueType::String), types::Typedef("filenames", ValueType::String, /* variadic */ true) } } } }, n); diff --git a/src/arkreactor/Builtins/List.cpp b/src/arkreactor/Builtins/List.cpp index f74e81f76..2c2ddc032 100644 --- a/src/arkreactor/Builtins/List.cpp +++ b/src/arkreactor/Builtins/List.cpp @@ -22,7 +22,7 @@ namespace Ark::internal::Builtins::List Value reverseList(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::List)) - types::generateError( + throw types::TypeCheckingError( "list:reverse", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, n); @@ -46,7 +46,7 @@ namespace Ark::internal::Builtins::List Value findInList(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::List, ValueType::Any)) - types::generateError( + throw types::TypeCheckingError( "list:find", { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any) } } } }, n); @@ -72,7 +72,7 @@ namespace Ark::internal::Builtins::List Value sliceList(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::List, ValueType::Number, ValueType::Number, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "list:slice", { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("start", ValueType::Number), @@ -114,7 +114,7 @@ namespace Ark::internal::Builtins::List Value sort_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::List)) - types::generateError( + throw types::TypeCheckingError( "list:sort", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, n); @@ -136,7 +136,7 @@ namespace Ark::internal::Builtins::List Value fill(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number, ValueType::Any)) - types::generateError( + throw types::TypeCheckingError( "list:fill", { { types::Contract { { types::Typedef("size", ValueType::Number), types::Typedef("value", ValueType::Any) } } } }, @@ -165,7 +165,7 @@ namespace Ark::internal::Builtins::List Value setListAt(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::List, ValueType::Number, ValueType::Any)) - types::generateError( + throw types::TypeCheckingError( "list:setAt", { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number), diff --git a/src/arkreactor/Builtins/Mathematics.cpp b/src/arkreactor/Builtins/Mathematics.cpp index 9679ba440..19b900a8e 100644 --- a/src/arkreactor/Builtins/Mathematics.cpp +++ b/src/arkreactor/Builtins/Mathematics.cpp @@ -22,7 +22,7 @@ namespace Ark::internal::Builtins::Mathematics Value exponential(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:exp", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -42,7 +42,7 @@ namespace Ark::internal::Builtins::Mathematics Value logarithm(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:ln", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -65,7 +65,7 @@ namespace Ark::internal::Builtins::Mathematics Value ceil_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:ceil", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -85,7 +85,7 @@ namespace Ark::internal::Builtins::Mathematics Value floor_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:floor", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -106,7 +106,7 @@ namespace Ark::internal::Builtins::Mathematics Value round_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:round", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -127,7 +127,7 @@ namespace Ark::internal::Builtins::Mathematics Value isnan_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Any)) - types::generateError( + throw types::TypeCheckingError( "math:NaN?", { { types::Contract { { types::Typedef("value", ValueType::Any) } } } }, n); @@ -151,7 +151,7 @@ namespace Ark::internal::Builtins::Mathematics Value isinf_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Any)) - types::generateError( + throw types::TypeCheckingError( "math:Inf?", { { types::Contract { { types::Typedef("value", ValueType::Any) } } } }, n); @@ -175,7 +175,7 @@ namespace Ark::internal::Builtins::Mathematics Value cos_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:cos", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -196,7 +196,7 @@ namespace Ark::internal::Builtins::Mathematics Value sin_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:sin", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -217,7 +217,7 @@ namespace Ark::internal::Builtins::Mathematics Value tan_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:tan", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -237,7 +237,7 @@ namespace Ark::internal::Builtins::Mathematics Value acos_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:arccos", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -257,7 +257,7 @@ namespace Ark::internal::Builtins::Mathematics Value asin_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:arcsin", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -277,7 +277,7 @@ namespace Ark::internal::Builtins::Mathematics Value atan_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:arctan", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -294,7 +294,7 @@ namespace Ark::internal::Builtins::Mathematics Value cosh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:cosh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -311,7 +311,7 @@ namespace Ark::internal::Builtins::Mathematics Value sinh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:sinh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -328,7 +328,7 @@ namespace Ark::internal::Builtins::Mathematics Value tanh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:tanh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -345,7 +345,7 @@ namespace Ark::internal::Builtins::Mathematics Value acosh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:acosh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -362,7 +362,7 @@ namespace Ark::internal::Builtins::Mathematics Value asinh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:asinh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -379,7 +379,7 @@ namespace Ark::internal::Builtins::Mathematics Value atanh_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "math:atanh", { { types::Contract { { types::Typedef("value", ValueType::Number) } } } }, n); @@ -403,7 +403,7 @@ namespace Ark::internal::Builtins::Mathematics static std::mt19937 gen { std::random_device()() }; if (n.size() == 2 && !types::check(n, ValueType::Number, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "random", { { types::Contract { { types::Typedef("min", ValueType::Number), types::Typedef("max", ValueType::Number) } } } }, diff --git a/src/arkreactor/Builtins/String.cpp b/src/arkreactor/Builtins/String.cpp index 0531bd9f1..be1c75a59 100644 --- a/src/arkreactor/Builtins/String.cpp +++ b/src/arkreactor/Builtins/String.cpp @@ -29,7 +29,7 @@ namespace Ark::internal::Builtins::String Value format(std::vector& n, VM* vm) { if (n.size() < 2 || n[0].valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "string:format", { { types::Contract { { types::Typedef("string", ValueType::String), types::Typedef("value", ValueType::Any, /* variadic */ true) } } } }, @@ -86,7 +86,7 @@ namespace Ark::internal::Builtins::String { if (!types::check(n, ValueType::String, ValueType::String) && !types::check(n, ValueType::String, ValueType::String, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "string:find", { { types::Contract { { types::Typedef("string", ValueType::String), @@ -119,7 +119,7 @@ namespace Ark::internal::Builtins::String Value removeAtStr(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "string:removeAt", { { types::Contract { { types::Typedef("string", ValueType::String), types::Typedef("index", ValueType::Number) } } } }, n); @@ -145,7 +145,7 @@ namespace Ark::internal::Builtins::String Value ord(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "string:ord", { { types::Contract { { types::Typedef("string", ValueType::String) } } } }, n); @@ -166,7 +166,7 @@ namespace Ark::internal::Builtins::String Value chr(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "string:chr", { { types::Contract { { types::Typedef("codepoint", ValueType::Number) } } } }, n); @@ -191,7 +191,7 @@ namespace Ark::internal::Builtins::String Value setStringAt(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String, ValueType::Number, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "string:setAt", { { types::Contract { { types::Typedef("string", ValueType::String), types::Typedef("index", ValueType::Number), diff --git a/src/arkreactor/Builtins/System.cpp b/src/arkreactor/Builtins/System.cpp index cf03bea2a..150e68aa8 100644 --- a/src/arkreactor/Builtins/System.cpp +++ b/src/arkreactor/Builtins/System.cpp @@ -43,7 +43,7 @@ namespace Ark::internal::Builtins::System Value system_(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "sys:exec", { { types::Contract { { types::Typedef("command", ValueType::String) } } } }, n); @@ -75,7 +75,7 @@ namespace Ark::internal::Builtins::System Value sleep(std::vector& n, VM* vm [[maybe_unused]]) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "sys:sleep", { { types::Contract { { types::Typedef("duration", ValueType::Number) } } } }, n); @@ -99,7 +99,7 @@ namespace Ark::internal::Builtins::System Value exit_(std::vector& n, VM* vm) { if (!types::check(n, ValueType::Number)) - types::generateError( + throw types::TypeCheckingError( "sys:exit", { { types::Contract { { types::Typedef("exitCode", ValueType::Number) } } } }, n); diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp index c80677952..f739191eb 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp @@ -204,7 +204,18 @@ namespace Ark::internal if (match(expected, entities) && condition(entities)) { auto [first, second] = createReplacement(entities); - return IR::Entity(replacement, first, second); + auto output = IR::Entity(replacement, first, second); + + for (const auto& entity : entities) + { + if (entity.hasValidSourceLocation()) + { + output.setSourceLocation(entity.filename(), entity.sourceLine()); + break; + } + } + + return output; } } diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 00ac379a5..4d8424294 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -137,8 +137,8 @@ namespace Ark::internal { uint16_t i = addSymbol(*it); page(p).emplace_back(GET_FIELD, i); - page(p).back().setSourceLocation(it->filename(), it->line()); } + page(p).back().setSourceLocation(x.filename(), x.line()); } // register values else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number) @@ -212,6 +212,7 @@ namespace Ark::internal case Keyword::Del: page(p).emplace_back(DEL, addSymbol(x.constList()[1])); + page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line()); break; } } @@ -425,7 +426,7 @@ namespace Ark::internal else page(p).emplace_back(SET_VAL, i); - page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line()); + page(p).back().setSourceLocation(x.filename(), x.line()); } void ASTLowerer::compileWhile(const Node& x, const Page p) diff --git a/src/arkreactor/Exceptions.cpp b/src/arkreactor/Exceptions.cpp index 3aab09905..39985acd4 100644 --- a/src/arkreactor/Exceptions.cpp +++ b/src/arkreactor/Exceptions.cpp @@ -83,7 +83,7 @@ namespace Ark::Diagnostics } } - void makeContext(std::ostream& os, const std::string& code, const std::size_t target_line, const std::size_t col_start, const std::size_t sym_size, const bool colorize) + void makeContext(std::ostream& os, const std::string& code, const std::size_t target_line, const std::size_t col_start, const std::size_t sym_size, const bool whole_line, const bool colorize) { using namespace Ark::literals; @@ -109,7 +109,7 @@ namespace Ark::Diagnostics { fmt::print(os, " |"); - if (sym_size > 0) + if (!whole_line) { // if we have an overflow then we start at the beginning of the line const std::size_t curr_col_start = (overflow == 0) ? col_start : 0; @@ -159,7 +159,7 @@ namespace Ark::Diagnostics fmt::print(os, "At {} @ {}:{}\n", expr, line + 1, column); if (!code.empty()) - makeContext(os, code, line, column, sym_size, colorize); + makeContext(os, code, line, column, sym_size, /* whole_line= */ false, colorize); const auto message_lines = Utils::splitString(message, '\n'); for (const auto& text : message_lines) diff --git a/src/arkreactor/TypeChecker.cpp b/src/arkreactor/TypeChecker.cpp index d177b571e..1ad9b670e 100644 --- a/src/arkreactor/TypeChecker.cpp +++ b/src/arkreactor/TypeChecker.cpp @@ -7,8 +7,6 @@ #include #include -#include - namespace Ark::types { std::string typeListToString(const std::vector& types) @@ -109,7 +107,7 @@ namespace Ark::types } } - [[noreturn]] void generateError(const std::string_view& funcname, const std::vector& contracts, const std::vector& args, std::ostream& os, bool colorize) + void generateError(const std::string_view& funcname, const std::vector& contracts, const std::vector& args, std::ostream& os, bool colorize) { { fmt::dynamic_format_arg_store store; @@ -187,7 +185,5 @@ namespace Ark::types fmt::print(os, "Alternative {}:\n", i + 1); displayContract(contracts[i], sanitizedArgs, os, colorize); } - - throw TypeError(""); } } diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 2111073f4..69139d0aa 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,7 @@ namespace Ark return b; } - types::generateError( + throw types::TypeCheckingError( "tail", { { types::Contract { { types::Typedef("value", ValueType::List) } }, types::Contract { { types::Typedef("value", ValueType::String) } } } }, @@ -68,7 +69,7 @@ namespace Ark return Value(std::string(1, a->stringRef()[0])); } - types::generateError( + throw types::TypeCheckingError( "head", { { types::Contract { { types::Typedef("value", ValueType::List) } }, types::Contract { { types::Typedef("value", ValueType::String) } } } }, @@ -688,7 +689,7 @@ namespace Ark { Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "append", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); @@ -710,7 +711,7 @@ namespace Ark { Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "concat", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); @@ -722,7 +723,7 @@ namespace Ark Value* next = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List || next->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "concat", { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } }, { *list, *next }); @@ -739,7 +740,7 @@ namespace Ark Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "append!", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); @@ -754,7 +755,7 @@ namespace Ark Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "concat", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); @@ -764,7 +765,7 @@ namespace Ark Value* next = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List || next->valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "concat!", { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } }, { *list, *next }); @@ -781,7 +782,7 @@ namespace Ark Value number = *popAndResolveAsPtr(context); if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "pop", { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, { list, number }); @@ -806,7 +807,7 @@ namespace Ark Value number = *popAndResolveAsPtr(context); if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "pop!", { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, { *list, number }); @@ -831,7 +832,7 @@ namespace Ark Value new_value = *popAndResolveAsPtr(context); if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "@=", { { types::Contract { { types::Typedef("list", ValueType::List), @@ -868,7 +869,7 @@ namespace Ark Value new_value = *popAndResolveAsPtr(context); if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "@@=", { { types::Contract { { types::Typedef("list", ValueType::List), @@ -886,7 +887,7 @@ namespace Ark if (!list->list()[static_cast(idx_y)].isIndexable() || (list->list()[static_cast(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String)) - types::generateError( + throw types::TypeCheckingError( "@@=", { { types::Contract { { types::Typedef("list", ValueType::List), @@ -965,7 +966,7 @@ namespace Ark else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String) push(Value(a->string() + b->string()), context); else - types::generateError( + throw types::TypeCheckingError( "+", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } }, types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } }, @@ -978,7 +979,7 @@ namespace Ark Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "-", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *a, *b }); @@ -991,7 +992,7 @@ namespace Ark Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "*", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *a, *b }); @@ -1004,7 +1005,7 @@ namespace Ark Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "/", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *a, *b }); @@ -1067,7 +1068,7 @@ namespace Ark else if (a->valueType() == ValueType::String) push(Value(static_cast(a->string().size())), context); else - types::generateError( + throw types::TypeCheckingError( "len", { { types::Contract { { types::Typedef("value", ValueType::List) } }, types::Contract { { types::Typedef("value", ValueType::String) } } } }, @@ -1084,7 +1085,7 @@ namespace Ark else if (a->valueType() == ValueType::String) push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context); else - types::generateError( + throw types::TypeCheckingError( "empty?", { { types::Contract { { types::Typedef("value", ValueType::List) } }, types::Contract { { types::Typedef("value", ValueType::String) } } } }, @@ -1119,7 +1120,7 @@ namespace Ark Value* const a = popAndResolveAsPtr(context); if (b->valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "assert", { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } }, { *a, *b }); @@ -1134,7 +1135,7 @@ namespace Ark const Value* a = popAndResolveAsPtr(context); if (a->valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "toNumber", { { types::Contract { { types::Typedef("value", ValueType::String) } } } }, { *a }); @@ -1161,7 +1162,7 @@ namespace Ark Value& a = *popAndResolveAsPtr(context); if (b->valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "@", { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } }, types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } }, @@ -1188,7 +1189,7 @@ namespace Ark fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size())); } else - types::generateError( + throw types::TypeCheckingError( "@", { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } }, types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } }, @@ -1206,7 +1207,7 @@ namespace Ark if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number || list.valueType() != ValueType::List) - types::generateError( + throw types::TypeCheckingError( "@@", { { types::Contract { { types::Typedef("src", ValueType::List), @@ -1246,7 +1247,7 @@ namespace Ark { const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) - types::generateError( + throw types::TypeCheckingError( "mod", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *a, *b }); @@ -1258,7 +1259,7 @@ namespace Ark { const Value* a = popAndResolveAsPtr(context); if (a == &m_undefined_value) [[unlikely]] - types::generateError( + throw types::TypeCheckingError( "type", { { types::Contract { { types::Typedef("value", ValueType::Any) } } } }, {}); @@ -1273,7 +1274,7 @@ namespace Ark Value* const field = popAndResolveAsPtr(context); Value* const closure = popAndResolveAsPtr(context); if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String) - types::generateError( + throw types::TypeCheckingError( "hasField", { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } }, { *closure, *field }); @@ -1364,7 +1365,7 @@ namespace Ark if (var->valueType() == ValueType::Number) push(Value(var->number() + secondary_arg), context); else - types::generateError( + throw types::TypeCheckingError( "+", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *var, Value(secondary_arg) }); @@ -1385,7 +1386,7 @@ namespace Ark if (var->valueType() == ValueType::Number) push(Value(var->number() + secondary_arg), context); else - types::generateError( + throw types::TypeCheckingError( "+", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *var, Value(secondary_arg) }); @@ -1406,7 +1407,7 @@ namespace Ark if (var->valueType() == ValueType::Number) push(Value(var->number() - secondary_arg), context); else - types::generateError( + throw types::TypeCheckingError( "-", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *var, Value(secondary_arg) }); @@ -1427,7 +1428,7 @@ namespace Ark if (var->valueType() == ValueType::Number) push(Value(var->number() - secondary_arg), context); else - types::generateError( + throw types::TypeCheckingError( "-", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, { *var, Value(secondary_arg) }); @@ -1542,19 +1543,29 @@ namespace Ark #endif } } + catch (const Error& e) + { + if (fail_with_exception) + { + std::stringstream stream; + backtrace(context, stream, /* colorize= */ false); + // It's important we have an Ark::Error here, as the constructor for NestedError + // does more than just aggregate error messages, hence the code duplication. + throw NestedError(e, stream.str()); + } + else + showBacktraceWithException(Error(e.details()), context); + } catch (const std::exception& e) { if (fail_with_exception) - throw; - - fmt::println("{}", e.what()); - backtrace(context); -#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; -#else - m_exit_code = 1; -#endif + { + std::stringstream stream; + backtrace(context, stream, /* colorize= */ false); + throw NestedError(e, stream.str()); + } + else + showBacktraceWithException(e, context); } catch (...) { @@ -1587,6 +1598,18 @@ namespace Ark throw std::runtime_error(std::string(errorKinds[static_cast(kind)]) + ": " + message + "\n"); } + void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context) + { + fmt::println("{}", e.what()); + backtrace(context); +#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; +#else + m_exit_code = 1; +#endif + } + std::optional VM::findSourceLocation(const std::size_t ip, const std::size_t pp) { std::optional match = std::nullopt; @@ -1596,19 +1619,21 @@ namespace Ark if (location.page_pointer == pp && !match) match = location; - // select the best match: we want to find the location that's nearest our instruction pointer - if (location.page_pointer == pp && match && location.inst_pointer <= ip / 4) + // select the best match: we want to find the location that's nearest our instruction pointer, + // but not equal to it as the IP will always be pointing to the next instruction, + // not yet executed. Thus, the erroneous instruction is the previous one. + if (location.page_pointer == pp && match && location.inst_pointer < ip / 4) match = location; // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip) - if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer > ip / 4)) + if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4)) break; } return match; } - void VM::backtrace(ExecutionContext& context) noexcept + 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; @@ -1619,17 +1644,18 @@ namespace Ark { const auto filename = m_state.m_filenames[maybe_location->filename_id]; - fmt::println("In file {}", filename, maybe_location->line + 1); + fmt::println(os, "In file {}", filename, maybe_location->line + 1); if (Utils::fileExists(filename)) Diagnostics::makeContext( - std::cout, + os, Utils::readFile(filename), maybe_location->line, /* col_start= */ 0, /* sym_size= */ 0, - /* colorize= */ true); - fmt::println(""); + /* whole_line= */ true, + /* colorize= */ colorize); + fmt::println(os, ""); } if (const uint16_t original_frame_count = context.fc; original_frame_count > 1) @@ -1642,7 +1668,7 @@ namespace Ark const auto maybe_call_loc = findSourceLocation(context.ip, context.pp); const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : ""; - fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan))); + fmt::print(os, "[{}] ", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())); if (context.pp != 0) { const uint16_t id = findNearestVariableIdWithValue( @@ -1650,9 +1676,9 @@ namespace Ark context); if (id < m_state.m_symbols.size()) - fmt::println("In function `{}'{}", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)), loc_as_text); + fmt::println(os, "In function `{}'{}", fmt::styled(m_state.m_symbols[id], colorize ? fmt::fg(fmt::color::green) : fmt::text_style()), loc_as_text); else // should never happen - fmt::println("In function `{}'{}", fmt::styled("???", fmt::fg(fmt::color::gold)), loc_as_text); + fmt::println(os, "In function `{}'{}", fmt::styled("???", colorize ? fmt::fg(fmt::color::gold) : fmt::text_style()), loc_as_text); Value* ip; do @@ -1666,33 +1692,35 @@ namespace Ark } else { - fmt::println("In global scope{}", loc_as_text); + fmt::println(os, "In global scope{}", loc_as_text); break; } if (original_frame_count - context.fc > 7) { - fmt::println("..."); + fmt::println(os, "..."); break; } } // display variables values in the current scope - fmt::println("\nCurrent scope variables values:"); + fmt::println(os, "\nCurrent scope variables values:"); for (std::size_t i = 0, size = old_scope.size(); i < size; ++i) { fmt::println( + os, "{} = {}", - fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], fmt::fg(fmt::color::cyan)), + fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), 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, fmt::fg(fmt::color::cyan)), - fmt::styled(saved_pp, fmt::fg(fmt::color::green)), - fmt::styled(saved_sp, fmt::fg(fmt::color::yellow))); + 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/Suites/DiagnosticsSuite.cpp b/tests/unittests/Suites/DiagnosticsSuite.cpp index 969c05f07..6f2dc9f1a 100644 --- a/tests/unittests/Suites/DiagnosticsSuite.cpp +++ b/tests/unittests/Suites/DiagnosticsSuite.cpp @@ -51,9 +51,16 @@ ut::suite<"Diagnostics"> diagnostics_suite = [] { catch (const std::exception& e) { std::string diag = e.what(); - if (diag.find_first_of('\n') != std::string::npos) - diag.erase(diag.find_first_of('\n'), diag.size() - 1); + // because of windows + diag.erase(std::ranges::remove(diag, '\r').begin(), diag.end()); + // remove the directory prefix so that we are environment agnostic + while (diag.find(ARK_TESTS_ROOT) != std::string::npos) + diag.erase(diag.find(ARK_TESTS_ROOT), std::size(ARK_TESTS_ROOT) - 1); ltrim(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 + rtrim(diag); expectOrDiff(data.expected, diag); } }; diff --git a/tests/unittests/Suites/TypeCheckerSuite.cpp b/tests/unittests/Suites/TypeCheckerSuite.cpp index e2adcd718..3d663dc85 100644 --- a/tests/unittests/Suites/TypeCheckerSuite.cpp +++ b/tests/unittests/Suites/TypeCheckerSuite.cpp @@ -205,22 +205,16 @@ ut::suite<"TypeChecker"> type_checker_suite = [] { should("generate error message " + data.stem) = [inputs, contracts, data] { std::stringstream stream; - try - { - Ark::types::generateError( - inputs.front().func, - contracts, - inputs.front().given_args, - stream, - /* colorize= */ false); - expect(fatal(false)) << "generateError should throw an Ark::TypeError"; - } - catch (const Ark::TypeError&) - { - auto result = stream.str(); - rtrim(ltrim(result)); - expectOrDiff(data.expected, result); - } + Ark::types::generateError( + inputs.front().func, + contracts, + inputs.front().given_args, + stream, + /* colorize= */ false); + + auto result = stream.str(); + rtrim(ltrim(result)); + expectOrDiff(data.expected, result); }; }, { .skip_folders = false }); diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.expected index 1d2761818..515306b66 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.expected @@ -1 +1,9 @@ ArityError: Function at page 1 needs 3 arguments, but it received 4 + +In file tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.ark + 1 | (let sum (fun (a b c) + 2 | (+ a b c))) + 3 | + 4 | (await (async sum 1 2 3 4)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_x.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_x.expected index 48a28e1f5..7c764bd43 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_x.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_x.expected @@ -1 +1,7 @@ IndexError: @@= index (x: 1) out of range (inner indexable size: 1) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_x.ark + 1 | (mut a [[1]]) + 2 | (@@= a 0 1 2) + | ^~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_y.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_y.expected index b16c2714d..4cd835b14 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_y.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_y.expected @@ -1 +1,7 @@ IndexError: @@= index (y: 1) out of range (list size: 1) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_at_eq_out_of_range_y.ark + 1 | (mut a [[1]]) + 2 | (@@= a 1 0 2) + | ^~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected index 4fa057182..2ebda34f7 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.expected @@ -1 +1,7 @@ IndexError: @@ index (x: 6) out of range (inner indexable size: 4) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_at_inner_out_of_range.ark + 1 | (let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) + 2 | (print (@@ lst 0 6)) + | ^~~~~~~~~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected index 5a21d49e8..58062c864 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.expected @@ -1 +1,7 @@ IndexError: @@ index (3) out of range (list size: 3) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_at_out_of_range.ark + 1 | (let lst [[0 1 2 3] [4 5 6 7] [8 9 0 1]]) + 2 | (print (@@ lst 3 1)) + | ^~~~~~~~~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_eq_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_eq_out_of_range.expected index 1e4c3122c..8187cc3e7 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_eq_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_eq_out_of_range.expected @@ -1 +1,7 @@ IndexError: @= index (1) out of range (indexable size: 1) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_eq_out_of_range.ark + 1 | (mut a [1]) + 2 | (@= a 1 2) + | ^~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_out_of_range.expected index bb78c23b7..f25f27811 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_out_of_range.expected @@ -1 +1,7 @@ IndexError: -2 out of range [1] (length 1) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_out_of_range.ark + 1 | (let a [1]) + 2 | (@ a -2) + | ^~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/at_str_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/at_str_out_of_range.expected index ddcd3ecd5..de9b9bfbf 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/at_str_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/at_str_out_of_range.expected @@ -1 +1,7 @@ IndexError: 1 out of range "a" (length 1) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/at_str_out_of_range.ark + 1 | (let a "a") + 2 | (@ a 1) + | ^~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/closure_field_wrong_fqn.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/closure_field_wrong_fqn.expected index c4922e9fc..0e8c017f6 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/closure_field_wrong_fqn.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/closure_field_wrong_fqn.expected @@ -1 +1,9 @@ ScopeError: `b:b' isn't in the closure environment: (.a=hello .b=1). A variable in the package might have the same name as 'b:b', and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this + +In file tests/unittests/resources/DiagnosticsSuite/runtime/closure_field_wrong_fqn/b.ark + 7 | (let make (fun (a b) + 8 | (fun (&a &b) ()))) + 9 | (let foo (make "hello" 1)) + 10 | (let c [foo.a foo.b]) + | ^~~~~~~~~~~~~~~~~~~~ + 11 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/db0.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/db0.expected index dc3419cfb..96fa85ff3 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/db0.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/db0.expected @@ -1 +1,6 @@ DivisionByZero: Can not compute expression (/ 5 0) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/db0.ark + 1 | (/ 5 0.0) + | ^~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/del_unbound.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/del_unbound.expected index 10d88753c..091c68ee8 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/del_unbound.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/del_unbound.expected @@ -1 +1,7 @@ ScopeError: Can not delete unbound variable `a' + +In file tests/unittests/resources/DiagnosticsSuite/runtime/del_unbound.ark + 1 | (del a) + | ^~~~~~ + 2 | (let a 0) + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected index efe52bd0a..9bf0aafc1 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected @@ -1 +1,5 @@ string:format: can not format "Hello {}, I'm {}" (1 argument provided) because of argument not found +In file tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.ark + 1 | (string:format "Hello {}, I'm {}" "World") + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected index ac4f897f3..d21978048 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected @@ -1 +1,5 @@ IndexError: list:setAt index (4) out of range (list size: 4) +In file tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.ark + 1 | (list:setAt [0 1 2 3] 4 9) + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected index 463822595..e2debbb90 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected @@ -1 +1,5 @@ list:slice: start position (6) must be less or equal to the end position (5) +In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.ark + 1 | (list:slice [1 2 3 4 5 6 7 8 9] 6 5 1) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected index 0a1bd9d6e..c10218295 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected @@ -1 +1,5 @@ list:slice: end index 12 out of range (length: 9) +In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.ark + 1 | (list:slice [1 2 3 4 5 6 7 8 9] 6 12 1) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected index edf2a5af9..7407fd3a3 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected @@ -1 +1,5 @@ list:slice: start index -1 can not be less than 0 +In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.ark + 1 | (list:slice [1 2 3 4 5 6 7 8 9] -1 5 1) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected index 1067fd6bc..3105d3bfd 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected @@ -1 +1,5 @@ list:slice: step can not be null or negative +In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.ark + 1 | (list:slice [1 2 3 4 5 6 7 8 9] 4 5 -1) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/nil_not_a_function.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/nil_not_a_function.expected index 53ff30d06..f913ab9ca 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/nil_not_a_function.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/nil_not_a_function.expected @@ -1 +1,7 @@ TypeError: nil is not a Function but a Nil + +In file tests/unittests/resources/DiagnosticsSuite/runtime/nil_not_a_function.ark + 1 | (()) + | ^~~ + 2 | (fun (a b) (if 1 2 3)) + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/not_a_closure.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/not_a_closure.expected index b78b3206c..dda9178b0 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/not_a_closure.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/not_a_closure.expected @@ -1 +1,8 @@ TypeError: `a' is a Number, not a Closure, can not get the field `c' from it + +In file tests/unittests/resources/DiagnosticsSuite/runtime/not_a_closure.ark + 1 | (let a 5) + 2 | (let c 5) + 3 | (let b a.c) + | ^~~~~~~~~~ + 4 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/not_callable.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/not_callable.expected index 53ff30d06..fea63f89f 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/not_callable.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/not_callable.expected @@ -1 +1,6 @@ TypeError: nil is not a Function but a Nil + +In file tests/unittests/resources/DiagnosticsSuite/runtime/not_callable.ark + 1 | (()) + | ^~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected index bffe134ab..9d6ee6db1 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected @@ -1 +1,13 @@ ArityError: Function `foo' needs 2 arguments, but it received 1 + +In file tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark + 1 | (let foo (fun (a b) (+ a b))) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | (foo 1) + 3 | + +[2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:1) +[1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:2) + +Current scope variables values: +foo = Function @ 1 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/out_of_range_in_place.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/out_of_range_in_place.expected index 752eda51c..e06632388 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/out_of_range_in_place.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/out_of_range_in_place.expected @@ -1 +1,7 @@ IndexError: pop! index (4) out of range (list size: 3) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/out_of_range_in_place.ark + 1 | (mut a [1 2 3]) + 2 | (pop! a 4) + | ^~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/pop_out_of_range.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/pop_out_of_range.expected index 26ea06e41..41e8fdc4f 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/pop_out_of_range.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/pop_out_of_range.expected @@ -1 +1,7 @@ IndexError: pop index (4) out of range (list size: 3) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/pop_out_of_range.ark + 1 | (let a [1 2 3]) + 2 | (pop a 4) + | ^~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected index 6057877fc..aa2391e8c 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected @@ -1 +1,23 @@ VMError: Maximum recursion depth exceeded. You could consider rewriting your function `foo' to make use of tail-call optimization. + +In file tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark + 1 | (let foo (fun (a) { + 2 | # hack to avoid getting TCO-ed + 3 | (let tmp (foo (+ a 1))) + | ^~~~~~~~~~~~~~~~~~~~~~ + 4 | tmp + 5 | })) + +[2047] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2046] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2045] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2044] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2043] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2042] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2041] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +[2040] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) +... + +Current scope variables values: +foo = Function @ 1 +a = 2045 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/set_unbound.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/set_unbound.expected index c6dc34850..cd0be8059 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/set_unbound.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/set_unbound.expected @@ -1 +1,7 @@ ScopeError: Unbound variable `a', can not change its value to 5 + +In file tests/unittests/resources/DiagnosticsSuite/runtime/set_unbound.ark + 1 | (set a 5) + | ^~~~~~~~ + 2 | (let a 0) + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected index f8bfa6dde..29c7ab01d 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected @@ -1 +1,5 @@ string:removeAt: index 5 out of range (length: 3) +In file tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.ark + 1 | (string:removeAt "abc" 5) + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected index bc7e8bd17..ec09e88c2 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected @@ -1 +1,5 @@ IndexError: string:setAt index (4) out of range (string size: 4) +In file tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.ark + 1 | (string:setAt "0123" 4 "9") + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected index 39f3c0371..3750fc988 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected @@ -1 +1,13 @@ ArityError: Function `foo' needs 2 arguments, but it received 3 + +In file tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark + 1 | (let foo (fun (a b) (+ a b))) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | (foo 1 2 3) + 3 | + +[2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:1) +[1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:2) + +Current scope variables values: +foo = Function @ 1 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/unbound_var.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/unbound_var.expected index 7ca84a57a..fd0f8185c 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/unbound_var.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/unbound_var.expected @@ -1 +1,7 @@ ScopeError: Unbound variable `a' + +In file tests/unittests/resources/DiagnosticsSuite/runtime/unbound_var.ark + 1 | (let b a) + | ^~~~~~~~ + 2 | (let a 5) + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/unknown_field.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/unknown_field.expected index 9d0b8e79b..0afa2e325 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/unknown_field.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/unknown_field.expected @@ -1 +1,9 @@ ScopeError: `c' isn't in the closure environment: (.a=5) + +In file tests/unittests/resources/DiagnosticsSuite/runtime/unknown_field.ark + 1 | (let a 5) + 2 | (let c 6) + 3 | (let b (fun (&a) ())) + 4 | (print b.c) + | ^~~~~~~~~~ + 5 | From 718abfdf5cfb24a71971f417ef2146b483a29be6 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Tue, 22 Apr 2025 18:40:49 +0200 Subject: [PATCH 4/7] feat(tests, vm): adding tests for type checking error messages --- .../IntermediateRepresentation/Entity.hpp | 1 + include/Ark/Exceptions.hpp | 18 +-------- include/Ark/TypeChecker.hpp | 5 ++- lib/modules | 2 +- .../IntermediateRepresentation/IRCompiler.cpp | 1 + src/arkreactor/VM/VM.cpp | 10 ++--- tests/unittests/Suites/DiagnosticsSuite.cpp | 38 +++++++++++++------ tests/unittests/Suites/ParserSuite.cpp | 2 +- tests/unittests/TestsHelper.cpp | 23 ++++++++++- tests/unittests/TestsHelper.hpp | 4 +- .../typeChecking/add_num_str.ark | 1 + .../typeChecking/add_num_str.expected | 11 ++++++ .../typeChecking/append_in_place_num_num.ark | 2 + .../append_in_place_num_num.expected | 8 ++++ .../typeChecking/append_num_num.ark | 1 + .../typeChecking/append_num_num.expected | 7 ++++ .../typeChecking/assert_num_num.ark | 1 + .../typeChecking/assert_num_num.expected | 8 ++++ .../at_at_eq_list_num_num_num.ark | 1 + .../at_at_eq_list_num_num_num.expected | 15 ++++++++ .../typeChecking/at_at_eq_num_num_num_num.ark | 1 + .../at_at_eq_num_num_num_num.expected | 10 +++++ .../typeChecking/at_at_num_num_num.ark | 1 + .../typeChecking/at_at_num_num_num.expected | 9 +++++ .../typeChecking/at_eq_num_num_num.ark | 1 + .../typeChecking/at_eq_num_num_num.expected | 13 +++++++ .../typeChecking/at_list_str.ark | 1 + .../typeChecking/at_list_str.expected | 11 ++++++ .../typeChecking/at_num_num.ark | 1 + .../typeChecking/at_num_num.expected | 11 ++++++ .../typeChecking/concat_in_place_list_num.ark | 2 + .../concat_in_place_list_num.expected | 9 +++++ .../typeChecking/concat_in_place_num_num.ark | 2 + .../concat_in_place_num_num.expected | 8 ++++ .../typeChecking/concat_list_num.ark | 1 + .../typeChecking/concat_list_num.expected | 8 ++++ .../typeChecking/concat_num_num.ark | 1 + .../typeChecking/concat_num_num.expected | 7 ++++ .../typeChecking/decrement_str_num.ark | 2 + .../typeChecking/decrement_str_num.expected | 9 +++++ .../typeChecking/div_str_num.ark | 1 + .../typeChecking/div_str_num.expected | 8 ++++ .../typeChecking/empty_num.ark | 1 + .../typeChecking/empty_num.expected | 9 +++++ .../typeChecking/hasfield_num_str.ark | 1 + .../typeChecking/hasfield_num_str.expected | 8 ++++ .../typeChecking/head_num.ark | 1 + .../typeChecking/head_num.expected | 9 +++++ .../typeChecking/increment_str_num.ark | 2 + .../typeChecking/increment_str_num.expected | 9 +++++ .../DiagnosticsSuite/typeChecking/len_num.ark | 1 + .../typeChecking/len_num.expected | 9 +++++ .../typeChecking/mod_str_str.ark | 1 + .../typeChecking/mod_str_str.expected | 8 ++++ .../typeChecking/mul_str_num.ark | 1 + .../typeChecking/mul_str_num.expected | 8 ++++ .../typeChecking/pop_in_place_num_num.ark | 1 + .../pop_in_place_num_num.expected | 8 ++++ .../typeChecking/pop_num_num.ark | 1 + .../typeChecking/pop_num_num.expected | 8 ++++ .../typeChecking/sub_str_str.ark | 1 + .../typeChecking/sub_str_str.expected | 8 ++++ .../typeChecking/tail_num.ark | 1 + .../typeChecking/tail_num.expected | 9 +++++ .../typeChecking/tonumber_num.ark | 1 + .../typeChecking/tonumber_num.expected | 7 ++++ 66 files changed, 350 insertions(+), 39 deletions(-) create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.expected create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.expected diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 90a5d60e8..89e200fb1 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/include/Ark/Exceptions.hpp b/include/Ark/Exceptions.hpp index 8fcea3cc2..9d56d36dd 100644 --- a/include/Ark/Exceptions.hpp +++ b/include/Ark/Exceptions.hpp @@ -36,24 +36,10 @@ namespace Ark std::runtime_error(message) {} - [[nodiscard]] virtual std::string details() const + [[nodiscard]] virtual std::string details(bool colorize [[maybe_unused]]) const { return what(); } - - Error& colorize(const bool toggle) noexcept - { - m_colorize = toggle; - return *this; - } - - [[nodiscard]] bool shouldColorize() const noexcept - { - return m_colorize; - } - - private: - bool m_colorize = true; }; /** @@ -85,7 +71,7 @@ namespace Ark public: NestedError(const Error& e, const std::string& details) : Error("NestedError"), - m_details(Error(e).colorize(false).details() + "\n" + details) + m_details(e.details(/* colorize= */ false) + "\n" + details) {} NestedError(const std::exception& e, const std::string& details) : diff --git a/include/Ark/TypeChecker.hpp b/include/Ark/TypeChecker.hpp index 2ca2fc9c6..e00d4796f 100644 --- a/include/Ark/TypeChecker.hpp +++ b/include/Ark/TypeChecker.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -111,10 +112,10 @@ namespace Ark::types m_passed_args(args) {} - [[nodiscard]] std::string details() const override + [[nodiscard]] std::string details(const bool colorize) const override { std::stringstream stream; - generateError(m_funcname, m_contracts, m_passed_args, stream, shouldColorize()); + generateError(m_funcname, m_contracts, m_passed_args, stream, colorize); return stream.str(); } diff --git a/lib/modules b/lib/modules index f2463ceac..a16d1a5c4 160000 --- a/lib/modules +++ b/lib/modules @@ -1 +1 @@ -Subproject commit f2463ceacefe5dec8fd6eec2359de1f530693bfd +Subproject commit a16d1a5c4b30ad6d799f1d92c0dd09c42d72cfc8 diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp index 277b2c052..0140392b7 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 69139d0aa..ddf183b8b 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -756,7 +756,7 @@ namespace Ark if (list->valueType() != ValueType::List) throw types::TypeCheckingError( - "concat", + "concat!", { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); @@ -842,7 +842,7 @@ namespace Ark { types::Typedef("string", ValueType::String), types::Typedef("index", ValueType::Number), types::Typedef("char", ValueType::String) } } } }, - { *list, number }); + { *list, number, new_value }); const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size(); long idx = static_cast(number.number()); @@ -876,7 +876,7 @@ namespace Ark types::Typedef("x", ValueType::Number), types::Typedef("y", ValueType::Number), types::Typedef("new_value", ValueType::Any) } } } }, - { *list, x, y }); + { *list, x, y, new_value }); long idx_y = static_cast(x.number()); idx_y = idx_y < 0 ? static_cast(list->list().size()) + idx_y : idx_y; @@ -899,7 +899,7 @@ namespace Ark types::Typedef("x", ValueType::Number), types::Typedef("y", ValueType::Number), types::Typedef("char", ValueType::String) } } } }, - { *list, x, y }); + { *list, x, y, new_value }); const bool is_list = list->list()[static_cast(idx_y)].valueType() == ValueType::List; const std::size_t size = @@ -1554,7 +1554,7 @@ namespace Ark throw NestedError(e, stream.str()); } else - showBacktraceWithException(Error(e.details()), context); + showBacktraceWithException(Error(e.details(/* colorize= */ true)), context); } catch (const std::exception& e) { diff --git a/tests/unittests/Suites/DiagnosticsSuite.cpp b/tests/unittests/Suites/DiagnosticsSuite.cpp index 6f2dc9f1a..e6be93a65 100644 --- a/tests/unittests/Suites/DiagnosticsSuite.cpp +++ b/tests/unittests/Suites/DiagnosticsSuite.cpp @@ -25,7 +25,7 @@ ut::suite<"Diagnostics"> diagnostics_suite = [] { } catch (const Ark::CodeError& e) { - std::string diag = sanitizeError(e, /* remove_in_file_line= */ true); + std::string diag = sanitizeCodeError(e, /* remove_in_file_line= */ true); rtrim(diag); expectOrDiff(data.expected, diag); } @@ -50,17 +50,31 @@ ut::suite<"Diagnostics"> diagnostics_suite = [] { } catch (const std::exception& e) { - std::string diag = e.what(); - // because of windows - diag.erase(std::ranges::remove(diag, '\r').begin(), diag.end()); - // remove the directory prefix so that we are environment agnostic - while (diag.find(ARK_TESTS_ROOT) != std::string::npos) - diag.erase(diag.find(ARK_TESTS_ROOT), std::size(ARK_TESTS_ROOT) - 1); - ltrim(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 - rtrim(diag); + std::string diag = sanitizeRuntimeError(e); + expectOrDiff(data.expected, diag); + } + }; + }); + + iterTestFiles( + "DiagnosticsSuite/typeChecking", + [](TestData&& data) { + Ark::State state({ lib_path }); + + should("compile without error typeChecking/" + data.stem) = [&] { + expect(mut(state).doFile(data.path, features)); + }; + + should("generate an error at runtime (typeChecking) in " + data.stem) = [&] { + try + { + Ark::VM vm(state); + vm.run(/* fail_with_exception= */ true); + expect(0 == 1); // we shouldn't be here, an error should be generated + } + catch (const std::exception& e) + { + std::string diag = sanitizeRuntimeError(e); expectOrDiff(data.expected, diag); } }; diff --git a/tests/unittests/Suites/ParserSuite.cpp b/tests/unittests/Suites/ParserSuite.cpp index c728eb8bf..8a5c2cf3a 100644 --- a/tests/unittests/Suites/ParserSuite.cpp +++ b/tests/unittests/Suites/ParserSuite.cpp @@ -78,7 +78,7 @@ ut::suite<"Parser"> parser_suite = [] { catch (const Ark::CodeError& e) { should("output the same error message (" + data.stem + ")") = [&] { - std::string tested = sanitizeError(e); + std::string tested = sanitizeCodeError(e); ltrim(rtrim(tested)); expectOrDiff(data.expected, tested); }; diff --git a/tests/unittests/TestsHelper.cpp b/tests/unittests/TestsHelper.cpp index d3ccdb5f7..b141ef0c3 100644 --- a/tests/unittests/TestsHelper.cpp +++ b/tests/unittests/TestsHelper.cpp @@ -44,7 +44,7 @@ std::string getResourcePath(const std::string& folder) return (ARK_TESTS_ROOT "tests/unittests/resources/") + folder; } -std::string sanitizeError(const Ark::CodeError& e, const bool remove_in_file_line) +std::string sanitizeCodeError(const Ark::CodeError& e, const bool remove_in_file_line) { std::stringstream stream; Ark::Diagnostics::generate(e, stream, /* colorize= */ false); @@ -60,6 +60,27 @@ std::string sanitizeError(const Ark::CodeError& e, const bool remove_in_file_lin return diag; } +std::string sanitizeRuntimeError(const std::exception& e) +{ + // std::replace(s.begin(), s.end(), '\\', '/'); + std::string diag = e.what(); + + // because of windows + diag.erase(std::ranges::remove(diag, '\r').begin(), diag.end()); + std::ranges::replace(diag, '\\', '/'); + + // remove the directory prefix so that we are environment agnostic + while (diag.find(ARK_TESTS_ROOT) != std::string::npos) + diag.erase(diag.find(ARK_TESTS_ROOT), std::size(ARK_TESTS_ROOT) - 1); + ltrim(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 + rtrim(diag); + + return 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 bffe379e9..828d0f180 100644 --- a/tests/unittests/TestsHelper.hpp +++ b/tests/unittests/TestsHelper.hpp @@ -76,7 +76,9 @@ inline std::string& rtrim(std::string& s) return s; } -std::string sanitizeError(const Ark::CodeError& e, bool remove_in_file_line = false); +std::string sanitizeCodeError(const Ark::CodeError& e, bool remove_in_file_line = false); + +std::string sanitizeRuntimeError(const std::exception& e); void expectOrDiff(const std::string& expected, const std::string& received); diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.ark new file mode 100644 index 000000000..c1524bdcd --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.ark @@ -0,0 +1 @@ +(+ 1 "2") diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.expected new file mode 100644 index 000000000..ab4bc1ebf --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.expected @@ -0,0 +1,11 @@ +Function + expected 2 arguments + -> a (Number) + -> b (Number) was of type String +Alternative 2: + -> a (String) was of type Number + -> b (String) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/add_num_str.ark + 1 | (+ 1 "2") + | ^~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.ark new file mode 100644 index 000000000..768eb4105 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.ark @@ -0,0 +1,2 @@ +(mut L 1) +(append! L 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.expected new file mode 100644 index 000000000..c05253be3 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.expected @@ -0,0 +1,8 @@ +Function append! expected 1 argument + -> list (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/append_in_place_num_num.ark + 1 | (mut L 1) + 2 | (append! L 5) + | ^~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.ark new file mode 100644 index 000000000..f3a4e671b --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.ark @@ -0,0 +1 @@ +(append 1 3) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.expected new file mode 100644 index 000000000..b93da8c85 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.expected @@ -0,0 +1,7 @@ +Function append expected 1 argument + -> list (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/append_num_num.ark + 1 | (append 1 3) + | ^~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.ark new file mode 100644 index 000000000..4103d60d8 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.ark @@ -0,0 +1 @@ +(assert 1 2) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.expected new file mode 100644 index 000000000..40d0a0595 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.expected @@ -0,0 +1,8 @@ +Function assert expected 2 arguments + -> expr (any) + -> message (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/assert_num_num.ark + 1 | (assert 1 2) + | ^~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.ark new file mode 100644 index 000000000..3e441ff17 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.ark @@ -0,0 +1 @@ +(@@= [1 2 3 4] 0 1 4) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.expected new file mode 100644 index 000000000..3bcb25e3a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.expected @@ -0,0 +1,15 @@ +Function @@= expected 4 arguments + -> list (List) + -> x (Number) + -> y (Number) + -> new_value (any) +Alternative 2: + -> string (String) was of type List + -> x (Number) + -> y (Number) + -> char (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_list_num_num_num.ark + 1 | (@@= [1 2 3 4] 0 1 4) + | ^~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.ark new file mode 100644 index 000000000..73a9386b0 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.ark @@ -0,0 +1 @@ +(@@= 1 2 3 4) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.expected new file mode 100644 index 000000000..9c067ba47 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.expected @@ -0,0 +1,10 @@ +Function @@= expected 4 arguments + -> list (List) was of type Number + -> x (Number) + -> y (Number) + -> new_value (any) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_eq_num_num_num_num.ark + 1 | (@@= 1 2 3 4) + | ^~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.ark new file mode 100644 index 000000000..2746844ea --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.ark @@ -0,0 +1 @@ +(@@ 1 2 3) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.expected new file mode 100644 index 000000000..e04db3625 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.expected @@ -0,0 +1,9 @@ +Function @@ expected 3 arguments + -> src (List) was of type Number + -> y (Number) + -> x (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_at_num_num_num.ark + 1 | (@@ 1 2 3) + | ^~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.ark new file mode 100644 index 000000000..64500a81a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.ark @@ -0,0 +1 @@ +(@= 1 2 3) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.expected new file mode 100644 index 000000000..7f59e744c --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.expected @@ -0,0 +1,13 @@ +Function @= expected 3 arguments + -> list (List) was of type Number + -> index (Number) + -> new_value (any) +Alternative 2: + -> string (String) was of type Number + -> index (Number) + -> char (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_eq_num_num_num.ark + 1 | (@= 1 2 3) + | ^~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.ark new file mode 100644 index 000000000..12ae1f645 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.ark @@ -0,0 +1 @@ +(@ [] "1") diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.expected new file mode 100644 index 000000000..caf6153d8 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.expected @@ -0,0 +1,11 @@ +Function @ expected 2 arguments + -> src (List) + -> idx (Number) was of type String +Alternative 2: + -> src (String) was of type List + -> idx (Number) was of type String + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_list_str.ark + 1 | (@ [] "1") + | ^~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.ark new file mode 100644 index 000000000..568e7e27a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.ark @@ -0,0 +1 @@ +(@ 1 2) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.expected new file mode 100644 index 000000000..2e4f82ee2 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.expected @@ -0,0 +1,11 @@ +Function @ expected 2 arguments + -> src (List) was of type Number + -> idx (Number) +Alternative 2: + -> src (String) was of type Number + -> idx (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/at_num_num.ark + 1 | (@ 1 2) + | ^~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.ark new file mode 100644 index 000000000..5f37c2395 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.ark @@ -0,0 +1,2 @@ +(mut L []) +(concat! L 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.expected new file mode 100644 index 000000000..2cfbff0b5 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.expected @@ -0,0 +1,9 @@ +Function concat! expected 2 arguments + -> dst (List) + -> src (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_list_num.ark + 1 | (mut L []) + 2 | (concat! L 5) + | ^~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.ark new file mode 100644 index 000000000..ed73ee649 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.ark @@ -0,0 +1,2 @@ +(mut L 1) +(concat! L 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.expected new file mode 100644 index 000000000..6af7c1549 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.expected @@ -0,0 +1,8 @@ +Function concat! expected 1 argument + -> list (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_in_place_num_num.ark + 1 | (mut L 1) + 2 | (concat! L 5) + | ^~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.ark new file mode 100644 index 000000000..21b6d946d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.ark @@ -0,0 +1 @@ +(concat [1] 3) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.expected new file mode 100644 index 000000000..a3161617a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.expected @@ -0,0 +1,8 @@ +Function concat expected 2 arguments + -> dst (List) + -> src (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_list_num.ark + 1 | (concat [1] 3) + | ^~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.ark new file mode 100644 index 000000000..0fb28f139 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.ark @@ -0,0 +1 @@ +(concat 1 3) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.expected new file mode 100644 index 000000000..807d3342d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.expected @@ -0,0 +1,7 @@ +Function concat expected 1 argument + -> list (List) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/concat_num_num.ark + 1 | (concat 1 3) + | ^~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.ark new file mode 100644 index 000000000..953769fb4 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.ark @@ -0,0 +1,2 @@ +(mut n "1") +(set n (- n 1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.expected new file mode 100644 index 000000000..e7e4f4427 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.expected @@ -0,0 +1,9 @@ +Function - expected 2 arguments + -> a (Number) was of type String + -> b (Number) was of type Function + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/decrement_str_num.ark + 1 | (mut n "1") + 2 | (set n (- n 1)) + | ^~~~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.ark new file mode 100644 index 000000000..16f945c9a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.ark @@ -0,0 +1 @@ +(/ "3" 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.expected new file mode 100644 index 000000000..60fb7dbbf --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.expected @@ -0,0 +1,8 @@ +Function / expected 2 arguments + -> a (Number) was of type String + -> b (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/div_str_num.ark + 1 | (/ "3" 5) + | ^~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.ark new file mode 100644 index 000000000..41163cce7 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.ark @@ -0,0 +1 @@ +(empty? 1) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.expected new file mode 100644 index 000000000..36e0aa47a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.expected @@ -0,0 +1,9 @@ +Function empty? expected 1 argument + -> value (List) was of type Number +Alternative 2: + -> value (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/empty_num.ark + 1 | (empty? 1) + | ^~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.ark new file mode 100644 index 000000000..6fc270e65 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.ark @@ -0,0 +1 @@ +(hasField 1 "c") diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.expected new file mode 100644 index 000000000..d25fe8dc1 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.expected @@ -0,0 +1,8 @@ +Function hasField expected 2 arguments + -> closure (Closure) was of type Number + -> field (String) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/hasfield_num_str.ark + 1 | (hasField 1 "c") + | ^~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.ark new file mode 100644 index 000000000..57e3f585a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.ark @@ -0,0 +1 @@ +(head 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.expected new file mode 100644 index 000000000..12b6d6307 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.expected @@ -0,0 +1,9 @@ +Function head expected 1 argument + -> value (List) was of type Number +Alternative 2: + -> value (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/head_num.ark + 1 | (head 5) + | ^~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.ark new file mode 100644 index 000000000..587def954 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.ark @@ -0,0 +1,2 @@ +(mut n "1") +(set n (+ n 1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.expected new file mode 100644 index 000000000..fa22a3571 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.expected @@ -0,0 +1,9 @@ +Function + expected 2 arguments + -> a (Number) was of type String + -> b (Number) was of type Function + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/increment_str_num.ark + 1 | (mut n "1") + 2 | (set n (+ n 1)) + | ^~~~~~~~~~~~~~ + 3 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.ark new file mode 100644 index 000000000..7ee48e0b0 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.ark @@ -0,0 +1 @@ +(len 1) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.expected new file mode 100644 index 000000000..153c2e786 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.expected @@ -0,0 +1,9 @@ +Function len expected 1 argument + -> value (List) was of type Number +Alternative 2: + -> value (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/len_num.ark + 1 | (len 1) + | ^~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.ark new file mode 100644 index 000000000..cdcf33872 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.ark @@ -0,0 +1 @@ +(mod "1" "2") diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.expected new file mode 100644 index 000000000..1a3c8c44a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.expected @@ -0,0 +1,8 @@ +Function mod expected 2 arguments + -> a (Number) was of type String + -> b (Number) was of type String + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/mod_str_str.ark + 1 | (mod "1" "2") + | ^~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.ark new file mode 100644 index 000000000..c7f9d3233 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.ark @@ -0,0 +1 @@ +(* "3" 4) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.expected new file mode 100644 index 000000000..34ecf187d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.expected @@ -0,0 +1,8 @@ +Function * expected 2 arguments + -> a (Number) was of type String + -> b (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/mul_str_num.ark + 1 | (* "3" 4) + | ^~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.ark new file mode 100644 index 000000000..4bcba1dc4 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.ark @@ -0,0 +1 @@ +(pop! 5 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.expected new file mode 100644 index 000000000..ab007cd5a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.expected @@ -0,0 +1,8 @@ +Function pop! expected 2 arguments + -> list (List) was of type Number + -> index (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_in_place_num_num.ark + 1 | (pop! 5 5) + | ^~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.ark new file mode 100644 index 000000000..0eb34ba58 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.ark @@ -0,0 +1 @@ +(pop 5 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.expected new file mode 100644 index 000000000..f4ba5e72b --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.expected @@ -0,0 +1,8 @@ +Function pop expected 2 arguments + -> list (List) was of type Number + -> index (Number) + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/pop_num_num.ark + 1 | (pop 5 5) + | ^~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.ark new file mode 100644 index 000000000..c059c474a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.ark @@ -0,0 +1 @@ +(- "1" "2") diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.expected new file mode 100644 index 000000000..6bd101655 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.expected @@ -0,0 +1,8 @@ +Function - expected 2 arguments + -> a (Number) was of type String + -> b (Number) was of type String + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/sub_str_str.ark + 1 | (- "1" "2") + | ^~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.ark new file mode 100644 index 000000000..b3742371b --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.ark @@ -0,0 +1 @@ +(tail 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.expected new file mode 100644 index 000000000..9923e523a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.expected @@ -0,0 +1,9 @@ +Function tail expected 1 argument + -> value (List) was of type Number +Alternative 2: + -> value (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/tail_num.ark + 1 | (tail 5) + | ^~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.ark new file mode 100644 index 000000000..0d15c268a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.ark @@ -0,0 +1 @@ +(toNumber 5) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.expected new file mode 100644 index 000000000..434d2d293 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.expected @@ -0,0 +1,7 @@ +Function toNumber expected 1 argument + -> value (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/tonumber_num.ark + 1 | (toNumber 5) + | ^~~~~~~~~~~ + 2 | From aecaa9e3cfad3b1e19bb36e2c8179910001f3b01 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Thu, 24 Apr 2025 13:54:31 +0200 Subject: [PATCH 5/7] chore(bytecodereader): fix instruction location table display when it's empty --- src/arkreactor/Compiler/BytecodeReader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index 68fea6eeb..3f07dcf65 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -396,7 +396,7 @@ namespace Ark if (showVal || segment == BytecodeSegment::HeadersOnly) fmt::println("{} (length: {})", fmt::styled("Instruction locations table", fmt::fg(fmt::color::cyan)), sliceSize); - if (showVal) + if (showVal && size > 0) fmt::println(" PP, IP"); for (std::size_t j = 0; j < size; ++j) @@ -408,6 +408,9 @@ namespace Ark if (showVal) fmt::println("{:>3},{:>3} -> {}:{}", location.page_pointer, location.inst_pointer, files.filenames[location.filename_id], location.line); } + + if (showVal) + fmt::print("\n"); } const auto stringify_value = [](const Value& val) -> std::string { From 59ec3075859671ba659120b8bc6e4242bf445849 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Thu, 24 Apr 2025 14:06:32 +0200 Subject: [PATCH 6/7] chore(vm): adding some breathing room in error messages, to separate the error from the context --- include/Ark/Exceptions.hpp | 16 ++++++++++++---- src/arkreactor/VM/VM.cpp | 5 ++++- .../runtime/fmt_arg_not_found.expected | 1 + .../runtime/list_set_at.expected | 1 + .../runtime/list_slice_end_start.expected | 1 + .../runtime/list_slice_past_end.expected | 1 + .../runtime/list_slice_start_less_0.expected | 1 + .../runtime/list_slice_step_null.expected | 1 + .../runtime/str_remove_out_of_bound.expected | 1 + .../runtime/string_set_at.expected | 1 + 10 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/Ark/Exceptions.hpp b/include/Ark/Exceptions.hpp index 9d56d36dd..a494124c5 100644 --- a/include/Ark/Exceptions.hpp +++ b/include/Ark/Exceptions.hpp @@ -71,13 +71,21 @@ namespace Ark public: NestedError(const Error& e, const std::string& details) : Error("NestedError"), - m_details(e.details(/* colorize= */ false) + "\n" + details) - {} + m_details(e.details(/* colorize= */ false)) + { + if (!m_details.empty() && m_details.back() != '\n') + m_details += '\n'; + m_details += "\n" + details; + } NestedError(const std::exception& e, const std::string& details) : Error("NestedError"), - m_details(e.what() + ("\n" + details)) - {} + m_details(e.what()) + { + if (!m_details.empty() && m_details.back() != '\n') + m_details += '\n'; + m_details += "\n" + details; + } [[nodiscard]] const char* what() const noexcept override { diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index ddf183b8b..770c428a0 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1600,7 +1600,10 @@ namespace Ark void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context) { - fmt::println("{}", e.what()); + std::string text = e.what(); + if (!text.empty() && text.back() != '\n') + text += '\n'; + fmt::println("{}", text); backtrace(context); #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected index 9bf0aafc1..7ac3592ca 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.expected @@ -1,4 +1,5 @@ string:format: can not format "Hello {}, I'm {}" (1 argument provided) because of argument not found + In file tests/unittests/resources/DiagnosticsSuite/runtime/fmt_arg_not_found.ark 1 | (string:format "Hello {}, I'm {}" "World") | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected index d21978048..70c4b10f9 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected @@ -1,4 +1,5 @@ IndexError: list:setAt index (4) out of range (list size: 4) + In file tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.ark 1 | (list:setAt [0 1 2 3] 4 9) | ^~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected index e2debbb90..9aa6d5d3a 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.expected @@ -1,4 +1,5 @@ list:slice: start position (6) must be less or equal to the end position (5) + In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_end_start.ark 1 | (list:slice [1 2 3 4 5 6 7 8 9] 6 5 1) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected index c10218295..6292b4d90 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.expected @@ -1,4 +1,5 @@ list:slice: end index 12 out of range (length: 9) + In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_past_end.ark 1 | (list:slice [1 2 3 4 5 6 7 8 9] 6 12 1) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected index 7407fd3a3..90b54eab8 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.expected @@ -1,4 +1,5 @@ list:slice: start index -1 can not be less than 0 + In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_start_less_0.ark 1 | (list:slice [1 2 3 4 5 6 7 8 9] -1 5 1) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected index 3105d3bfd..7162e4a90 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.expected @@ -1,4 +1,5 @@ list:slice: step can not be null or negative + In file tests/unittests/resources/DiagnosticsSuite/runtime/list_slice_step_null.ark 1 | (list:slice [1 2 3 4 5 6 7 8 9] 4 5 -1) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected index 29c7ab01d..42790fa93 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.expected @@ -1,4 +1,5 @@ string:removeAt: index 5 out of range (length: 3) + In file tests/unittests/resources/DiagnosticsSuite/runtime/str_remove_out_of_bound.ark 1 | (string:removeAt "abc" 5) | ^~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected index ec09e88c2..423015a56 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected @@ -1,4 +1,5 @@ IndexError: string:setAt index (4) out of range (string size: 4) + In file tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.ark 1 | (string:setAt "0123" 4 "9") | ^~~~~~~~~~~~~~~~~~~~~~~~~~ From 46f10c23d3d25aa921ce31e81d10771288e70ac7 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Thu, 24 Apr 2025 17:48:09 +0200 Subject: [PATCH 7/7] feat(vm): compressing identical traces when displaying the stacktrace of an error --- include/Ark/TypeChecker.hpp | 1 + src/arkreactor/VM/VM.cpp | 33 ++++++++++++++----- .../runtime/not_enough_args.expected | 4 +-- .../runtime/recursion_depth.expected | 9 +---- .../runtime/too_many_args.expected | 4 +-- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/include/Ark/TypeChecker.hpp b/include/Ark/TypeChecker.hpp index e00d4796f..466a0c94d 100644 --- a/include/Ark/TypeChecker.hpp +++ b/include/Ark/TypeChecker.hpp @@ -17,6 +17,7 @@ #include #include +#include #include namespace Ark::types diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 770c428a0..9875ce694 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -1661,27 +1661,44 @@ namespace Ark fmt::println(os, ""); } - if (const uint16_t original_frame_count = context.fc; original_frame_count > 1) + if (context.fc > 1) { // display call stack trace const ScopeView old_scope = context.locals.back(); + std::string previous_trace; + std::size_t displayed_traces = 0; + std::size_t consecutive_similar_traces = 0; + while (context.fc != 0) { const auto maybe_call_loc = findSourceLocation(context.ip, context.pp); const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : ""; - fmt::print(os, "[{}] ", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())); if (context.pp != 0) { const uint16_t id = findNearestVariableIdWithValue( Value(static_cast(context.pp)), context); + const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???"; - if (id < m_state.m_symbols.size()) - fmt::println(os, "In function `{}'{}", fmt::styled(m_state.m_symbols[id], colorize ? fmt::fg(fmt::color::green) : fmt::text_style()), loc_as_text); - else // should never happen - fmt::println(os, "In function `{}'{}", fmt::styled("???", colorize ? fmt::fg(fmt::color::gold) : fmt::text_style()), loc_as_text); + if (func_name + loc_as_text != previous_trace) + { + fmt::println( + os, + "[{:4}] In function `{}'{}", + fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), + fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()), + loc_as_text); + previous_trace = func_name + loc_as_text; + ++displayed_traces; + consecutive_similar_traces = 0; + } + else if (consecutive_similar_traces == 0) + { + fmt::println("{0:^{1}}", "...", 21 + func_name.size() + loc_as_text.size()); + ++consecutive_similar_traces; + } Value* ip; do @@ -1695,11 +1712,11 @@ namespace Ark } else { - fmt::println(os, "In global scope{}", loc_as_text); + fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text); break; } - if (original_frame_count - context.fc > 7) + if (displayed_traces > 7) { fmt::println(os, "..."); break; diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected index 9d6ee6db1..38e655fac 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.expected @@ -6,8 +6,8 @@ In file tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark 2 | (foo 1) 3 | -[2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:1) -[1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:2) +[ 2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:1) +[ 1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/not_enough_args.ark:2) Current scope variables values: foo = Function @ 1 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected index aa2391e8c..55bae592a 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected @@ -9,14 +9,7 @@ In file tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark 5 | })) [2047] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2046] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2045] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2044] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2043] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2042] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2041] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -[2040] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:3) -... +[ 1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:7) Current scope variables values: foo = Function @ 1 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected index 3750fc988..d618a3b96 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.expected @@ -6,8 +6,8 @@ In file tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark 2 | (foo 1 2 3) 3 | -[2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:1) -[1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:2) +[ 2] In function `foo' (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:1) +[ 1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/too_many_args.ark:2) Current scope variables values: foo = Function @ 1