diff --git a/CHANGELOG.md b/CHANGELOG.md index bb78c3fd..7471e8bc 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/CMakeLists.txt b/CMakeLists.txt index 6cabb126..03411465 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/Compiler/BytecodeReader.hpp b/include/Ark/Compiler/BytecodeReader.hpp index 48d4b288..6afe0d5b 100644 --- a/include/Ark/Compiler/BytecodeReader.hpp +++ b/include/Ark/Compiler/BytecodeReader.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace Ark { @@ -31,7 +32,8 @@ namespace Ark Symbols, Values, Code, - HeadersOnly + HeadersOnly, + InstructionLocation }; struct Version @@ -55,6 +57,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 +145,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 9ed41589..ae771888 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 b27b5bcf..89e200fb 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 * */ @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -62,12 +63,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 235f69d0..2a6d47e4 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 00000000..801f6a46 --- /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 84a1fa85..19987275 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 @@ -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/Exceptions.hpp b/include/Ark/Exceptions.hpp index 60d0896f..a494124c 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,11 @@ namespace Ark explicit Error(const std::string& message) : std::runtime_error(message) {} + + [[nodiscard]] virtual std::string details(bool colorize [[maybe_unused]]) const + { + return what(); + } }; /** @@ -49,22 +54,6 @@ namespace Ark {} }; - /** - * @brief A special zero division error triggered when a number is divided by 0 - * - */ - class ARK_API ZeroDivisionError 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.") - {} - }; - /** * @brief An assertion error, only triggered from ArkScript code through (assert expr error-message) * @@ -77,6 +66,36 @@ namespace Ark {} }; + class ARK_API NestedError final : public Error + { + public: + NestedError(const Error& e, const std::string& details) : + Error("NestedError"), + 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()) + { + if (!m_details.empty() && m_details.back() != '\n') + m_details += '\n'; + m_details += "\n" + details; + } + + [[nodiscard]] const char* what() const noexcept override + { + return m_details.c_str(); + } + + private: + std::string m_details; + }; + /** * @brief CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself) * @@ -111,9 +130,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 5b59f1cb..466a0c94 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 * */ @@ -15,7 +15,9 @@ #include #include #include +#include +#include #include namespace Ark::types @@ -94,12 +96,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 bool colorize) const override + { + std::stringstream stream; + generateError(m_funcname, m_contracts, m_passed_args, stream, colorize); + return stream.str(); + } + + private: + std::string m_funcname; + std::vector m_contracts; + std::vector m_passed_args; + }; } #endif diff --git a/include/Ark/VM/State.hpp b/include/Ark/VM/State.hpp index 48cf4480..07e1d5cd 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/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index df885d82..56a01ccc 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -338,12 +338,25 @@ 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 + * + * @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 * * @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/lib/modules b/lib/modules index f2463cea..a16d1a5c 160000 --- a/lib/modules +++ b/lib/modules @@ -1 +1 @@ -Subproject commit f2463ceacefe5dec8fd6eec2359de1f530693bfd +Subproject commit a16d1a5c4b30ad6d799f1d92c0dd09c42d72cfc8 diff --git a/src/arkreactor/Builtins/Async.cpp b/src/arkreactor/Builtins/Async.cpp index 031e0dcf..9c344636 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 38bdb339..a2c84772 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 f74e81f7..2c2ddc03 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 9679ba44..19b900a8 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 0531bd9f..be1c75a5 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 cf03bea2..150e68aa 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/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index c263e479..3f07dcf6 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,9 @@ namespace Ark const auto syms = symbols(); const auto vals = values(syms); - const auto code_block = code(vals); + const auto files = filenames(vals); + const auto inst_locs = instLocations(files); + const auto code_block = code(inst_locs); // symbols table { @@ -308,6 +383,36 @@ 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 && size > 0) + 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); + } + + if (showVal) + fmt::print("\n"); + } + const auto stringify_value = [](const Value& val) -> std::string { switch (val.valueType()) { diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp index 9e4ae521..7c03a865 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 3d8d95a9..0140392b 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include #include +#include #include #include @@ -23,7 +25,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 +71,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 +129,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 +147,7 @@ namespace Ark::internal } } - for (auto inst : page) + for (auto& inst : page) { switch (inst.kind()) { @@ -200,7 +215,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 +232,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 +279,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/IntermediateRepresentation/IROptimizer.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp index c8067795..f739191e 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 17c4ee2e..4d842429 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -138,6 +138,7 @@ namespace Ark::internal uint16_t i = addSymbol(*it); page(p).emplace_back(GET_FIELD, i); } + page(p).back().setSourceLocation(x.filename(), x.line()); } // register values else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number) @@ -211,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; } } @@ -303,6 +305,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 +318,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 +342,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 +425,8 @@ namespace Ark::internal } else page(p).emplace_back(SET_VAL, i); + + page(p).back().setSourceLocation(x.filename(), x.line()); } void ASTLowerer::compileWhile(const Node& x, const Page p) @@ -428,6 +436,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 +479,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 +553,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 +587,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 +631,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(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 781d560b..39985acd 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; @@ -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 (!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; + // 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); + } } } } @@ -138,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 d177b571..1ad9b670 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/State.cpp b/src/arkreactor/VM/State.cpp index dd677b24..555e9934 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/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 5ece28f6..9875ce69 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,8 +755,8 @@ namespace Ark Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) - types::generateError( - "concat", + 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), @@ -841,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()); @@ -868,14 +869,14 @@ 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), 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; @@ -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), @@ -898,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 = @@ -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(/* colorize= */ true)), 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,30 +1598,107 @@ namespace Ark throw std::runtime_error(std::string(errorKinds[static_cast(kind)]) + ": " + message + "\n"); } - void VM::backtrace(ExecutionContext& context) noexcept + void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context) + { + 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 + 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; + + 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, + // 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)) + break; + } + + return match; + } + + void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize) { const std::size_t saved_ip = context.ip; const std::size_t saved_pp = context.pp; const uint16_t saved_sp = context.sp; - if (const uint16_t original_frame_count = context.fc; original_frame_count > 1) + 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(os, "In file {}", filename, maybe_location->line + 1); + + if (Utils::fileExists(filename)) + Diagnostics::makeContext( + os, + Utils::readFile(filename), + maybe_location->line, + /* col_start= */ 0, + /* sym_size= */ 0, + /* whole_line= */ true, + /* colorize= */ colorize); + fmt::println(os, ""); + } + + 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) { - fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan))); + 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) : ""; + 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("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green))); - else // should never happen - fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold))); + 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 @@ -1624,33 +1712,35 @@ namespace Ark } else { - fmt::println("In global scope"); + 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("..."); + 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/BytecodeReaderSuite.cpp b/tests/unittests/Suites/BytecodeReaderSuite.cpp index 2655b652..278aa9ca 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); diff --git a/tests/unittests/Suites/DiagnosticsSuite.cpp b/tests/unittests/Suites/DiagnosticsSuite.cpp index 969c05f0..e6be93a6 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,10 +50,31 @@ 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); - ltrim(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 c728eb8b..8a5c2cf3 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/Suites/TypeCheckerSuite.cpp b/tests/unittests/Suites/TypeCheckerSuite.cpp index e2adcd71..3d663dc8 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/TestsHelper.cpp b/tests/unittests/TestsHelper.cpp index d3ccdb5f..b141ef0c 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 bffe379e..828d0f18 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/runtime/arity_error_async.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/arity_error_async.expected index 1d276181..515306b6 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 48a28e1f..7c764bd4 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 b16c2714..4cd835b1 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 4fa05718..2ebda34f 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 5a21d49e..58062c86 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 1e4c3122..8187cc3e 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 bb78c23b..f25f2781 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 ddcd3ecd..de9b9bfb 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 c4922e9f..0e8c017f 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 dc3419cf..96fa85ff 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 10d88753..091c68ee 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 efe52bd0..7ac3592c 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,6 @@ 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 ac4f897f..70c4b10f 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/list_set_at.expected @@ -1 +1,6 @@ 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 46382259..9aa6d5d3 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,6 @@ 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 0a1bd9d6..6292b4d9 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,6 @@ 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 edf2a5af..90b54eab 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,6 @@ 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 1067fd6b..7162e4a9 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,6 @@ 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 53ff30d0..f913ab9c 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 b78b3206..dda9178b 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 53ff30d0..fea63f89 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 bffe134a..38e655fa 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 752eda51..e0663238 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 26ea06e4..41e8fdc4 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 6057877f..55bae592 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.expected @@ -1 +1,16 @@ 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) +[ 1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/recursion_depth.ark:7) + +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 c6dc3485..cd0be805 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 f8bfa6dd..42790fa9 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,6 @@ 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 bc7e8bd1..423015a5 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/string_set_at.expected @@ -1 +1,6 @@ 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 39f3c037..d618a3b9 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 7ca84a57..fd0f8185 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 9d0b8e79..0afa2e32 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 | 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 00000000..c1524bdc --- /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 00000000..ab4bc1eb --- /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 00000000..768eb410 --- /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 00000000..c05253be --- /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 00000000..f3a4e671 --- /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 00000000..b93da8c8 --- /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 00000000..4103d60d --- /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 00000000..40d0a059 --- /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 00000000..3e441ff1 --- /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 00000000..3bcb25e3 --- /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 00000000..73a9386b --- /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 00000000..9c067ba4 --- /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 00000000..2746844e --- /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 00000000..e04db362 --- /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 00000000..64500a81 --- /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 00000000..7f59e744 --- /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 00000000..12ae1f64 --- /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 00000000..caf6153d --- /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 00000000..568e7e27 --- /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 00000000..2e4f82ee --- /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 00000000..5f37c239 --- /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 00000000..2cfbff0b --- /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 00000000..ed73ee64 --- /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 00000000..6af7c154 --- /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 00000000..21b6d946 --- /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 00000000..a3161617 --- /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 00000000..0fb28f13 --- /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 00000000..807d3342 --- /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 00000000..953769fb --- /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 00000000..e7e4f442 --- /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 00000000..16f945c9 --- /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 00000000..60fb7dbb --- /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 00000000..41163cce --- /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 00000000..36e0aa47 --- /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 00000000..6fc270e6 --- /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 00000000..d25fe8dc --- /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 00000000..57e3f585 --- /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 00000000..12b6d630 --- /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 00000000..587def95 --- /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 00000000..fa22a357 --- /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 00000000..7ee48e0b --- /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 00000000..153c2e78 --- /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 00000000..cdcf3387 --- /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 00000000..1a3c8c44 --- /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 00000000..c7f9d323 --- /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 00000000..34ecf187 --- /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 00000000..4bcba1dc --- /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 00000000..ab007cd5 --- /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 00000000..0eb34ba5 --- /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 00000000..f4ba5e72 --- /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 00000000..c059c474 --- /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 00000000..6bd10165 --- /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 00000000..b3742371 --- /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 00000000..9923e523 --- /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 00000000..0d15c268 --- /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 00000000..434d2d29 --- /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 |