diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c87e1c3d..2818ddc78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ on: - 'LICENCE' - '.gitignore' pull_request: - branches: [dev] env: BUILD_TYPE: Debug @@ -215,7 +214,6 @@ jobs: shell: bash run: | mkdir -p artifact/lib/std - cp build/ark artifact cp build/arkscript artifact cp build/libArkReactor.* artifact cp lib/*.arkm artifact/lib @@ -227,7 +225,6 @@ jobs: shell: bash run: | mkdir -p artifact/lib/std - cp build/$BUILD_TYPE/ark.exe artifact cp build/$BUILD_TYPE/arkscript.exe artifact cp build/$BUILD_TYPE/ArkReactor.dll artifact cp lib/*.arkm artifact/lib @@ -307,7 +304,7 @@ jobs: run: | mv artifact/cpp/out tests/cpp/ mv build/lib/*.arkm lib/ - chmod u+x build/ark build/arkscript tests/cpp/out/* + chmod u+x build/arkscript tests/cpp/out/* - name: Pre-test if: startsWith(matrix.config.name, 'Windows') @@ -343,7 +340,7 @@ jobs: - shell: bash run: | mv build/lib/*.arkm lib/ - chmod u+x build/ark build/arkscript + chmod u+x build/arkscript - name: Update LLVM compilers shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6641bb6f5..0efb65787 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,7 +116,6 @@ jobs: shell: bash run: | mkdir -p artifact/lib/std - cp build/ark artifact cp build/arkscript artifact cp build/libArkReactor.* artifact cp lib/*.arkm artifact/lib @@ -128,7 +127,6 @@ jobs: shell: bash run: | mkdir -p artifact/lib/std - cp build/$BUILD_TYPE/ark.exe artifact cp build/$BUILD_TYPE/arkscript.exe artifact cp build/$BUILD_TYPE/ArkReactor.dll artifact cp lib/*.arkm artifact/lib @@ -206,7 +204,7 @@ jobs: run: | mv artifact/cpp/out tests/cpp/ mv build/lib/*.arkm lib/ - chmod u+x build/ark build/arkscript tests/cpp/out/* + chmod u+x build/arkscript tests/cpp/out/* - name: Pre-test if: startsWith(matrix.config.name, 'Windows') diff --git a/.gitignore b/.gitignore index 3c6b0a55e..f59d697ca 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ afl/ # Folders .vs/ .idea/ +.cache/ build/ ninja/ diff --git a/.gitmodules b/.gitmodules index d5d8b0eda..137ae9487 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "lib/modules"] path = lib/modules url = https://github.com/ArkScript-lang/modules.git -[submodule "lib/String"] - path = lib/String - url = https://github.com/ArkScript-lang/String.git [submodule "lib/clipp"] path = lib/clipp url = https://github.com/muellan/clipp.git @@ -22,3 +19,6 @@ [submodule "lib/replxx"] path = lib/replxx url = https://github.com/AmokHuginnsson/replxx.git +[submodule "lib/fmt"] + path = lib/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/CHANGELOG.md b/CHANGELOG.md index b95dd9ce3..e311bae6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log +## [4.0.0] - 20XX-XX-XX +### Added +- more tests for the io builtins +- added lines and code coloration in the error context +- new dependency: fmtlib + +### Changed +- instructions are on 4 bytes: 1 byte for the instruction, 1 byte of padding, 2 bytes for an immediate argument +- enhanced the bytecode reader and its command line interface +- added the padding/instruction/argumentation values when displaying instructions in the bytecode reader +- fixed underline bug in the error context +- the str:format functions now expects strings following this syntax: https://fmt.dev/latest/syntax.html +- more documentation about the compiler implementation +- more documentation about the virtual machine +- closures can be now be compared field per field: `(= closure1 closure2)` will work only if they have the same fields (name) and if the values match + +### Removed +- removed unused `NodeType::Closure` +- removing the custom string, replacing it with std::string (the format engine of the custom string had a lot of memory leaks) +- removing the custom string, replacing it with std::string (the format engine of the custom string had a lot of memory leaks) +- `Utils::digPlaces` and `Utils::decPlaces` got removed as they were no longer needed +- removed deprecated code (`list:removeAt`, `ark` executable now replaced by `arkscript`) + ## [3.5.0] - 2023-02-19 ### Added - added fuzzing tools and corpus for [AFL](https://github.com/AFLplusplus/AFLplusplus) diff --git a/CMakeLists.txt b/CMakeLists.txt index 986d1b3e0..a38fd0ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.9) project(ark CXX) # VERSION -set(ARK_VERSION_MAJOR 3) -set(ARK_VERSION_MINOR 4) +set(ARK_VERSION_MAJOR 4) +set(ARK_VERSION_MINOR 0) set(ARK_VERSION_PATCH 0) include(cmake/link_time_optimization.cmake) @@ -26,7 +26,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # files needed for the library ArkReactor file(GLOB_RECURSE SOURCE_FILES - ${ark_SOURCE_DIR}/src/arkreactor/*.cpp) + ${ark_SOURCE_DIR}/src/arkreactor/*.cpp + ${ark_SOURCE_DIR}/lib/fmt/src/*.cc) add_library(ArkReactor SHARED ${SOURCE_FILES}) @@ -92,15 +93,14 @@ endif() # Link libraries add_subdirectory("${ark_SOURCE_DIR}/lib/termcolor" EXCLUDE_FROM_ALL) -add_subdirectory("${ark_SOURCE_DIR}/lib/String/" EXCLUDE_FROM_ALL) target_include_directories(ArkReactor PUBLIC "${ark_SOURCE_DIR}/lib/utf8_decoder/" "${ark_SOURCE_DIR}/lib/picosha2/" - "${ark_SOURCE_DIR}/lib/String/include/") + "${ark_SOURCE_DIR}/lib/fmt/include") -target_link_libraries(ArkReactor PUBLIC termcolor ArkScriptString) +target_link_libraries(ArkReactor PUBLIC termcolor) if (UNIX OR LINUX) if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) @@ -184,7 +184,6 @@ if (ARK_BUILD_EXE) ${ark_SOURCE_DIR}/src/arkscript/main.cpp) add_executable(arkscript ${EXE_SOURCES}) - add_executable(ark ${EXE_SOURCES}) if (MSVC) # Disable warnings for lib/replxx @@ -199,15 +198,10 @@ if (ARK_BUILD_EXE) add_subdirectory("${ark_SOURCE_DIR}/lib/clipp" EXCLUDE_FROM_ALL) target_include_directories(arkscript PUBLIC "${ark_SOURCE_DIR}/src/arkscript/") - target_include_directories(ark PUBLIC "${ark_SOURCE_DIR}/src/arkscript/") - target_link_libraries(arkscript PUBLIC ArkReactor replxx clipp termcolor) - target_link_libraries(ark PUBLIC ArkReactor replxx clipp termcolor) target_compile_features(arkscript PRIVATE cxx_std_17) - target_compile_features(ark PRIVATE cxx_std_17) - enable_lto(ark) enable_lto(arkscript) # Installs the arkscript executable. diff --git a/README.md b/README.md index 5e6e5848e..2d134b7cd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# ArkScript ![Latest version](https://img.shields.io/github/v/release/arkscript-lang/ark?include_prereleases&style=for-the-badge) +# ArkScript ![Latest version](https://img.shields.io/github/v/release/arkscript-lang/ark?style=for-the-badge) ![Code size](https://img.shields.io/github/languages/code-size/arkscript-lang/ark?style=for-the-badge&logo=github) ![Downloads](https://img.shields.io/github/downloads/arkscript-lang/ark/total?color=%2324cc24&style=for-the-badge&logo=github) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ArkScript-lang/Ark/CMake?logo=cmake&style=for-the-badge) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ArkScript-lang/Ark/ci.yml?logo=cmake&style=for-the-badge&branch=4.0) ArkScript log by Mazz @@ -173,15 +173,17 @@ DESCRIPTION ArkScript programming language SYNOPSIS - arkscript -h - arkscript -v - arkscript --dev-info - arkscript -e - arkscript -c [-d] - arkscript -bcr -on - arkscript -bcr [-(a|st|vt)] [-s ] - arkscript -bcr [-cs] [-p ] - arkscript [-d] [-L ] + arkscript -h + arkscript -v + arkscript --dev-info + arkscript -e + arkscript -c [-d] + arkscript -bcr -on + arkscript -bcr -a [-s ] + arkscript -bcr -st [-s ] + arkscript -bcr -vt [-s ] + arkscript -bcr [-cs] [-p ] [-s ] + arkscript [-d] [-L ] OPTIONS -h, --help Display this message @@ -195,9 +197,9 @@ OPTIONS -a, --all Display all the bytecode segments (default) -st, --symbols Display only the symbols table -vt, --values Display only the values table - -s, --slice Select a slice of instructions in the bytecode -cs, --code Display only the code segments -p, --page Set the bytecode reader code segment to display + -s, --slice Select a slice of instructions in the bytecode -L, --lib Set the location of the ArkScript standard library. Paths can be delimited by ';' diff --git a/examples/99bottles.ark b/examples/99bottles.ark index bfb83c3eb..03f599f99 100644 --- a/examples/99bottles.ark +++ b/examples/99bottles.ark @@ -16,6 +16,6 @@ (mut n i) (while (> n 1) { - (print (str:format "%% Bottles of beer on the wall\n%% bottles of beer\nTake one down, pass it around" n n)) + (print (str:format "{} Bottles of beer on the wall\n{} bottles of beer\nTake one down, pass it around" n n)) (set n (- n 1)) - (print (str:format "%% Bottles of beer on the wall." n))}) + (print (str:format "{} Bottles of beer on the wall." n))}) diff --git a/examples/blockchain.ark b/examples/blockchain.ark index 4ca29c813..62c659e37 100644 --- a/examples/blockchain.ark +++ b/examples/blockchain.ark @@ -95,9 +95,9 @@ (let new (json:fromString request)) (set nodes_transactions (append nodes_transactions new)) (print "New transaction") - (print (str:format "FROM: %%" (json:get new "from"))) - (print (str:format "TO: %%" (json:get new "to"))) - (print (str:format "AMOUNT: %%" (json:get new "amount"))) + (print (str:format "FROM: {}" (json:get new "from"))) + (print (str:format "TO: {}" (json:get new "to"))) + (print (str:format "AMOUNT: {}" (json:get new "amount"))) # return value [200 "transaction submission successful" "text/plain"]})) diff --git a/examples/run-all b/examples/run-all index 7fdf61037..a48a0fdf7 100644 --- a/examples/run-all +++ b/examples/run-all @@ -10,11 +10,11 @@ Purple='\033[0;35m' Cyan='\033[0;36m' White='\033[0;37m' -maybe_ark=$(which ark) +maybe_ark=$(which arkscript) if [[ $($maybe_ark --help | grep "ArkScript programming language") != "" ]]; then ark=$maybe_ark -elif [ -f ../build/ark ]; then - ark=../build/ark +elif [ -f ../build/arkscript ]; then + ark=../build/arkscript else echo -e "${Red}Couldn't find ark${Reset}" exit 1 diff --git a/include/Ark/Builtins/Builtins.hpp b/include/Ark/Builtins/Builtins.hpp index 69ae219ad..a34ce632c 100644 --- a/include/Ark/Builtins/Builtins.hpp +++ b/include/Ark/Builtins/Builtins.hpp @@ -4,9 +4,9 @@ * @brief Host the declaration of all the ArkScript builtins * @version 0.1 * @date 2020-10-27 - * + * * @copyright Copyright (c) 2020-2021 - * + * */ #ifndef ARK_BUILTINS_BUILTINS_HPP @@ -35,13 +35,12 @@ namespace Ark::internal::Builtins // ------------------------------ namespace List { - Value reverseList(std::vector& n, VM* vm); // list:reverse, single arg - Value findInList(std::vector& n, VM* vm); // list:find, 2 arguments - Value removeAtList(std::vector& n, VM* vm); // list:removeAt, 2 arguments -- DEPRECATED - Value sliceList(std::vector& n, VM* vm); // list:slice, 4 arguments - Value sort_(std::vector& n, VM* vm); // list:sort, 1 argument - Value fill(std::vector& n, VM* vm); // list:fill, 2 arguments - Value setListAt(std::vector& n, VM* vm); // list:setAt, 3 arguments + Value reverseList(std::vector& n, VM* vm); // list:reverse, single arg + Value findInList(std::vector& n, VM* vm); // list:find, 2 arguments + Value sliceList(std::vector& n, VM* vm); // list:slice, 4 arguments + Value sort_(std::vector& n, VM* vm); // list:sort, 1 argument + Value fill(std::vector& n, VM* vm); // list:fill, 2 arguments + Value setListAt(std::vector& n, VM* vm); // list:setAt, 3 arguments } namespace IO diff --git a/include/Ark/Builtins/BuiltinsErrors.inl b/include/Ark/Builtins/BuiltinsErrors.inl deleted file mode 100644 index c5e187d7b..000000000 --- a/include/Ark/Builtins/BuiltinsErrors.inl +++ /dev/null @@ -1,8 +0,0 @@ -// List - -// TO BE DEPRECATED -#define LIST_RMAT_ARITY "list:removeAt needs 2 arguments: list, index" -#define LIST_RMAT_TE0 "list:removeAt: list must be a List" -#define LIST_RMAT_TE1 "list:removeAt: index must be a Number" -#define LIST_RMAT_OOR "list:removeAt: index out of range" -// -- diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index 593a42b9d..3d2391154 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -2,10 +2,10 @@ * @file Common.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief Common code for the compiler - * @version 0.3 + * @version 0.4 * @date 2021-10-02 * - * @copyright Copyright (c) 2021 + * @copyright Copyright (c) 2021-2022 * */ @@ -34,13 +34,12 @@ namespace Ark::internal String, Number, List, - Closure, Macro, Spread, Unused }; - constexpr std::array nodeTypes = { + constexpr std::array nodeTypes = { "Symbol", "Capture", "GetField", @@ -48,7 +47,6 @@ namespace Ark::internal "String", "Number", "List", - "Closure", "Macro", "Spread", "Unused" diff --git a/include/Ark/Compiler/Compiler.hpp b/include/Ark/Compiler/Compiler.hpp index d77d6a1b7..a5a8d4524 100644 --- a/include/Ark/Compiler/Compiler.hpp +++ b/include/Ark/Compiler/Compiler.hpp @@ -2,7 +2,7 @@ * @file Compiler.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief ArkScript compiler is in charge of transforming the AST into bytecode - * @version 0.3 + * @version 1.2 * @date 2020-10-27 * * @copyright Copyright (c) 2020-2021 @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -82,8 +83,8 @@ namespace Ark std::vector m_defined_symbols; std::vector m_plugins; std::vector m_values; - std::vector> m_code_pages; - std::vector> m_temp_pages; ///< we need temporary code pages for some compilations passes + std::vector> m_code_pages; + std::vector> m_temp_pages; ///< we need temporary code pages for some compilations passes bytecode_t m_bytecode; unsigned m_debug; ///< the debug level of the compiler @@ -104,9 +105,9 @@ namespace Ark * @brief helper functions to get a temp or finalized code page * * @param i page index, if negative, refers to a temporary code page - * @return std::vector& + * @return std::vector& */ - inline std::vector& page(int i) noexcept + inline std::vector& page(int i) noexcept { if (i >= 0) return m_code_pages[i]; @@ -117,21 +118,15 @@ namespace Ark * @brief helper functions to get a temp or finalized code page * * @param i page index, if negative, refers to a temporary code page - * @return std::vector* + * @return std::vector* */ - inline std::vector* page_ptr(int i) noexcept + inline std::vector* page_ptr(int i) noexcept { if (i >= 0) return &m_code_pages[i]; return &m_temp_pages[-i - 1]; } - inline void setNumberAt(int p, std::size_t at_inst, std::size_t number) - { - page(p)[at_inst] = (number & 0xff00) >> 8; - page(p)[at_inst + 1] = number & 0x00ff; - } - /** * @brief Count the number of "valid" ark objects in a node * @details Isn't considered valid a GetField, because we use @@ -148,7 +143,7 @@ namespace Ark * @param name symbol name * @return std::optional position in the operators' list */ - std::optional isOperator(const std::string& name) noexcept; + std::optional getOperator(const std::string& name) noexcept; /** * @brief Checking if a symbol is a builtin @@ -156,7 +151,7 @@ namespace Ark * @param name symbol name * @return std::optional position in the builtins' list */ - std::optional isBuiltin(const std::string& name) noexcept; + std::optional getBuiltin(const std::string& name) noexcept; /** * @brief Check if a symbol needs to be compiled to a specific instruction @@ -164,7 +159,7 @@ namespace Ark * @param name * @return std::optional corresponding instruction if it exists */ - inline std::optional isSpecific(const std::string& name) noexcept + inline std::optional getSpecific(const std::string& name) noexcept { if (name == "list") return internal::Instruction::LIST; @@ -198,9 +193,8 @@ namespace Ark * * @param inst * @param previous - * @param p */ - void pushSpecificInstArgc(internal::Instruction inst, uint16_t previous, int p) noexcept; + uint16_t computeSpecificInstArgc(internal::Instruction inst, uint16_t previous) noexcept; /** * @brief Checking if a symbol may be coming from a plugin @@ -212,51 +206,41 @@ namespace Ark bool mayBeFromPlugin(const std::string& name) noexcept; /** - * @brief Throw a nice error message + * @brief Display a warning message * * @param message * @param node */ - [[noreturn]] void throwCompilerError(const std::string& message, const internal::Node& node); + void compilerWarning(const std::string& message, const internal::Node& node); /** - * @brief Display a warning message + * @brief Throw a nice error message * * @param message * @param node */ - void compilerWarning(const std::string& message, const internal::Node& node); + [[noreturn]] void throwCompilerError(const std::string& message, const internal::Node& node); /** - * @brief Compile a single node recursively + * @brief Compile an expression (a node) recursively * * @param x the internal::Node to compile * @param p the current page number we're on - * @param produces_result + * @param is_result_unused * @param is_terminal * @param var_name */ - void _compile(const internal::Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name = ""); + void compileExpression(const internal::Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name = ""); - void compileSymbol(const internal::Node& x, int p, bool produces_result); - void compileSpecific(const internal::Node& c0, const internal::Node& x, int p, bool produces_result); - void compileIf(const internal::Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name); - void compileFunction(const internal::Node& x, int p, bool produces_result, const std::string& var_name); + void compileSymbol(const internal::Node& x, int p, bool is_result_unused); + void compileSpecific(const internal::Node& c0, const internal::Node& x, int p, bool is_result_unused); + void compileIf(const internal::Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name); + void compileFunction(const internal::Node& x, int p, bool is_result_unused, const std::string& var_name); void compileLetMutSet(internal::Keyword n, const internal::Node& x, int p); void compileWhile(const internal::Node& x, int p); - void compileQuote(const internal::Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name); + void compileQuote(const internal::Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name); void compilePluginImport(const internal::Node& x, int p); - void compileDel(const internal::Node& x, int p); - void handleCalls(const internal::Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name); - - /** - * @brief Put a value in the bytecode, handling the closures chains - * - * @param x value node - * @param p current page index - * @param produces_result - */ - void putValue(const internal::Node& x, int p, bool produces_result); + void handleCalls(const internal::Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name); /** * @brief Register a given node in the symbol table @@ -299,14 +283,6 @@ namespace Ark */ void checkForUndefinedSymbol(); - /** - * @brief Push a number on stack (need 2 bytes) - * - * @param n the number to push - * @param page the page where it should land, nullptr for current page - */ - void pushNumber(uint16_t n, std::vector* page = nullptr) noexcept; - /** * @brief Suggest a symbol of what the user may have meant to input * diff --git a/include/Ark/Compiler/Word.hpp b/include/Ark/Compiler/Word.hpp new file mode 100644 index 000000000..64c5b766b --- /dev/null +++ b/include/Ark/Compiler/Word.hpp @@ -0,0 +1,45 @@ +/** + * @file Word.hpp + * @author Alexandre Plateau (lexplt.dev@gmail.com) + * @brief Describe an instruction and its immediate argument + * @version 0.3 + * @date 2022-07-02 + * + * @copyright Copyright (c) 2022 + * + */ + +#ifndef ARK_COMPILER_WORD_HPP +#define ARK_COMPILER_WORD_HPP + +#include +#include + +namespace Ark::internal +{ + struct bytes_t + { + uint8_t second; + uint8_t first; + }; + + struct Word + { + uint8_t padding = 0; ///< Padding reserved for future use + uint8_t opcode; ///< Instruction opcode + union { + uint16_t data; ///< Immediate data, interpreted differently for different instructions + bytes_t bytes; + }; + + Word() : + opcode(0), data(0) + {} + + Word(uint8_t inst, uint16_t arg = 0) : + opcode(inst), data(arg) + {} + }; +} + +#endif diff --git a/include/Ark/Files.hpp b/include/Ark/Files.hpp index 1df743e32..c9fd31736 100644 --- a/include/Ark/Files.hpp +++ b/include/Ark/Files.hpp @@ -26,7 +26,7 @@ namespace Ark::Utils * @return true on success * @return false on failure */ - inline bool fileExists(const std::string& name) noexcept // 11 + inline bool fileExists(const std::string& name) noexcept { try { @@ -47,7 +47,7 @@ namespace Ark::Utils */ inline std::string readFile(const std::string& name) { - std::ifstream f(name.c_str()); + std::ifstream f(name); // admitting the file exists return std::string( (std::istreambuf_iterator(f)), diff --git a/include/Ark/Utils.hpp b/include/Ark/Utils.hpp index df13883d4..03a3ca98c 100644 --- a/include/Ark/Utils.hpp +++ b/include/Ark/Utils.hpp @@ -64,24 +64,6 @@ namespace Ark::Utils return end != s.c_str() && *end == '\0' && val != HUGE_VAL; } - /** - * @brief Count the number of decimals for a double - * @todo remove when migrating to libfmt - * - * @param d - * @return int - */ - int decPlaces(double d); - - /** - * @brief Count the number of digits for a double - * @todo remove when migrating to libfmt - * - * @param d - * @return int - */ - int digPlaces(double d); - /** * @brief Calculate the Levenshtein distance between two strings * diff --git a/include/Ark/VM/Scope.hpp b/include/Ark/VM/Scope.hpp index f4901e6b3..ff6f497b6 100644 --- a/include/Ark/VM/Scope.hpp +++ b/include/Ark/VM/Scope.hpp @@ -16,6 +16,7 @@ #include #include +#include #include namespace Ark::internal @@ -66,6 +67,14 @@ namespace Ark::internal */ Value* operator[](uint16_t id) noexcept; + /** + * @brief Get a value from its symbol id + * + * @param id + * @return const Value* Returns nullptr if the value can not be found + */ + const Value* operator[](uint16_t id) const noexcept; + /** * @brief Get the id of a variable based on its value ; used for debug only * @@ -81,11 +90,15 @@ namespace Ark::internal */ std::size_t size() const noexcept; + friend ARK_API bool operator==(const Scope& A, const Scope& B) noexcept; + friend class Ark::VM; friend class Ark::internal::Closure; private: std::vector> m_data; + uint16_t m_min_id; + uint16_t m_max_id; }; } diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index f28354262..0df4087c5 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -2,7 +2,7 @@ * @file VM.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief The ArkScript virtual machine - * @version 0.3 + * @version 1.0 * @date 2020-10-27 * * @copyright Copyright (c) 2020-2021 @@ -54,10 +54,6 @@ namespace Ark */ explicit VM(State& state) noexcept; - [[deprecated("Use VM(State&) instead of VM(State*)")]] explicit VM(State* state) noexcept : - VM(*state) - {} - /** * @brief Run the bytecode held in the state * @@ -197,15 +193,6 @@ namespace Ark */ void init() noexcept; - /** - * @brief Read a 2 bytes number from the current bytecode page, starting at the current instruction - * @details Modify the instruction pointer to point on the instruction right after the number. - * - * @param context - * @return uint16_t - */ - inline uint16_t readNumber(internal::ExecutionContext& context); - // ================================================ // stack related // ================================================ diff --git a/include/Ark/VM/Value.hpp b/include/Ark/VM/Value.hpp index afed85857..8a660c3c3 100644 --- a/include/Ark/VM/Value.hpp +++ b/include/Ark/VM/Value.hpp @@ -20,7 +20,6 @@ #include #include #include -#include // our string implementation #include #include @@ -76,7 +75,7 @@ namespace Ark using Value_t = std::variant< double, // 8 bytes - String, // 16 bytes + std::string, // 32 bytes internal::PageAddr_t, // 2 bytes ProcType, // 8 bytes internal::Closure, // 24 bytes @@ -84,7 +83,7 @@ namespace Ark std::vector, // 24 bytes Value* // 8 bytes >; // +8 bytes overhead - // total 32 bytes + // total 40 bytes /** * @brief Construct a new Value object @@ -148,13 +147,6 @@ namespace Ark */ explicit Value(const std::string& value) noexcept; - /** - * @brief Construct a new Value object as a String - * - * @param value - */ - explicit Value(const String& value) noexcept; - /** * @brief Construct a new Value object as a String * @@ -229,9 +221,9 @@ namespace Ark /** * @brief Return the stored string * - * @return const String& + * @return const std::string& */ - inline const String& string() const; + inline const std::string& string() const; /** * @brief Return the stored list @@ -257,9 +249,9 @@ namespace Ark /** * @brief Return the stored string as a reference * - * @return String& + * @return std::string& */ - String& stringRef(); + std::string& stringRef(); /** * @brief Return the stored user type as a reference diff --git a/include/Ark/VM/Value/Closure.hpp b/include/Ark/VM/Value/Closure.hpp index a0b3bf87e..8b7d22457 100644 --- a/include/Ark/VM/Value/Closure.hpp +++ b/include/Ark/VM/Value/Closure.hpp @@ -95,7 +95,7 @@ namespace Ark::internal */ void toString(std::ostream& os, VM& vm) const noexcept; - friend ARK_API_INLINE bool operator==(const Closure& A, const Closure& B) noexcept; + friend ARK_API bool operator==(const Closure& A, const Closure& B) noexcept; friend ARK_API_INLINE bool operator<(const Closure& A, const Closure& B) noexcept; private: @@ -104,11 +104,6 @@ namespace Ark::internal PageAddr_t m_page_addr; }; - inline bool operator==(const Closure& A, const Closure& B) noexcept - { - return A.m_scope == B.m_scope; - } - inline bool operator<(const Closure& A, const Closure& B) noexcept { return A.m_page_addr < B.m_page_addr; diff --git a/include/Ark/VM/inline/VM.inl b/include/Ark/VM/inline/VM.inl index e14dcb177..01368c7f9 100644 --- a/include/Ark/VM/inline/VM.inl +++ b/include/Ark/VM/inline/VM.inl @@ -141,16 +141,6 @@ inline Value VM::resolve(internal::ExecutionContext* context, std::vector #pragma region "stack management" -inline uint16_t VM::readNumber(internal::ExecutionContext& context) -{ - uint16_t tmp = - (static_cast(m_state.m_pages[context.pp][context.ip]) << 8) + - static_cast(m_state.m_pages[context.pp][context.ip + 1]); - - ++context.ip; - return tmp; -} - inline Value* VM::pop(internal::ExecutionContext& context) { if (context.sp > 0) @@ -346,7 +336,7 @@ inline void VM::call(internal::ExecutionContext& context, int16_t argc_) context.locals.back()->push_back(context.last_symbol, function); context.pp = new_page_pointer; - context.ip = -1; // because we are doing a context.ip++ right after that + context.ip = 0; break; } @@ -365,7 +355,7 @@ inline void VM::call(internal::ExecutionContext& context, int16_t argc_) swapStackForFunCall(argc, context); context.pp = new_page_pointer; - context.ip = -1; // because we are doing a context.ip++ right after that + context.ip = 0; break; } @@ -383,10 +373,11 @@ inline void VM::call(internal::ExecutionContext& context, int16_t argc_) needed_argc = 0; // every argument is a MUT declaration in the bytecode - while (m_state.m_pages[context.pp][index] == Instruction::MUT) + // index+1 to skip the padding + while (m_state.m_pages[context.pp][index + 1] == Instruction::MUT) { needed_argc += 1; - index += 3; // jump the argument of MUT (integer on 2 bits, big endian) + index += 4; // instructions are on 4 bytes } if (needed_argc != argc) diff --git a/include/Ark/VM/inline/Value.inl b/include/Ark/VM/inline/Value.inl index 536094610..b8e8ea3a5 100644 --- a/include/Ark/VM/inline/Value.inl +++ b/include/Ark/VM/inline/Value.inl @@ -18,9 +18,9 @@ inline double Value::number() const return std::get(m_value); } -inline const String& Value::string() const +inline const std::string& Value::string() const { - return std::get(m_value); + return std::get(m_value); } inline const std::vector& Value::constList() const @@ -100,7 +100,7 @@ inline bool operator!(const Value& A) noexcept return !A.number(); case ValueType::String: - return A.string().size() == 0; + return A.string().empty(); case ValueType::User: case ValueType::Nil: diff --git a/lib/String b/lib/String deleted file mode 160000 index 2c358d940..000000000 --- a/lib/String +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c358d940c937eb9b6257ba746dcb7dbac6cecbb diff --git a/lib/fmt b/lib/fmt new file mode 160000 index 000000000..7bdf0628b --- /dev/null +++ b/lib/fmt @@ -0,0 +1 @@ +Subproject commit 7bdf0628b1276379886c7f6dda2cef2b3b374f0b diff --git a/lib/modules b/lib/modules index 0c99e073c..74f2cd3a2 160000 --- a/lib/modules +++ b/lib/modules @@ -1 +1 @@ -Subproject commit 0c99e073c53e9b3d73589ebeb1d7052b728c45b4 +Subproject commit 74f2cd3a246138f336997c8da8cba496f41c8930 diff --git a/src/arkreactor/Builtins/Builtins.cpp b/src/arkreactor/Builtins/Builtins.cpp index 6ec5920fb..841032099 100644 --- a/src/arkreactor/Builtins/Builtins.cpp +++ b/src/arkreactor/Builtins/Builtins.cpp @@ -28,7 +28,6 @@ namespace Ark::internal::Builtins // List { "list:reverse", Value(List::reverseList) }, { "list:find", Value(List::findInList) }, - { "list:removeAt", Value(List::removeAtList) }, // DEPRECATED { "list:slice", Value(List::sliceList) }, { "list:sort", Value(List::sort_) }, { "list:fill", Value(List::fill) }, diff --git a/src/arkreactor/Builtins/IO.cpp b/src/arkreactor/Builtins/IO.cpp index 8be164c8b..9bd14ef6e 100644 --- a/src/arkreactor/Builtins/IO.cpp +++ b/src/arkreactor/Builtins/IO.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace Ark::internal::Builtins::IO { @@ -61,7 +62,7 @@ namespace Ark::internal::Builtins::IO Value input(std::vector& n, VM* vm [[maybe_unused]]) { if (types::check(n, ValueType::String)) - std::printf("%s", n[0].string().c_str()); + fmt::print(n[0].string()); else if (n.size() != 0) types::generateError("input", { { types::Contract {}, types::Contract { { types::Typedef("prompt", ValueType::String) } } } }, n); @@ -87,14 +88,14 @@ namespace Ark::internal::Builtins::IO { if (types::check(n, ValueType::String, ValueType::Any)) { - std::ofstream f(n[0].string().c_str()); + std::ofstream f(n[0].string()); if (f.is_open()) { n[1].toString(f, *vm); f.close(); } else - throw std::runtime_error("Couldn't write to file \"" + n[0].stringRef().toString() + "\""); + throw std::runtime_error("Couldn't write to file \"" + n[0].stringRef() + "\""); } else if (types::check(n, ValueType::String, ValueType::String, ValueType::Any)) { @@ -106,14 +107,14 @@ namespace Ark::internal::Builtins::IO if (mode == "a") ios_mode = std::ios::out | std::ios::app; - std::ofstream f(n[0].string().c_str(), ios_mode); + std::ofstream f(n[0].string(), ios_mode); if (f.is_open()) { n[2].toString(f, *vm); f.close(); } else - throw std::runtime_error("Couldn't write to file \"" + n[0].stringRef().toString() + "\""); + throw std::runtime_error("Couldn't write to file \"" + n[0].stringRef() + "\""); } else types::generateError( @@ -142,7 +143,7 @@ namespace Ark::internal::Builtins::IO { { types::Contract { { types::Typedef("filename", ValueType::String) } } } }, n); - auto filename = n[0].string().c_str(); + std::string filename = n[0].string(); if (!Utils::fileExists(filename)) throw std::runtime_error("Couldn't read file \"" + std::string(filename) + "\": it doesn't exist"); @@ -166,7 +167,7 @@ namespace Ark::internal::Builtins::IO { { types::Contract { { types::Typedef("filename", ValueType::String) } } } }, n); - return Utils::fileExists(n[0].string().c_str()) ? trueSym : falseSym; + return Utils::fileExists(n[0].string()) ? trueSym : falseSym; } /** @@ -187,8 +188,8 @@ namespace Ark::internal::Builtins::IO n); std::vector r; - for (const auto& entry : std::filesystem::directory_iterator(n[0].string().c_str())) - r.emplace_back(entry.path().string().c_str()); + for (const auto& entry : std::filesystem::directory_iterator(n[0].string())) + r.emplace_back(entry.path().string()); return Value(std::move(r)); } @@ -210,7 +211,7 @@ namespace Ark::internal::Builtins::IO { { types::Contract { { types::Typedef("path", ValueType::String) } } } }, n); - return (std::filesystem::is_directory(std::filesystem::path(n[0].string().c_str()))) ? trueSym : falseSym; + return (std::filesystem::is_directory(std::filesystem::path(n[0].string()))) ? trueSym : falseSym; } /** @@ -230,7 +231,7 @@ namespace Ark::internal::Builtins::IO { { types::Contract { { types::Typedef("path", ValueType::String) } } } }, n); - std::filesystem::create_directories(std::filesystem::path(n[0].string().c_str())); + std::filesystem::create_directories(std::filesystem::path(n[0].string())); return nil; } @@ -260,7 +261,7 @@ namespace Ark::internal::Builtins::IO { { types::Contract { { types::Typedef("filename", ValueType::String), types::Typedef("filenames", ValueType::String, /* variadic */ true) } } } }, n); - std::filesystem::remove_all(std::filesystem::path(it->string().c_str())); + std::filesystem::remove_all(std::filesystem::path(it->string())); } return nil; diff --git a/src/arkreactor/Builtins/List.cpp b/src/arkreactor/Builtins/List.cpp index f5746142e..5e6a8a933 100644 --- a/src/arkreactor/Builtins/List.cpp +++ b/src/arkreactor/Builtins/List.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -61,44 +60,6 @@ namespace Ark::internal::Builtins::List return Value(-1); } - /** - * @name list:removeAt - * @brief Remove an element in a List and return a new one - * @details The original list is not modified - * @param list the list to remove an element from - * @param index the index of the element to remove (can be negative to search from the end) - * =begin - * (list:removeAt [1 2 3] 0) # [2 3] - * (list:removeAt [1 2 3] -1) # [1 2] - * =end - * @author https://github.com/SuperFola - */ - Value removeAtList(std::vector& n, VM* vm [[maybe_unused]]) - { - static bool has_warned = false; - if (!has_warned) - { - std::cout << "list:removeAt will be deprecated in ArkScript 4.0.0, consider using pop! or pop\n"; - has_warned = true; - } - - // TEMP: not fixing the errors here because this will be deprecated and removed - - if (n.size() != 2) - throw std::runtime_error(LIST_RMAT_ARITY); - if (n[0].valueType() != ValueType::List) - throw TypeError(LIST_RMAT_TE0); - if (n[1].valueType() != ValueType::Number) - throw TypeError(LIST_RMAT_TE1); - - std::size_t idx = static_cast(n[1].number()); - if (idx >= n[0].list().size()) - throw std::runtime_error(LIST_RMAT_OOR); - - n[0].list().erase(n[0].list().begin() + idx); - return n[0]; - } - /** * @name list:slice * @brief Get a slice from a List diff --git a/src/arkreactor/Builtins/Mathematics.cpp b/src/arkreactor/Builtins/Mathematics.cpp index 232190a57..6570a7050 100644 --- a/src/arkreactor/Builtins/Mathematics.cpp +++ b/src/arkreactor/Builtins/Mathematics.cpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/src/arkreactor/Builtins/String.cpp b/src/arkreactor/Builtins/String.cpp index 40450d17c..809504212 100644 --- a/src/arkreactor/Builtins/String.cpp +++ b/src/arkreactor/Builtins/String.cpp @@ -1,8 +1,8 @@ #include -#include #include #include +#include #include #include @@ -12,15 +12,15 @@ namespace Ark::internal::Builtins::String /** * @name str:format * @brief Format a String given replacements - * @details The format is %% for anything, and %x for hex numbers + * @details https://fmt.dev/latest/syntax.html * @param format the String to format * @param values as any argument as you need, of any valid ArkScript type * =begin - * (str:format "Hello %%, my name is %%" "world" "ArkScript") + * (str:format "Hello {}, my name is {}" "world" "ArkScript") * # Hello world, my name is ArkScript * - * (str:format "Test %% with %%" "1") - * # Test 1 with %% + * (str:format "Test {} with {}" "1") + * # Test 1 with {} * =end * @author https://github.com/SuperFola */ @@ -33,35 +33,37 @@ namespace Ark::internal::Builtins::String types::Typedef("value", ValueType::Any, /* variadic */ true) } } } }, n); - ::String f(n[0].string().c_str()); + std::string all_str = n[0].stringRef(); + std::string f; + std::size_t previous = 0; for (Value::Iterator it = n.begin() + 1, it_end = n.end(); it != it_end; ++it) { + std::size_t len = all_str.find_first_of('}', previous); + std::string current = all_str.substr(previous, len + 1); + previous += len + 1; + if (it->valueType() == ValueType::String) - { - ::String& obj = it->stringRef(); - f.format(f.size() + obj.size(), obj.c_str()); - } + current = fmt::format(current, it->stringRef()); else if (it->valueType() == ValueType::Number) - { - double obj = it->number(); - f.format(f.size() + Utils::digPlaces(obj) + Utils::decPlaces(obj) + 1, obj); - } + current = fmt::format(current, it->number()); else if (it->valueType() == ValueType::Nil) - f.format(f.size() + 5, std::string_view("nil")); + current = fmt::format(current, "nil"); else if (it->valueType() == ValueType::True) - f.format(f.size() + 5, std::string_view("true")); + current = fmt::format(current, "true"); else if (it->valueType() == ValueType::False) - f.format(f.size() + 5, std::string_view("false")); + current = fmt::format(current, "false"); else { std::stringstream ss; it->toString(ss, *vm); - f.format(f.size() + ss.str().size(), std::string_view(ss.str().c_str())); + current = fmt::format(current, ss.str()); } + + f += current; } - n[0].stringRef() = f; - return n[0]; + + return Value(f); } /** @@ -84,7 +86,10 @@ namespace Ark::internal::Builtins::String { { types::Contract { { types::Typedef("string", ValueType::String), types::Typedef("substr", ValueType::String) } } } }, n); - return Value(n[0].stringRef().find(n[1].stringRef())); + std::size_t index = n[0].stringRef().find(n[1].stringRef()); + if (index != std::string::npos) + return Value(static_cast(index)); + return Value(-1); } /** @@ -111,7 +116,7 @@ namespace Ark::internal::Builtins::String if (id < 0 || static_cast(id) >= n[0].stringRef().size()) throw std::runtime_error("str:removeAt: index out of range"); - n[0].stringRef().erase(id, id + 1); + n[0].stringRef().erase(id, 1); return n[0]; } diff --git a/src/arkreactor/Builtins/Time.cpp b/src/arkreactor/Builtins/Time.cpp index d3ea3e6d7..b5d907e0a 100644 --- a/src/arkreactor/Builtins/Time.cpp +++ b/src/arkreactor/Builtins/Time.cpp @@ -3,7 +3,6 @@ #undef abs #include -#include #include namespace Ark::internal::Builtins::Time diff --git a/src/arkreactor/Compiler/AST/Node.cpp b/src/arkreactor/Compiler/AST/Node.cpp index 00c5f4ee8..438b9cc06 100644 --- a/src/arkreactor/Compiler/AST/Node.cpp +++ b/src/arkreactor/Compiler/AST/Node.cpp @@ -226,10 +226,6 @@ namespace Ark::internal break; } - case NodeType::Closure: - os << "Closure"; - break; - case NodeType::Keyword: switch (N.keyword()) { @@ -287,8 +283,7 @@ namespace Ark::internal if (A.m_type != B.m_type) // should have the same types return false; - if (A.m_type != NodeType::List && - A.m_type != NodeType::Closure) + if (A.m_type != NodeType::List) return A.m_value == B.m_value; if (A.m_type == NodeType::List) diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index 125c10fb2..9e0fdae95 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -5,6 +5,7 @@ #undef abs #include +#include #include #include @@ -109,10 +110,11 @@ namespace Ark os << "SHA256: "; for (std::size_t j = 0; j < picosha2::k_digest_size; ++j) { - os << std::hex << static_cast(m_bytecode[i]) << std::dec; + os << std::hex << static_cast(m_bytecode[i]); ++i; } - os << "\n\n"; + os << "\n\n" + << std::dec; std::vector symbols; std::vector values; @@ -259,16 +261,13 @@ namespace Ark return; uint16_t pp = 0; + std::size_t cumulated_segment_size = i + 3; while (b[i] == Instruction::CODE_SEGMENT_START && (segment == BytecodeSegment::All || segment == BytecodeSegment::Code || segment == BytecodeSegment::HeadersOnly)) { i++; uint16_t size = readNumber(i); i++; - uint16_t sliceSize = size; - - if (sStart.has_value() && sEnd.has_value()) - sliceSize = sEnd.value() - sStart.value() + 1; bool displayCode = true; @@ -276,7 +275,7 @@ namespace Ark displayCode = pp == page.value(); if (displayCode) - os << termcolor::magenta << "Code segment " << pp << termcolor::reset << " (length: " << sliceSize << ")\n"; + os << termcolor::magenta << "Code segment " << pp << termcolor::reset << " (length: " << size << ")\n"; if (size == 0) { @@ -285,339 +284,154 @@ namespace Ark } else { - uint16_t j = i; + i += 4 * sStart.value_or(0); - bool displayLine = segment == BytecodeSegment::HeadersOnly ? false : displayCode; - while (true) + if (cPage.value_or(pp) == pp && segment != BytecodeSegment::HeadersOnly) { - uint16_t line_number = i - j; if (sStart.has_value() && sEnd.has_value() && ((sStart.value() > size) || (sEnd.value() > size))) { os << termcolor::red << "Slice start or end can't be greater than the segment size: " << size << termcolor::reset << "\n"; return; } - else if (sStart.has_value() && sEnd.has_value() && cPage.has_value()) - displayLine = displayCode && (line_number >= sStart.value() && line_number <= sEnd.value()); - - if (displayLine) - os << termcolor::cyan << line_number << termcolor::reset << " " << termcolor::yellow; - uint8_t inst = b[i]; - i++; - if (inst == Instruction::NOP) + for (std::size_t j = sStart.value_or(0), end = sEnd.value_or(size); j < end; ++j) { - if (displayLine) + [[maybe_unused]] uint8_t padding = b[i]; + ++i; + uint8_t inst = b[i]; + ++i; + uint16_t arg = readNumber(i); + ++i; + + // instruction number + os << termcolor::cyan << j << " "; + // padding inst arg arg + os << termcolor::reset << std::hex + << std::setw(2) << std::setfill('0') << static_cast(padding) << " " + << std::setw(2) << std::setfill('0') << static_cast(inst) << " " + << std::setw(2) << std::setfill('0') << static_cast(b[i - 2]) << " " + << std::setw(2) << std::setfill('0') << static_cast(b[i - 1]) << " "; + // reset stream + os << std::dec << termcolor::yellow; + + if (inst == Instruction::NOP) os << "NOP\n"; - } - else if (inst == Instruction::LOAD_SYMBOL) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "LOAD_SYMBOL " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::LOAD_CONST) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "LOAD_CONST " << termcolor::magenta << values[index] << "\n"; - i++; - } - else if (inst == Instruction::POP_JUMP_IF_TRUE) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "POP_JUMP_IF_TRUE " << termcolor::red << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::STORE) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "STORE " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::LET) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "LET " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::POP_JUMP_IF_FALSE) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "POP_JUMP_IF_FALSE " << termcolor::red << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::JUMP) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "JUMP " << termcolor::red << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::RET) - { - if (displayLine) + else if (inst == Instruction::LOAD_SYMBOL) + os << "LOAD_SYMBOL " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::LOAD_CONST) + os << "LOAD_CONST " << termcolor::magenta << values[arg] << "\n"; + else if (inst == Instruction::POP_JUMP_IF_TRUE) + os << "POP_JUMP_IF_TRUE " << termcolor::red << "(" << arg << ")\n"; + else if (inst == Instruction::STORE) + os << "STORE " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::LET) + os << "LET " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::POP_JUMP_IF_FALSE) + os << "POP_JUMP_IF_FALSE " << termcolor::red << "(" << arg << ")\n"; + else if (inst == Instruction::JUMP) + os << "JUMP " << termcolor::red << "(" << arg << ")\n"; + else if (inst == Instruction::RET) os << "RET\n"; - } - else if (inst == Instruction::HALT) - { - if (displayLine) + else if (inst == Instruction::HALT) os << "HALT\n"; - } - else if (inst == Instruction::CALL) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "CALL " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::CAPTURE) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "CAPTURE " << termcolor::reset << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::BUILTIN) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "BUILTIN " << termcolor::reset << Builtins::builtins[index].first << "\n"; - i++; - } - else if (inst == Instruction::MUT) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "MUT " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::DEL) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "DEL " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::SAVE_ENV) - { - if (displayLine) + else if (inst == Instruction::CALL) + os << "CALL " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::CAPTURE) + os << "CAPTURE " << termcolor::reset << symbols[arg] << "\n"; + else if (inst == Instruction::BUILTIN) + os << "BUILTIN " << termcolor::reset << Builtins::builtins[arg].first << "\n"; + else if (inst == Instruction::MUT) + os << "MUT " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::DEL) + os << "DEL " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::SAVE_ENV) os << "SAVE_ENV\n"; - } - else if (inst == Instruction::GET_FIELD) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "GET_FIELD " << termcolor::green << symbols[index] << "\n"; - i++; - } - else if (inst == Instruction::PLUGIN) - { - uint16_t index = readNumber(i); - if (displayLine) - os << "PLUGIN " << termcolor::magenta << values[index] << "\n"; - i++; - } - else if (inst == Instruction::LIST) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "LIST " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::APPEND) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "APPEND " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::CONCAT) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "CONCAT " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::APPEND_IN_PLACE) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "APPEND_IN_PLACE " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::CONCAT_IN_PLACE) - { - uint16_t value = readNumber(i); - if (displayLine) - os << "CONCAT_IN_PLACE " << termcolor::reset << "(" << value << ")\n"; - i++; - } - else if (inst == Instruction::POP_LIST) - { - if (displayLine) + else if (inst == Instruction::GET_FIELD) + os << "GET_FIELD " << termcolor::green << symbols[arg] << "\n"; + else if (inst == Instruction::PLUGIN) + os << "PLUGIN " << termcolor::magenta << values[arg] << "\n"; + else if (inst == Instruction::LIST) + os << "LIST " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::APPEND) + os << "APPEND " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::CONCAT) + os << "CONCAT " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::APPEND_IN_PLACE) + os << "APPEND_IN_PLACE " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::CONCAT_IN_PLACE) + os << "CONCAT_IN_PLACE " << termcolor::reset << "(" << arg << ")\n"; + else if (inst == Instruction::POP_LIST) os << "POP_LIST " << termcolor::reset << "\n"; - i++; - } - else if (inst == Instruction::POP_LIST_IN_PLACE) - { - if (displayLine) + else if (inst == Instruction::POP_LIST_IN_PLACE) os << "POP_LIST_IN_PLACE " << termcolor::reset << "\n"; - i++; - } - else if (inst == Instruction::POP) - { - if (displayLine) + else if (inst == Instruction::POP) os << "POP\n"; - } - else if (inst == Instruction::ADD) - { - if (displayLine) + else if (inst == Instruction::ADD) os << "ADD\n"; - } - else if (inst == Instruction::SUB) - { - if (displayLine) + else if (inst == Instruction::SUB) os << "SUB\n"; - } - else if (inst == Instruction::MUL) - { - if (displayLine) + else if (inst == Instruction::MUL) os << "MUL\n"; - } - else if (inst == Instruction::DIV) - { - if (displayLine) + else if (inst == Instruction::DIV) os << "DIV\n"; - } - else if (inst == Instruction::GT) - { - if (displayLine) + else if (inst == Instruction::GT) os << "GT\n"; - } - else if (inst == Instruction::LT) - { - if (displayLine) + else if (inst == Instruction::LT) os << "LT\n"; - } - else if (inst == Instruction::LE) - { - if (displayLine) + else if (inst == Instruction::LE) os << "LE\n"; - } - else if (inst == Instruction::GE) - { - if (displayLine) + else if (inst == Instruction::GE) os << "GE\n"; - } - else if (inst == Instruction::NEQ) - { - if (displayLine) + else if (inst == Instruction::NEQ) os << "NEQ\n"; - } - else if (inst == Instruction::EQ) - { - if (displayLine) + else if (inst == Instruction::EQ) os << "EQ\n"; - } - else if (inst == Instruction::LEN) - { - if (displayLine) + else if (inst == Instruction::LEN) os << "LEN\n"; - } - else if (inst == Instruction::EMPTY) - { - if (displayLine) + else if (inst == Instruction::EMPTY) os << "EMPTY\n"; - } - else if (inst == Instruction::TAIL) - { - if (displayLine) + else if (inst == Instruction::TAIL) os << "TAIL\n"; - } - else if (inst == Instruction::HEAD) - { - if (displayLine) + else if (inst == Instruction::HEAD) os << "HEAD\n"; - } - else if (inst == Instruction::ISNIL) - { - if (displayLine) + else if (inst == Instruction::ISNIL) os << "ISNIL\n"; - } - else if (inst == Instruction::ASSERT) - { - if (displayLine) + else if (inst == Instruction::ASSERT) os << "ASSERT\n"; - } - else if (inst == Instruction::TO_NUM) - { - if (displayLine) + else if (inst == Instruction::TO_NUM) os << "TO_NUM\n"; - } - else if (inst == Instruction::TO_STR) - { - if (displayLine) + else if (inst == Instruction::TO_STR) os << "TO_STR\n"; - } - else if (inst == Instruction::AT) - { - if (displayLine) + else if (inst == Instruction::AT) os << "AT\n"; - } - else if (inst == Instruction::AND_) - { - if (displayLine) + else if (inst == Instruction::AND_) os << "AND_\n"; - } - else if (inst == Instruction::OR_) - { - if (displayLine) + else if (inst == Instruction::OR_) os << "OR_\n"; - } - else if (inst == Instruction::MOD) - { - if (displayLine) + else if (inst == Instruction::MOD) os << "MOD\n"; - } - else if (inst == Instruction::TYPE) - { - if (displayLine) + else if (inst == Instruction::TYPE) os << "TYPE\n"; - } - else if (inst == Instruction::HASFIELD) - { - if (displayLine) + else if (inst == Instruction::HASFIELD) os << "HASFIELD\n"; - } - else if (inst == Instruction::NOT) - { - if (displayLine) + else if (inst == Instruction::NOT) os << "NOT\n"; - } - else - { - if (displayLine) + else + { os << termcolor::reset << "Unknown instruction: " << static_cast(inst) << '\n' << termcolor::reset; - return; + return; + } } - - if (i - j == size) - break; } + + i = cumulated_segment_size + size * 4; + cumulated_segment_size += size * 4 + 3; } if (displayCode && segment != BytecodeSegment::HeadersOnly) os << "\n" << termcolor::reset; - if (cPage.has_value() && pp == cPage) - return; - ++pp; if (i == b.size()) diff --git a/src/arkreactor/Compiler/Compiler.cpp b/src/arkreactor/Compiler/Compiler.cpp index d920ff199..53b1e331f 100644 --- a/src/arkreactor/Compiler/Compiler.cpp +++ b/src/arkreactor/Compiler/Compiler.cpp @@ -39,32 +39,49 @@ namespace Ark m_code_pages.emplace_back(); // create empty page // gather symbols, values, and start to create code segments - _compile(m_optimizer.ast(), /* current_page */ 0, /* produces_result */ false, /* is_terminal */ false); + compileExpression(m_optimizer.ast(), /* current_page */ 0, /* is_result_unused */ false, /* is_terminal */ false); // throw an error on undefined symbol uses checkForUndefinedSymbol(); pushSymAndValTables(); // push the different code segments - for (const bytecode_t& page : m_code_pages) + for (std::size_t i = 0, end = m_code_pages.size(); i < end; ++i) { - m_bytecode.push_back(Instruction::CODE_SEGMENT_START); + std::vector& page = m_code_pages[i]; + // just in case we got too far, always add a HALT to be sure the + // VM won't do anything crazy + page.push_back(Instruction::HALT); // push number of elements - pushNumber(static_cast(page.size() + 1)); + std::size_t page_size = page.size(); + if (page_size > std::numeric_limits::max()) + throw std::overflow_error("Size of page " + std::to_string(i) + " exceeds the maximum size of 2^16 - 1"); + + m_bytecode.push_back(Instruction::CODE_SEGMENT_START); + m_bytecode.push_back(static_cast((page_size & 0xff00) >> 8)); + m_bytecode.push_back(static_cast(page_size & 0x00ff)); for (auto inst : page) - m_bytecode.push_back(inst); - // just in case we got too far, always add a HALT to be sure the - // VM won't do anything crazy - m_bytecode.push_back(Instruction::HALT); + { + m_bytecode.push_back(inst.padding); + m_bytecode.push_back(inst.opcode); + m_bytecode.push_back(inst.bytes.first); + m_bytecode.push_back(inst.bytes.second); + } } if (!m_code_pages.size()) { + // code segment with a single instruction m_bytecode.push_back(Instruction::CODE_SEGMENT_START); - pushNumber(1_u16); + m_bytecode.push_back(0_u8); + m_bytecode.push_back(1_u8); + + m_bytecode.push_back(0_u8); m_bytecode.push_back(Instruction::HALT); + m_bytecode.push_back(0_u8); + m_bytecode.push_back(0_u8); } constexpr std::size_t header_size = 18; @@ -106,17 +123,19 @@ namespace Ark m_bytecode.push_back(0_u8); // push version - pushNumber(ARK_VERSION_MAJOR); - pushNumber(ARK_VERSION_MINOR); - pushNumber(ARK_VERSION_PATCH); + for (int n : std::array { ARK_VERSION_MAJOR, ARK_VERSION_MINOR, ARK_VERSION_PATCH }) + { + m_bytecode.push_back(static_cast((n & 0xff00) >> 8)); + m_bytecode.push_back(static_cast(n & 0x00ff)); + } // push timestamp unsigned long long timestamp = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); - for (char c = 0; c < 8; c++) + for (std::size_t i = 0; i < 8; ++i) { - unsigned shift = 8 * (7 - c); + unsigned shift = 8 * (7 - i); uint8_t ts_byte = (timestamp & (0xffULL << shift)) >> shift; m_bytecode.push_back(ts_byte); } @@ -124,17 +143,14 @@ namespace Ark void Compiler::pushSymAndValTables() { - /* - - symbols table - + elements - - values table header - + elements - */ + std::size_t symbol_size = m_symbols.size(); + if (symbol_size > std::numeric_limits::max()) + throw std::overflow_error("Too many symbols: " + std::to_string(symbol_size) + ", exceeds the maximum size of 2^16 - 1"); m_bytecode.push_back(Instruction::SYM_TABLE_START); - // push size - pushNumber(static_cast(m_symbols.size())); - // push elements + m_bytecode.push_back(static_cast((symbol_size & 0xff00) >> 8)); + m_bytecode.push_back(static_cast(symbol_size & 0x00ff)); + for (auto sym : m_symbols) { // push the string, null terminated @@ -144,12 +160,15 @@ namespace Ark m_bytecode.push_back(0_u8); } - // values table + std::size_t value_size = m_values.size(); + if (value_size > std::numeric_limits::max()) + throw std::overflow_error("Too many values: " + std::to_string(value_size) + ", exceeds the maximum size of 2^16 - 1"); + m_bytecode.push_back(Instruction::VAL_TABLE_START); - // push size - pushNumber(static_cast(m_values.size())); - // push elements (separated with 0x00) - for (auto val : m_values) + m_bytecode.push_back(static_cast((value_size & 0xff00) >> 8)); + m_bytecode.push_back(static_cast(value_size & 0x00ff)); + + for (const ValTableElem& val : m_values) { if (val.type == ValTableElemType::Number) { @@ -169,7 +188,9 @@ namespace Ark else if (val.type == ValTableElemType::PageAddr) { m_bytecode.push_back(Instruction::FUNC_TYPE); - pushNumber(static_cast(std::get(val.value))); + std::size_t addr = std::get(val.value); + m_bytecode.push_back(static_cast((addr & 0xff00) >> 8)); + m_bytecode.push_back(static_cast(addr & 0x00ff)); } else throw CompilationError("trying to put a value in the value table, but the type isn't handled.\nCertainly a logic problem in the compiler source code"); @@ -189,7 +210,7 @@ namespace Ark return n; } - std::optional Compiler::isOperator(const std::string& name) noexcept + std::optional Compiler::getOperator(const std::string& name) noexcept { auto it = std::find(internal::operators.begin(), internal::operators.end(), name); if (it != internal::operators.end()) @@ -197,7 +218,7 @@ namespace Ark return std::nullopt; } - std::optional Compiler::isBuiltin(const std::string& name) noexcept + std::optional Compiler::getBuiltin(const std::string& name) noexcept { auto it = std::find_if(Builtins::builtins.begin(), Builtins::builtins.end(), [&name](const std::pair& element) -> bool { @@ -229,13 +250,22 @@ namespace Ark } } - void Compiler::pushSpecificInstArgc(Instruction inst, uint16_t previous, int p) noexcept + uint16_t Compiler::computeSpecificInstArgc(Instruction inst, uint16_t previous) noexcept { - if (inst == Instruction::LIST) - pushNumber(previous, page_ptr(p)); - else if (inst == Instruction::APPEND || inst == Instruction::APPEND_IN_PLACE || - inst == Instruction::CONCAT || inst == Instruction::CONCAT_IN_PLACE) - pushNumber(previous - 1, page_ptr(p)); + switch (inst) + { + case Instruction::LIST: + return previous; + + case Instruction::APPEND: + case Instruction::APPEND_IN_PLACE: + case Instruction::CONCAT: + case Instruction::CONCAT_IN_PLACE: + return previous - 1; + + default: + return 0; + } } bool Compiler::mayBeFromPlugin(const std::string& name) noexcept @@ -248,55 +278,47 @@ namespace Ark return it != m_plugins.end(); } - void Compiler::throwCompilerError(const std::string& message, const Node& node) + void Compiler::compilerWarning(const std::string& message, const Node& node) { - throw CompilationError(makeNodeBasedErrorCtx(message, node)); + if (m_options & FeatureShowWarnings) + std::cout << termcolor::yellow << "Warning " << termcolor::reset << makeNodeBasedErrorCtx(message, node) << "\n"; } - void Compiler::compilerWarning(const std::string& message, const Node& node) + void Compiler::throwCompilerError(const std::string& message, const Node& node) { - if (m_options & FeatureShowWarnings) - std::cerr << termcolor::yellow << "Warning " << termcolor::reset << makeNodeBasedErrorCtx(message, node) << "\n"; + throw CompilationError(makeNodeBasedErrorCtx(message, node)); } - void Compiler::_compile(const Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name) + void Compiler::compileExpression(const Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name) { // register symbols if (x.nodeType() == NodeType::Symbol) - compileSymbol(x, p, produces_result); + compileSymbol(x, p, is_result_unused); else if (x.nodeType() == NodeType::GetField) { - std::string name = x.string(); - // 'name' shouldn't be a builtin/operator, we can use it as-is uint16_t i = addSymbol(x); - - page(p).emplace_back(Instruction::GET_FIELD); - pushNumber(i, page_ptr(p)); + page(p).emplace_back(Instruction::GET_FIELD, i); } // register values else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number) { uint16_t i = addValue(x); - if (!produces_result) - { - page(p).emplace_back(Instruction::LOAD_CONST); - pushNumber(i, page_ptr(p)); - } + if (!is_result_unused) + page(p).emplace_back(Instruction::LOAD_CONST, i); } // empty code block should be nil else if (x.constList().empty()) { - if (!produces_result) + if (!is_result_unused) { - auto it_builtin = isBuiltin("nil"); - page(p).emplace_back(Instruction::BUILTIN); - pushNumber(static_cast(it_builtin.value()), page_ptr(p)); + static const std::optional nil = getBuiltin("nil"); + page(p).emplace_back(Instruction::BUILTIN, static_cast(nil.value())); } } // specific instructions - else if (auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && isSpecific(c0.string()).has_value()) - compileSpecific(c0, x, p, produces_result); + else if (auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getSpecific(c0.string()).has_value()) + compileSpecific(c0, x, p, is_result_unused); // registering structures else if (x.constList()[0].nodeType() == NodeType::Keyword) { @@ -305,7 +327,7 @@ namespace Ark switch (n) { case Keyword::If: - compileIf(x, p, produces_result, is_terminal, var_name); + compileIf(x, p, is_result_unused, is_terminal, var_name); break; case Keyword::Set: @@ -317,17 +339,17 @@ namespace Ark break; case Keyword::Fun: - compileFunction(x, p, produces_result, var_name); + compileFunction(x, p, is_result_unused, var_name); break; case Keyword::Begin: { for (std::size_t i = 1, size = x.constList().size(); i < size; ++i) - _compile( + compileExpression( x.constList()[i], p, // All the nodes in a begin (except for the last one) are producing a result that we want to drop. - (i != size - 1) ? true : produces_result, + (i != size - 1) ? true : is_result_unused, // If the begin is a terminal node, only its last node is terminal. is_terminal ? (i == size - 1) : false, var_name); @@ -343,11 +365,11 @@ namespace Ark break; case Keyword::Quote: - compileQuote(x, p, produces_result, is_terminal, var_name); + compileQuote(x, p, is_result_unused, is_terminal, var_name); break; case Keyword::Del: - compileDel(x, p); + page(p).emplace_back(Instruction::DEL, addSymbol(x.constList()[1])); break; } } @@ -355,40 +377,32 @@ namespace Ark { // if we are here, we should have a function name // push arguments first, then function name, then call it - handleCalls(x, p, produces_result, is_terminal, var_name); + handleCalls(x, p, is_result_unused, is_terminal, var_name); } } - void Compiler::compileSymbol(const Node& x, int p, bool produces_result) + void Compiler::compileSymbol(const Node& x, int p, bool is_result_unused) { std::string name = x.string(); - if (auto it_builtin = isBuiltin(name)) - { - page(p).emplace_back(Instruction::BUILTIN); - pushNumber(static_cast(it_builtin.value()), page_ptr(p)); - } - else if (auto it_operator = isOperator(name)) + if (auto it_builtin = getBuiltin(name)) + page(p).emplace_back(Instruction::BUILTIN, static_cast(it_builtin.value())); + else if (auto it_operator = getOperator(name)) page(p).emplace_back(static_cast(Instruction::FIRST_OPERATOR + it_operator.value())); - else // var-use - { - uint16_t i = addSymbol(x); - - page(p).emplace_back(Instruction::LOAD_SYMBOL); - pushNumber(i, page_ptr(p)); - } + else + page(p).emplace_back(Instruction::LOAD_SYMBOL, addSymbol(x)); // using the variable - if (produces_result) + if (is_result_unused) { compilerWarning("Statement has no effect", x); page(p).push_back(Instruction::POP); } } - void Compiler::compileSpecific(const Node& c0, const Node& x, int p, bool produces_result) + void Compiler::compileSpecific(const Node& c0, const Node& x, int p, bool is_result_unused) { std::string name = c0.string(); - Instruction inst = isSpecific(name).value(); + Instruction inst = getSpecific(name).value(); // length of at least 1 since we got a symbol name uint16_t argc = countArkObjects(x.constList()) - 1; @@ -405,53 +419,49 @@ namespace Ark uint16_t diff = i - j; while (j < i) { - _compile(x.constList()[j], p, false, false); + compileExpression(x.constList()[j], p, false, false); ++j; } - _compile(x.constList()[i], p, false, false); + compileExpression(x.constList()[i], p, false, false); i -= diff; } // put inst and number of arguments - page(p).emplace_back(inst); - pushSpecificInstArgc(inst, argc, p); + page(p).emplace_back(inst, computeSpecificInstArgc(inst, argc)); - if (produces_result && name.back() != '!') // in-place functions never push a value + if (is_result_unused && name.back() != '!') // in-place functions never push a value { compilerWarning("Ignoring return value of function", x); page(p).push_back(Instruction::POP); } } - void Compiler::compileIf(const Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name) + void Compiler::compileIf(const Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name) { // compile condition - _compile(x.constList()[1], p, false, false); + compileExpression(x.constList()[1], p, false, false); // jump only if needed to the if - page(p).push_back(Instruction::POP_JUMP_IF_TRUE); std::size_t jump_to_if_pos = page(p).size(); - // absolute address to jump to if condition is true - pushNumber(0_u16, page_ptr(p)); + page(p).push_back(Instruction::POP_JUMP_IF_TRUE); // else code if (x.constList().size() == 4) // we have an else clause - _compile(x.constList()[3], p, produces_result, is_terminal, var_name); + compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name); // when else is finished, jump to end - page(p).push_back(Instruction::JUMP); std::size_t jump_to_end_pos = page(p).size(); - pushNumber(0_u16, page_ptr(p)); + page(p).push_back(Instruction::JUMP); - // set jump to if pos - setNumberAt(p, jump_to_if_pos, page(p).size()); + // absolute address to jump to if condition is true + page(p)[jump_to_if_pos].data = static_cast(page(p).size()); // if code - _compile(x.constList()[2], p, produces_result, is_terminal, var_name); + compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name); // set jump to end pos - setNumberAt(p, jump_to_end_pos, page(p).size()); + page(p)[jump_to_end_pos].data = static_cast(page(p).size()); } - void Compiler::compileFunction(const Node& x, int p, bool produces_result, const std::string& var_name) + void Compiler::compileFunction(const Node& x, int p, bool is_result_unused, const std::string& var_name) { // capture, if needed for (auto it = x.constList()[1].constList().begin(), it_end = x.constList()[1].constList().end(); it != it_end; ++it) @@ -464,40 +474,36 @@ namespace Ark // we didn't find it in the defined symbol list, thus we can't capture it throwCompilerError("Can not capture " + it->string() + " because it is referencing an unbound variable.", *it); } - page(p).emplace_back(Instruction::CAPTURE); + addDefinedSymbol(it->string()); - uint16_t var_id = addSymbol(*it); - pushNumber(var_id, page_ptr(p)); + page(p).emplace_back(Instruction::CAPTURE, addSymbol(*it)); } } // create new page for function body m_code_pages.emplace_back(); std::size_t page_id = m_code_pages.size() - 1; - // load value on the stack - page(p).emplace_back(Instruction::LOAD_CONST); - // save page_id into the constants table as PageAddr - pushNumber(addValue(page_id, x), page_ptr(p)); + // save page_id into the constants table as PageAddr and load the const + page(p).emplace_back(Instruction::LOAD_CONST, addValue(page_id, x)); // pushing arguments from the stack into variables in the new scope for (auto it = x.constList()[1].constList().begin(), it_end = x.constList()[1].constList().end(); it != it_end; ++it) { if (it->nodeType() == NodeType::Symbol) { - page(page_id).emplace_back(Instruction::MUT); - uint16_t var_id = addSymbol(*it); addDefinedSymbol(it->string()); - pushNumber(var_id, page_ptr(page_id)); + page(page_id).emplace_back(Instruction::MUT, addSymbol(*it)); } } // push body of the function - _compile(x.constList()[2], page_id, false, true, var_name); + compileExpression(x.constList()[2], page_id, false, true, var_name); // return last value on the stack page(page_id).emplace_back(Instruction::RET); - if (produces_result) + // if the computed function is unused, pop it + if (is_result_unused) { compilerWarning("Unused declared function", x); page(p).push_back(Instruction::POP); @@ -506,20 +512,22 @@ namespace Ark void Compiler::compileLetMutSet(Keyword n, const Node& x, int p) { + std::string name = x.constList()[1].string(); uint16_t i = addSymbol(x.constList()[1]); if (n != Keyword::Set) - addDefinedSymbol(x.constList()[1].string()); + addDefinedSymbol(name); // put value before symbol id - putValue(x, p, false); + // starting at index = 2 because x is a (let|mut|set variable ...) node + for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx) + compileExpression(x.constList()[idx], p, false, false, name); if (n == Keyword::Let) - page(p).push_back(Instruction::LET); + page(p).emplace_back(Instruction::LET, i); else if (n == Keyword::Mut) - page(p).push_back(Instruction::MUT); + page(p).emplace_back(Instruction::MUT, i); else - page(p).push_back(Instruction::STORE); - pushNumber(i, page_ptr(p)); + page(p).emplace_back(Instruction::STORE, i); } void Compiler::compileWhile(const Node& x, int p) @@ -527,36 +535,33 @@ namespace Ark // save current position to jump there at the end of the loop std::size_t current = page(p).size(); // push condition - _compile(x.constList()[1], p, false, false); + compileExpression(x.constList()[1], p, false, false); // absolute jump to end of block if condition is false - page(p).push_back(Instruction::POP_JUMP_IF_FALSE); std::size_t jump_to_end_pos = page(p).size(); - // absolute address to jump to if condition is false - pushNumber(0_u16, page_ptr(p)); + page(p).push_back(Instruction::POP_JUMP_IF_FALSE); // push code to page - _compile(x.constList()[2], p, true, false); + compileExpression(x.constList()[2], p, true, false); + // loop, jump to the condition - page(p).push_back(Instruction::JUMP); - // abosolute address - pushNumber(static_cast(current), page_ptr(p)); - // set jump to end pos - setNumberAt(p, jump_to_end_pos, page(p).size()); + page(p).emplace_back(Instruction::JUMP, current); + + // absolute address to jump to if condition is false + page(p)[jump_to_end_pos].data = static_cast(page(p).size()); } - void Compiler::compileQuote(const Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name) + void Compiler::compileQuote(const Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name) { // create new page for quoted code m_code_pages.emplace_back(); std::size_t page_id = m_code_pages.size() - 1; - _compile(x.constList()[1], page_id, false, is_terminal, var_name); + compileExpression(x.constList()[1], page_id, false, is_terminal, var_name); page(page_id).emplace_back(Instruction::RET); // return to the last frame // call it uint16_t id = addValue(page_id, x); // save page_id into the constants table as PageAddr - page(p).emplace_back(Instruction::LOAD_CONST); - pushNumber(id, page_ptr(p)); + page(p).emplace_back(Instruction::LOAD_CONST, id); - if (produces_result) + if (is_result_unused) { compilerWarning("Unused quote expression", x); page(p).push_back(Instruction::POP); @@ -570,24 +575,14 @@ namespace Ark // save plugin name to use it later m_plugins.push_back(x.constList()[1].string()); // add plugin instruction + id of the constant refering to the plugin path - page(p).emplace_back(Instruction::PLUGIN); - pushNumber(id, page_ptr(p)); + page(p).emplace_back(Instruction::PLUGIN, id); } - void Compiler::compileDel(const Node& x, int p) - { - // get id of symbol to delete - uint16_t i = addSymbol(x.constList()[1]); - - page(p).emplace_back(Instruction::DEL); - pushNumber(i, page_ptr(p)); - } - - void Compiler::handleCalls(const Node& x, int p, bool produces_result, bool is_terminal, const std::string& var_name) + void Compiler::handleCalls(const Node& x, int p, bool is_result_unused, bool is_terminal, const std::string& var_name) { m_temp_pages.emplace_back(); int proc_page = -static_cast(m_temp_pages.size()); - _compile(x.constList()[0], proc_page, false, false); // storing proc + compileExpression(x.constList()[0], proc_page, false, false); // storing proc // trying to handle chained closure.field.field.field... std::size_t n = 1; // we need it later @@ -596,17 +591,15 @@ namespace Ark { if (x.constList()[n].nodeType() == NodeType::GetField) { - _compile(x.constList()[n], proc_page, false, false); + compileExpression(x.constList()[n], proc_page, false, false); n++; } else break; } - std::size_t proc_page_len = m_temp_pages.back().size(); - // we know that operators take only 1 instruction, so if there are more // it's a builtin/function - if (proc_page_len > 1) + if (m_temp_pages.back()[0].opcode < Instruction::FIRST_OPERATOR) { if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string()) { @@ -615,26 +608,22 @@ namespace Ark // push the arguments in reverse order for (std::size_t i = x.constList().size() - 1; i >= n; --i) - _compile(x.constList()[i], p, false, false); + compileExpression(x.constList()[i], p, false, false); // jump to the top of the function - page(p).push_back(Instruction::JUMP); - pushNumber(0_u16, page_ptr(p)); - + page(p).emplace_back(Instruction::JUMP, 0_u16); return; // skip the possible Instruction::POP at the end } else { // push arguments on current page for (auto exp = x.constList().begin() + n, exp_end = x.constList().end(); exp != exp_end; ++exp) - _compile(*exp, p, false, false); + compileExpression(*exp, p, false, false); // push proc from temp page - for (auto const& inst : m_temp_pages.back()) - page(p).push_back(inst); + for (const Word& word : m_temp_pages.back()) + page(p).push_back(word); m_temp_pages.pop_back(); - // call the procedure - page(p).push_back(Instruction::CALL); // number of arguments std::size_t args_count = 0; for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it) @@ -643,23 +632,24 @@ namespace Ark it->nodeType() != NodeType::Capture) args_count++; } - pushNumber(static_cast(args_count), page_ptr(p)); + // call the procedure + page(p).emplace_back(Instruction::CALL, args_count); } } else // operator { // retrieve operator - auto op_inst = m_temp_pages.back()[0]; + auto op = m_temp_pages.back()[0]; m_temp_pages.pop_back(); - if (op_inst == Instruction::ASSERT) - produces_result = false; + if (op.opcode == Instruction::ASSERT) + is_result_unused = false; // push arguments on current page std::size_t exp_count = 0; for (std::size_t index = n, size = x.constList().size(); index < size; ++index) { - _compile(x.constList()[index], p, false, false); + compileExpression(x.constList()[index], p, false, false); if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::GetField && @@ -670,13 +660,13 @@ namespace Ark // in order to be able to handle things like (op A B C D...) // which should be transformed into A B op C op D op... if (exp_count >= 2) - page(p).push_back(op_inst); + page(p).emplace_back(op.opcode, 2); // TODO generalize to n arguments (n >= 2) } if (exp_count == 1) { - if (isUnaryInst(static_cast(op_inst))) - page(p).push_back(op_inst); + if (isUnaryInst(static_cast(op.opcode))) + page(p).push_back(op.opcode); else throwCompilerError("Operator needs two arguments, but was called with only one", x.constList()[0]); } @@ -684,7 +674,7 @@ namespace Ark // need to check we didn't push the (op A B C D...) things for operators not supporting it if (exp_count > 2) { - switch (op_inst) + switch (op.opcode) { // authorized instructions case Instruction::ADD: [[fallthrough]]; @@ -699,26 +689,17 @@ namespace Ark default: throwCompilerError( "can not create a chained expression (of length " + std::to_string(exp_count) + - ") for operator `" + std::string(internal::operators[static_cast(op_inst - Instruction::FIRST_OPERATOR)]) + + ") for operator `" + std::string(internal::operators[static_cast(op.opcode - Instruction::FIRST_OPERATOR)]) + "'. You most likely forgot a `)'.", x); } } } - if (produces_result) + if (is_result_unused) page(p).push_back(Instruction::POP); } - void Compiler::putValue(const Node& x, int p, bool produces_result) - { - std::string name = x.constList()[1].string(); - - // starting at index = 2 because x is a (let|mut|set variable ...) node - for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx) - _compile(x.constList()[idx], p, produces_result, false, name); - } - uint16_t Compiler::addSymbol(const Node& sym) { // otherwise, add the symbol, and return its id in the table @@ -817,18 +798,4 @@ namespace Ark return suggestion; } - - void Compiler::pushNumber(uint16_t n, std::vector* page) noexcept - { - if (page == nullptr) - { - m_bytecode.push_back((n & 0xff00) >> 8); - m_bytecode.push_back(n & 0x00ff); - } - else - { - page->emplace_back((n & 0xff00) >> 8); - page->emplace_back(n & 0x00ff); - } - } } diff --git a/src/arkreactor/Compiler/Macros/Processor.cpp b/src/arkreactor/Compiler/Macros/Processor.cpp index e678aea88..fe304c9b6 100644 --- a/src/arkreactor/Compiler/Macros/Processor.cpp +++ b/src/arkreactor/Compiler/Macros/Processor.cpp @@ -602,7 +602,6 @@ namespace Ark::internal case NodeType::Capture: case NodeType::GetField: - case NodeType::Closure: return false; case NodeType::Keyword: diff --git a/src/arkreactor/Utils.cpp b/src/arkreactor/Utils.cpp index 03799843b..b18b1a00e 100644 --- a/src/arkreactor/Utils.cpp +++ b/src/arkreactor/Utils.cpp @@ -2,34 +2,6 @@ namespace Ark::Utils { - int decPlaces(double d) - { - constexpr double precision = 1e-7; - double temp = 0.0; - int decimal_places = 0; - - do - { - d *= 10; - temp = d - static_cast(d); - decimal_places++; - } while (temp > precision && decimal_places < std::numeric_limits::digits10); - - return decimal_places; - } - - int digPlaces(double d) - { - int digit_places = 0; - int i = static_cast(d); - while (i != 0) - { - digit_places++; - i /= 10; - } - return digit_places; - } - int levenshteinDistance(const std::string& str1, const std::string& str2) { std::size_t str1_len = str1.size(); diff --git a/src/arkreactor/VM/Scope.cpp b/src/arkreactor/VM/Scope.cpp index b6c3a4a83..9cbe5f9f0 100644 --- a/src/arkreactor/VM/Scope.cpp +++ b/src/arkreactor/VM/Scope.cpp @@ -4,24 +4,35 @@ namespace Ark::internal { - Scope::Scope() noexcept + Scope::Scope() noexcept : + m_min_id(std::numeric_limits::max()), m_max_id(0) { m_data.reserve(3); } void Scope::push_back(uint16_t id, Value&& val) noexcept { + if (id < m_min_id) + m_min_id = id; + if (id > m_max_id) + m_max_id = id; + m_data.emplace_back(std::move(id), std::move(val)); } void Scope::push_back(uint16_t id, const Value& val) noexcept { + if (id < m_min_id) + m_min_id = id; + if (id > m_max_id) + m_max_id = id; + m_data.emplace_back(id, val); } bool Scope::has(uint16_t id) noexcept { - return operator[](id) != nullptr; + return m_min_id <= id && m_max_id <= id && operator[](id) != nullptr; } Value* Scope::operator[](uint16_t id) noexcept @@ -34,6 +45,16 @@ namespace Ark::internal return nullptr; } + const Value* Scope::operator[](uint16_t id) const noexcept + { + for (std::size_t i = 0, end = m_data.size(); i < end; ++i) + { + if (m_data[i].first == id) + return &m_data[i].second; + } + return nullptr; + } + uint16_t Scope::idFromValue(const Value& val) const noexcept { for (std::size_t i = 0, end = m_data.size(); i < end; ++i) @@ -48,4 +69,22 @@ namespace Ark::internal { return m_data.size(); } + + bool operator==(const Scope& A, const Scope& B) noexcept + { + const std::size_t size = A.size(); + if (size != B.size()) + return false; + if (A.m_min_id != B.m_min_id || A.m_max_id != B.m_max_id) + return false; + + // assuming we have the same closure page address, the element should be in the same order + for (std::size_t i = 0; i < size; ++i) + { + if (A.m_data[i] != B.m_data[i]) + return false; + } + + return true; + } } diff --git a/src/arkreactor/VM/State.cpp b/src/arkreactor/VM/State.cpp index 97c963ec1..e2093ac60 100644 --- a/src/arkreactor/VM/State.cpp +++ b/src/arkreactor/VM/State.cpp @@ -325,7 +325,7 @@ namespace Ark while (m_bytecode[i] == Instruction::CODE_SEGMENT_START) { i++; - uint16_t size = readNumber(i); + uint16_t size = readNumber(i) * 4; // because the instructions are on 4 bytes i++; m_pages.emplace_back(); diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index a705fb945..9116d3c38 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -89,7 +89,7 @@ namespace Ark { namespace fs = std::filesystem; - const std::string file = m_state.m_constants[id].stringRef().toString(); + const std::string file = m_state.m_constants[id].stringRef(); std::string path = file; // bytecode loaded from file @@ -270,9 +270,11 @@ namespace Ark while (m_running && context.fc > untilFrameCount) { // get current instruction - uint8_t inst = m_state.m_pages[context.pp][context.ip]; + [[maybe_unused]] uint8_t padding = m_state.m_pages[context.pp][context.ip]; + uint8_t inst = m_state.m_pages[context.pp][context.ip + 1]; + uint16_t arg = (static_cast(m_state.m_pages[context.pp][context.ip + 2]) << 8) + static_cast(m_state.m_pages[context.pp][context.ip + 3]); + context.ip += 4; - // and it's time to du-du-du-du-duel! switch (inst) { #pragma region "Instructions" @@ -284,8 +286,7 @@ namespace Ark Job: Load a symbol from its id onto the stack */ - ++context.ip; - context.last_symbol = readNumber(context); + context.last_symbol = arg; if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr) // push internal reference, shouldn't break anything so far @@ -304,18 +305,15 @@ namespace Ark and push a Closure with the page address + environment instead of the constant */ - ++context.ip; - uint16_t id = readNumber(context); - - if (context.saved_scope && m_state.m_constants[id].valueType() == ValueType::PageAddr) + if (context.saved_scope && m_state.m_constants[arg].valueType() == ValueType::PageAddr) { - push(Value(Closure(context.saved_scope.value(), m_state.m_constants[id].pageAddr())), context); + push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context); context.saved_scope.reset(); } else { // push internal ref - push(&(m_state.m_constants[id]), context); + push(&(m_state.m_constants[arg]), context); } break; @@ -329,11 +327,8 @@ namespace Ark Remove the value from the stack no matter what it is */ - ++context.ip; - uint16_t id = readNumber(context); - if (*popAndResolveAsPtr(context) == Builtins::trueSym) - context.ip = static_cast(id) - 1; // because we are doing a ++context.ip right after this + context.ip = static_cast(arg) * 4; // instructions are 4 bytes break; } @@ -346,20 +341,17 @@ namespace Ark couldn't find a scope where the variable exists */ - ++context.ip; - uint16_t id = readNumber(context); - - if (Value* var = findNearestVariable(id, context); var != nullptr) + if (Value* var = findNearestVariable(arg, context); var != nullptr) { if (var->isConst()) - throwVMError(ErrorKind::Mutability, "Can not modify a constant: " + m_state.m_symbols[id]); + throwVMError(ErrorKind::Mutability, "Can not modify a constant: " + m_state.m_symbols[arg]); *var = *popAndResolveAsPtr(context); var->setConst(false); break; } - throwVMError(ErrorKind::Scope, "Unbound variable " + m_state.m_symbols[id] + ", can not change its value"); + throwVMError(ErrorKind::Scope, "Unbound variable " + m_state.m_symbols[arg] + ", can not change its value"); break; } @@ -371,16 +363,13 @@ namespace Ark following the given symbol id (cf symbols table) */ - ++context.ip; - uint16_t id = readNumber(context); - // check if we are redefining a variable - if (auto val = (*context.locals.back())[id]; val != nullptr) - throwVMError(ErrorKind::Mutability, "Can not use 'let' to redefine the variable " + m_state.m_symbols[id]); + if (auto val = (*context.locals.back())[arg]; val != nullptr) + throwVMError(ErrorKind::Mutability, "Can not use 'let' to redefine the variable " + m_state.m_symbols[arg]); Value val = *popAndResolveAsPtr(context); val.setConst(true); - (*context.locals.back()).push_back(id, val); + (*context.locals.back()).push_back(arg, val); break; } @@ -393,11 +382,8 @@ namespace Ark the value from the stack no matter what it is */ - ++context.ip; - uint16_t id = readNumber(context); - if (*popAndResolveAsPtr(context) == Builtins::falseSym) - context.ip = static_cast(id) - 1; // because we are doing a ++context.ip right after this + context.ip = static_cast(arg) * 4; // instructions are 4 bytes break; } @@ -408,10 +394,7 @@ namespace Ark Job: Jump to the provided address */ - ++context.ip; - uint16_t id = readNumber(context); - - context.ip = static_cast(id) - 1; // because we are doing a ++context.ip right after this + context.ip = static_cast(arg) * 4; // instructions are 4 bytes break; } @@ -466,7 +449,7 @@ namespace Ark ErrorKind::VM, "Maximum recursion depth exceeded.\n" "You could consider rewriting your function to make use of tail-call optimization."); - call(context); + call(context, arg); break; } @@ -479,17 +462,14 @@ namespace Ark they were created */ - ++context.ip; - uint16_t id = readNumber(context); - if (!context.saved_scope) context.saved_scope = std::make_shared(); - Value* ptr = (*context.locals.back())[id]; + Value* ptr = (*context.locals.back())[arg]; if (!ptr) - throwVMError(ErrorKind::Scope, "Couldn't capture '" + m_state.m_symbols[id] + "' as it is currently unbound"); + throwVMError(ErrorKind::Scope, "Couldn't capture '" + m_state.m_symbols[arg] + "' as it is currently unbound"); ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr; - (*context.saved_scope.value()).push_back(id, *ptr); + (*context.saved_scope.value()).push_back(arg, *ptr); break; } @@ -501,10 +481,7 @@ namespace Ark Job: Push the builtin function object on the stack */ - ++context.ip; - uint16_t id = readNumber(context); - - push(Builtins::builtins[id].second, context); + push(Builtins::builtins[arg].second, context); break; } @@ -516,16 +493,13 @@ namespace Ark named following the given symbol id (cf symbols table) */ - ++context.ip; - uint16_t id = readNumber(context); - Value val = *popAndResolveAsPtr(context); val.setConst(false); // avoid adding the pair (id, _) multiple times, with different values - Value* local = (*context.locals.back())[id]; + Value* local = (*context.locals.back())[arg]; if (local == nullptr) - (*context.locals.back()).push_back(id, val); + (*context.locals.back()).push_back(arg, val); else *local = val; @@ -539,10 +513,7 @@ namespace Ark Job: Remove a variable/constant named following the given symbol id (cf symbols table) */ - ++context.ip; - uint16_t id = readNumber(context); - - if (Value* var = findNearestVariable(id, context); var != nullptr) + if (Value* var = findNearestVariable(arg, context); var != nullptr) { if (var->valueType() == ValueType::User) var->usertypeRef().del(); @@ -550,7 +521,7 @@ namespace Ark break; } - throwVMError(ErrorKind::Scope, "Unbound variable: " + m_state.m_symbols[id]); + throwVMError(ErrorKind::Scope, "Unbound variable: " + m_state.m_symbols[arg]); break; } @@ -572,16 +543,14 @@ namespace Ark stored in TS. Pop TS and push the value of field read on the stack */ - ++context.ip; - uint16_t id = readNumber(context); - Value* var = popAndResolveAsPtr(context); if (var->valueType() != ValueType::Closure) - throwVMError(ErrorKind::Type, "The variable `" + m_state.m_symbols[context.last_symbol] + "' isn't a closure, can not get the field `" + m_state.m_symbols[id] + "' from it"); + throwVMError(ErrorKind::Type, "The variable `" + m_state.m_symbols[context.last_symbol] + "' isn't a closure, can not get the field `" + m_state.m_symbols[arg] + "' from it"); - if (Value* field = (*var->refClosure().scope())[id]; field != nullptr) + if (Value* field = (*var->refClosure().scope())[arg]; field != nullptr) { // check for CALL instruction + // doing a +1 on the IP to read the instruction because context.ip is already on the next instruction word (the padding) if (static_cast(context.ip) + 1 < m_state.m_pages[context.pp].size() && m_state.m_pages[context.pp][context.ip + 1] == Instruction::CALL) { context.locals.push_back(var->refClosure().scope()); @@ -592,7 +561,7 @@ namespace Ark break; } - throwVMError(ErrorKind::Scope, "Couldn't find the variable " + m_state.m_symbols[id] + " in the closure enviroment"); + throwVMError(ErrorKind::Scope, "Couldn't find the variable " + m_state.m_symbols[arg] + " in the closure enviroment"); break; } @@ -604,10 +573,7 @@ namespace Ark Raise an error if it couldn't find the module. */ - ++context.ip; - uint16_t id = readNumber(context); - - loadPlugin(id, context); + loadPlugin(arg, context); break; } @@ -617,14 +583,12 @@ namespace Ark Takes at least 0 arguments and push a list on the stack. The content is pushed in reverse order */ - ++context.ip; - uint16_t count = readNumber(context); Value l(ValueType::List); - if (count != 0) - l.list().reserve(count); + if (arg != 0) + l.list().reserve(arg); - for (uint16_t i = 0; i < count; ++i) + for (uint16_t i = 0; i < arg; ++i) l.push_back(*popAndResolveAsPtr(context)); push(std::move(l), context); break; @@ -632,9 +596,6 @@ namespace Ark case Instruction::APPEND: { - ++context.ip; - uint16_t count = readNumber(context); - Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) types::generateError( @@ -645,9 +606,9 @@ namespace Ark const uint16_t size = list->constList().size(); Value obj = Value(*list); - obj.list().reserve(size + count); + obj.list().reserve(size + arg); - for (uint16_t i = 0; i < count; ++i) + for (uint16_t i = 0; i < arg; ++i) obj.push_back(*popAndResolveAsPtr(context)); push(std::move(obj), context); break; @@ -655,9 +616,6 @@ namespace Ark case Instruction::CONCAT: { - ++context.ip; - uint16_t count = readNumber(context); - Value* list = popAndResolveAsPtr(context); if (list->valueType() != ValueType::List) types::generateError( @@ -667,7 +625,7 @@ namespace Ark Value obj = Value(*list); - for (uint16_t i = 0; i < count; ++i) + for (uint16_t i = 0; i < arg; ++i) { Value* next = popAndResolveAsPtr(context); @@ -686,9 +644,6 @@ namespace Ark case Instruction::APPEND_IN_PLACE: { - ++context.ip; - uint16_t count = readNumber(context); - Value* list = popAndResolveAsPtr(context); if (list->isConst()) @@ -699,7 +654,7 @@ namespace Ark { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); - for (uint16_t i = 0; i < count; ++i) + for (uint16_t i = 0; i < arg; ++i) list->push_back(*popAndResolveAsPtr(context)); break; @@ -707,9 +662,6 @@ namespace Ark case Instruction::CONCAT_IN_PLACE: { - ++context.ip; - uint16_t count = readNumber(context); - Value* list = popAndResolveAsPtr(context); if (list->isConst()) @@ -720,7 +672,7 @@ namespace Ark { { types::Contract { { types::Typedef("list", ValueType::List) } } } }, { *list }); - for (uint16_t i = 0; i < count; ++i) + for (uint16_t i = 0; i < arg; ++i) { Value* next = popAndResolveAsPtr(context); @@ -960,7 +912,7 @@ namespace Ark else { Value b = *a; - b.stringRef().erase_front(0); + b.stringRef().erase(b.stringRef().begin()); push(std::move(b), context); } } @@ -1023,7 +975,7 @@ namespace Ark { *a, *b }); if (*a == Builtins::falseSym) - throw AssertionFailed(b->stringRef().toString()); + throw AssertionFailed(b->stringRef()); break; } @@ -1038,7 +990,7 @@ namespace Ark { *a }); double val; - if (Utils::isDouble(a->string().c_str(), &val)) + if (Utils::isDouble(a->string(), &val)) push(Value(val), context); else push(Builtins::nil, context); @@ -1137,7 +1089,7 @@ namespace Ark if (field->valueType() != ValueType::String) throw TypeError("Argument no 2 of hasField should be a String"); - auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef().toString()); + auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef()); if (it == m_state.m_symbols.end()) { push(Builtins::falseSym, context); @@ -1165,9 +1117,6 @@ namespace Ark break; } - // move forward - ++context.ip; - #ifdef ARK_PROFILER_MIPS ++instructions_executed; #endif @@ -1294,7 +1243,7 @@ namespace Ark } std::cerr << termcolor::reset - << "At IP: " << (saved_ip != -1 ? saved_ip : 0) + << "At IP: " << (saved_ip != -1 ? saved_ip / 4 : 0) // dividing by 4 because the instructions are actually on 4 bytes << ", PP: " << saved_pp << ", SP: " << saved_sp << "\n"; diff --git a/src/arkreactor/VM/Value.cpp b/src/arkreactor/VM/Value.cpp index d2e4fa60f..f54401a39 100644 --- a/src/arkreactor/VM/Value.cpp +++ b/src/arkreactor/VM/Value.cpp @@ -1,6 +1,6 @@ #include -#include +#include #define init_const_type(is_const, type) ((is_const ? (1 << 7) : 0) | static_cast(type)) @@ -72,10 +72,6 @@ namespace Ark {} Value::Value(const std::string& value) noexcept : - m_const_type(init_const_type(false, ValueType::String)), m_value(value.c_str()) - {} - - Value::Value(const String& value) noexcept : m_const_type(init_const_type(false, ValueType::String)), m_value(value) {} @@ -119,9 +115,9 @@ namespace Ark return std::get(m_value); } - String& Value::stringRef() + std::string& Value::stringRef() { - return std::get(m_value); + return std::get(m_value); } UserType& Value::usertypeRef() @@ -154,13 +150,12 @@ namespace Ark case ValueType::Number: { double d = number(); - os.precision(Utils::digPlaces(d) + Utils::decPlaces(d)); - os << d; + os << fmt::format("{}", d); break; } case ValueType::String: - os << string().c_str(); + os << string(); break; case ValueType::PageAddr: diff --git a/src/arkreactor/VM/Value/Closure.cpp b/src/arkreactor/VM/Value/Closure.cpp index 7ecdefbf8..937d1a676 100644 --- a/src/arkreactor/VM/Value/Closure.cpp +++ b/src/arkreactor/VM/Value/Closure.cpp @@ -44,4 +44,13 @@ namespace Ark::internal } os << ")"; } + + bool operator==(const Closure& A, const Closure& B) noexcept + { + // they do not come from the same closure builder + if (A.m_page_addr != B.m_page_addr) + return false; + + return *A.m_scope == *B.m_scope; + } } diff --git a/src/arkscript/main.cpp b/src/arkscript/main.cpp index 1fed0a43e..92e878459 100644 --- a/src/arkscript/main.cpp +++ b/src/arkscript/main.cpp @@ -17,19 +17,6 @@ int main(int argc, char** argv) { using namespace clipp; -// TODO remove once next major version of ArkScript is available -#if ARK_VERSION_MAJOR == 4 -# error "this code block should be removed from ArkScript 4.x.y" -#endif - { - namespace fs = std::filesystem; - fs::path program(argv[0]); - - if (program.stem() == "ark") - std::cout << termcolor::yellow << "Warning" << termcolor::reset << " the command `ark' is being deprecated in favor of `arkscript'" << std::endl; - } - - enum class mode { help, @@ -86,16 +73,16 @@ int main(int argc, char** argv) option("-a", "--all").set(segment, Ark::BytecodeSegment::All).doc("Display all the bytecode segments (default)") | option("-st", "--symbols").set(segment, Ark::BytecodeSegment::Symbols).doc("Display only the symbols table") | option("-vt", "--values").set(segment, Ark::BytecodeSegment::Values).doc("Display only the values table") + | ( + option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments") + , option("-p", "--page").set(segment, Ark::BytecodeSegment::Code).doc("Set the bytecode reader code segment to display") + & value("page", bcr_page) + ) ) , option("-s", "--slice").doc("Select a slice of instructions in the bytecode") & value("start", bcr_start) & value("end", bcr_end) ) - | ( - option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments") - , option("-p", "--page").doc("Set the bytecode reader code segment to display") - & value("page", bcr_page) - ) ) ) | ( @@ -171,8 +158,6 @@ int main(int argc, char** argv) " sizeof(ExecutionContext) = %zuB\n" "\nMisc\n" " sizeof(vector) = %zuB\n" - " sizeof(std::string) = %zuB\n" - " sizeof(String) = %zuB\n" " sizeof(char) = %zuB\n" "\nsizeof(Node) = %zuB\n", ARK_COMPILER, ARK_COMPILATION_OPTIONS, @@ -190,8 +175,6 @@ int main(int argc, char** argv) sizeof(Ark::internal::ExecutionContext), // misc sizeof(std::vector), - sizeof(std::string), - sizeof(String), sizeof(char), sizeof(Ark::internal::Node)); break; diff --git a/tests/arkscript/builtins-tests.ark b/tests/arkscript/builtins-tests.ark index 175d7cc3e..82a16e89d 100644 --- a/tests/arkscript/builtins-tests.ark +++ b/tests/arkscript/builtins-tests.ark @@ -16,7 +16,6 @@ (set tests (assert-eq (list:find [12] 12) 0 "list:find" tests)) (set tests (assert-eq (list:find [1 2 3] 2) 1 "list:find" tests)) (set tests (assert-eq (list:find [12] nil) -1 "list:find" tests)) - (set tests (assert-eq (list:removeAt base-list 1) [1 3] "list:removeAt" tests)) # DEPRECATED (set tests (assert-eq (list:slice base-list-enhanced 0 3 1) base-list "list:slice" tests)) (set tests (assert-eq (list:slice base-list-enhanced 0 1 1) [1] "list:slice" tests)) (set tests (assert-eq (list:slice base-list-enhanced 0 3 2) [1 3] "list:slice" tests)) diff --git a/tests/arkscript/list-tests.ark b/tests/arkscript/list-tests.ark index c7332488f..dd981e64a 100644 --- a/tests/arkscript/list-tests.ark +++ b/tests/arkscript/list-tests.ark @@ -27,8 +27,6 @@ (set tests (assert-eq (pop a 1) [1 3] "pop" tests)) (set tests (assert-eq (pop a 2) [1 2] "pop" tests)) (set tests (assert-eq a [1 2 3] "unmodified list" tests)) - (set tests (assert-eq (list:removeAt a 0) [2 3] "remove" tests)) # DEPRECATED - (set tests (assert-eq (list:removeAt a 2) [1 2] "remove" tests)) # DEPRECATED (set tests (assert-eq a [1 2 3] "unmodified list" tests)) (set tests (assert-eq (list:reverse a) [3 2 1] "reverse" tests)) (set tests (assert-eq a [1 2 3] "unmodified list" tests)) diff --git a/tests/arkscript/tests-tools.ark b/tests/arkscript/tests-tools.ark index 145be0ff9..529fef62e 100644 --- a/tests/arkscript/tests-tools.ark +++ b/tests/arkscript/tests-tools.ark @@ -1,31 +1,31 @@ (import "console.arkm") (let assert-eq (fun (val1 val2 message tests) { - (assert (= val1 val2) (str:format "%% (%%) - %% SHOULD BE EQUAL TO %%" message tests val1 val2)) + (assert (= val1 val2) (str:format "{} ({}) - {} SHOULD BE EQUAL TO {}" message tests val1 val2)) (+ 1 tests)})) (let assert-neq (fun (val1 val2 message tests) { - (assert (!= val1 val2) (str:format "%% (%%) - %% SHOULD BE NOT EQUAL TO %%" message tests val1 val2)) + (assert (!= val1 val2) (str:format "{} ({}) - {} SHOULD BE NOT EQUAL TO {}" message tests val1 val2)) (+ 1 tests)})) (let assert-gt (fun (val1 val2 message tests) { - (assert (> val1 val2) (str:format "%% (%%) - %% SHOULD BE GREATER THAN %%" message tests val1 val2)) + (assert (> val1 val2) (str:format "{} ({}) - {} SHOULD BE GREATER THAN {}" message tests val1 val2)) (+ 1 tests)})) (let assert-ge (fun (val1 val2 message tests) { - (assert (>= val1 val2) (str:format "%% (%%) - %% SHOULD BE GREATER OR EQUAL TO %%" message tests val1 val2)) + (assert (>= val1 val2) (str:format "{} ({}) - {} SHOULD BE GREATER OR EQUAL TO {}" message tests val1 val2)) (+ 1 tests)})) (let assert-lt (fun (val1 val2 message tests) { - (assert (< val1 val2) (str:format "%% (%%) - %% SHOULD BE LESSER THAN %%" message tests val1 val2)) + (assert (< val1 val2) (str:format "{} ({}) - {} SHOULD BE LESSER THAN {}" message tests val1 val2)) (+ 1 tests)})) (let assert-le (fun (val1 val2 message tests) { - (assert (<= val1 val2) (str:format "%% (%%) - %% SHOULD BE LESSER OR EQUAL TO %%" message tests val1 val2)) + (assert (<= val1 val2) (str:format "{} ({}) - {} SHOULD BE LESSER OR EQUAL TO {}" message tests val1 val2)) (+ 1 tests)})) (let assert-val (fun (val0 message tests) { - (assert val0 (str:format "%% (%%) - %% SHOULD BE TRUTHY" message tests val0)) + (assert val0 (str:format "{} ({}) - {} SHOULD BE TRUTHY" message tests val0)) (+ 1 tests)})) (let recap (fun (test-name tests time_) { diff --git a/tests/arkscript/vm-tests.ark b/tests/arkscript/vm-tests.ark index 5f7f01f68..6f78e9606 100644 --- a/tests/arkscript/vm-tests.ark +++ b/tests/arkscript/vm-tests.ark @@ -5,6 +5,15 @@ (let start-time (time)) (let closure (fun (&tests) ())) + (let make (fun (a b c) + (fun (&a &b &c) ()))) + (let make2 (fun (a b c) + (fun (&a &b &c) ()))) + (let closure_1 (make 1 2 3)) + (let closure_1_bis closure_1) + (let closure_2 (make 1 2 3)) + (let closure_3 (make 3 2 3)) + (let closure_4 (make2 1 2 3)) (set tests (assert-eq (+ 1 2) 3 "addition" tests)) (set tests (assert-eq (+ 1.5 2.5) 4.0 "addition (double)" tests)) @@ -87,6 +96,12 @@ (set tests (assert-val (hasField closure "tests") "hasField" tests)) (set tests (assert-val (not (hasField closure "12")) "not hasField" tests)) (set tests (assert-eq (toString closure) "(.tests=0)" "toString closure" tests)) + (set tests (assert-eq closure_1 closure_1_bis "closure comparison" tests)) + (set tests (assert-eq closure_1 closure_2 "closure comparison" tests)) + (set tests (assert-neq closure_1 closure_4 "closure comparison" tests)) + (set tests (assert-neq closure_2 closure_3 "closure comparison" tests)) + (set tests (assert-neq closure_1 closure_3 "closure comparison" tests)) + (set tests (assert-neq closure closure_1 "closure comparison" tests)) (recap "VM operations passed" tests (- (time) start-time))